Использование javascript document.write, поскольку считается блокирующим [дубликатом]

Из решений в этой нити я придумал следующее, вероятно, сверхсложное решение, которое позволяет вам отложить рендеринг любых html (скриптов) в блоке использования.

ИСПОЛЬЗОВАНИЕ

Создайте «раздел»

  1. Типичный сценарий: В частичном представлении включайте только блок один раз, независимо от того, сколько раз частичное просмотр повторяется на странице:
    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        
    }
    
  2. В частичном представлении включите блок для каждого использования частичного использования:
    @using (Html.Delayed()) {
        show me multiple times, @Model.Whatever
    }
    
  3. В частичном представлении включить только блок один раз, независимо от того, сколько раз частичное повторяется, но позже выведите его конкретно по имени when-i-call-you:
    @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
        show me once by name
        @Model.First().Value
    }
    

Отметьте «разделы»

(т.е. отобразить задержанную секцию в родительском представлении)

@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`

CODE

public static class HtmlRenderExtensions {

    /// 
    /// Delegate script/resource/etc injection until the end of the page
    /// @via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ 
    /// 
    private class DelayedInjectionBlock : IDisposable {
        /// 
        /// Unique internal storage key
        /// 
        private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";

        /// 
        /// Internal storage identifier for remembering unique/isOnlyOne items
        /// 
        private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;

        /// 
        /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
        /// 
        private const string EMPTY_IDENTIFIER = "";

        /// 
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// 
        /// the helper from which we use the context
        /// optional unique sub-identifier for a given injection block
        /// list of delayed-execution callbacks to render internal content
        public static Queue GetQueue(HtmlHelper helper, string identifier = null) {
            return _GetOrSet(helper, new Queue(), identifier ?? EMPTY_IDENTIFIER);
        }

        /// 
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// 
        /// the helper from which we use the context
        /// the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value
        /// optional unique sub-identifier for a given injection block
        /// list of delayed-execution callbacks to render internal content
        private static T _GetOrSet(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
            var storage = GetStorage(helper);

            // return the stored item, or set it if it does not exist
            return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
        }

        /// 
        /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
        /// 
        /// 
        /// 
        public static Dictionary GetStorage(HtmlHelper helper) {
            var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary;
            if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary());
            return storage;
        }


        private readonly HtmlHelper helper;
        private readonly string identifier;
        private readonly string isOnlyOne;

        /// 
        /// Create a new using block from the given helper (used for trapping appropriate context)
        /// 
        /// the helper from which we use the context
        /// optional unique identifier to specify one or many injection blocks
        /// extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)
        public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
            this.helper = helper;

            // start a new writing context
            ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());

            this.identifier = identifier ?? EMPTY_IDENTIFIER;
            this.isOnlyOne = isOnlyOne;
        }

        /// 
        /// Append the internal content to the context's cached list of output delegates
        /// 
        public void Dispose() {
            // render the internal content of the injection block helper
            // make sure to pop from the stack rather than just render from the Writer
            // so it will remove it from regular rendering
            var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
            var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
            // if we only want one, remove the existing
            var queue = GetQueue(this.helper, this.identifier);

            // get the index of the existing item from the alternate storage
            var existingIdentifiers = _GetOrSet(this.helper, new Dictionary(), UNIQUE_IDENTIFIER_KEY);

            // only save the result if this isn't meant to be unique, or
            // if it's supposed to be unique and we haven't encountered this identifier before
            if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
                // remove the new writing context we created for this block
                // and save the output to the queue for later
                queue.Enqueue(renderedContent);

                // only remember this if supposed to
                if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
            }
        }
    }


    /// 
    /// Start a delayed-execution block of output -- this will be rendered/printed on the next call to .
    /// 
    /// 
    /// Print once in "default block" (usually rendered at end via @Html.RenderDelayed()).  Code:
    /// 
    /// @using (Html.Delayed()) {
    ///     show at later
    ///     @Model.Name
    ///     etc
    /// }
    /// 
    /// 
    /// 
    /// 
    /// 
    /// Print once (i.e. if within a looped partial), using identified block via @Html.RenderDelayed("one-time").  Code:
    /// 
    /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    ///     show me once
    ///     @Model.First().Value
    /// }
    /// 
    /// 
    /// 
    /// 
    /// the helper from which we use the context
    /// optional unique identifier to specify one or many injection blocks
    /// extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)
    /// using block to wrap delayed output
    public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
        return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
    }

    /// 
    /// Render all queued output blocks injected via .
    /// 
    /// 
    /// Print all delayed blocks using default identifier (i.e. not provided)
    /// 
    /// @using (Html.Delayed()) {
    ///     show me later
    ///     @Model.Name
    ///     etc
    /// }
    /// 
    /// -- then later --
    /// 
    /// @using (Html.Delayed()) {
    ///     more for later
    ///     etc
    /// }
    /// 
    /// -- then later --
    /// 
    /// @Html.RenderDelayed() // will print both delayed blocks
    /// 
    /// 
    /// 
    /// 
    /// 
    /// Allow multiple repetitions of rendered blocks, using same @Html.Delayed()... as before.  Code:
    /// 
    /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
    /// @Html.RenderDelayed() /* will print again because not removed before */
    /// 
    /// 
    /// 

    /// 
    /// the helper from which we use the context
    /// optional unique identifier to specify one or many injection blocks
    /// only render this once
    /// rendered output content
    public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
        var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);

        if( removeAfterRendering ) {
            var sb = new StringBuilder(
#if DEBUG
                string.Format("", injectionBlockId)
#endif
                );
            // .count faster than .any
            while (stack.Count > 0) {
                sb.AppendLine(stack.Dequeue());
            }
            return MvcHtmlString.Create(sb.ToString());
        } 

        return MvcHtmlString.Create(
#if DEBUG
                string.Format("", injectionBlockId) + 
#endif
            string.Join(Environment.NewLine, stack));
    }


}

55
задан Quentin 12 September 2016 в 15:06
поделиться

10 ответов

В качестве рекомендуемой альтернативы document.write вы можете использовать DOM-манипуляцию для прямого запроса и добавления элементов узла в DOM.

11
ответ дан Darin Dimitrov 19 August 2018 в 02:53
поделиться

Вот код, который должен заменить document.write на месте:

document.write=function(s){
    var scripts = document.getElementsByTagName('script');
    var lastScript = scripts[scripts.length-1];
    lastScript.insertAdjacentHTML("beforebegin", s);
}
20
ответ дан Adrian Kalbarczyk 19 August 2018 в 02:53
поделиться
  • 1
    хороший отзыв. Это кроссбраузер? Я тестировал его на Chrome и работал. – Pons 27 May 2014 в 14:31
  • 2
    Разве это не будет работать, если на странице нет скриптов? – Noyo 10 July 2014 в 10:21
  • 3
    Ну, по крайней мере, на странице (где бы вы ни разместили эту функцию), но что, если единственный скрипт находится в & lt; head & gt; & lt; / head & gt ;? Ключевая вещь, которую он упоминает, - это функция insertAdjacentHTML. – Lonnie Best 14 August 2014 в 18:38
  • 4
    Странно, у меня не было писем, которые кто-то комментировал до сегодняшнего дня LOL. @Pons - это должен быть кросс-браузер, но я не знаю об IE. – Adrian Kalbarczyk 16 August 2014 в 10:24
  • 5
    @Noyo - обычно вы используете document.write внутри & lt; script & gt; на странице, как Github GISTs: gist.github.com/adriank/7436248.js – Adrian Kalbarczyk 16 August 2014 в 10:27

Я не вижу проблемы с document.write. Если вы используете его до того, как событие onload срабатывает, как вы предположительно, для создания элементов из структурированных данных, например, это подходящий инструмент для использования. Нет преимущества производительности при использовании insertAdjacentHTML или явного добавления узлов в DOM после его создания. Я просто протестировал его тремя разными способами со старым сценарием, который когда-то использовал для планирования входящих вызовов модема для круглосуточной службы на банке из 4 модемов.

К моменту завершения этого сценария создается более 3000 узлов DOM, в основном, ячеек таблицы. На 7-летнем ПК, работающем под управлением Firefox на Vista, это небольшое упражнение занимает менее 2 секунд, используя document.write из локального исходного файла 12 КБ и три 1 пиксельных GIF, которые повторно используются примерно в 2000 раз.

Использование insertAdjacentHTML не является прямой заменой, поскольку браузер закрывает теги, которые требуется сценарию, остается открытым и занимает в два раза больше , чтобы в конечном итоге создать искаженную страницу. Написание всех фрагментов на строку, а затем передача ее на insertAdjacentHTML занимает еще больше времени, но, по крайней мере, вы получаете страницу как созданную. Другие варианты (например, ручное восстановление DOM по одному узлу за раз) настолько смешны, что я даже не собираюсь туда.

Иногда document.write используется. Тот факт, что он является одним из самых старых методов в JavaScript, не является противным моментом, но точка в его пользу - это высоко оптимизированный код, который делает именно то, что он должен был делать и делал с момента его создания.

Приятно знать, что существуют альтернативные методы пост-нагрузки, но следует понимать, что они предназначены для другой цели целиком; а именно изменение DOM после его создания и выделение памяти. Для использования этих методов требуется более ресурсоемкий метод, если ваш скрипт предназначен для написания HTML-кода, с которого браузер создает DOM в первую очередь.

Просто напишите его и дайте браузеру и интерпретатору выполнить эту работу. Это то, для чего они нужны.

PS: Я только что проверил, используя параметр onload в теге body, и даже в этот момент документ все еще open и document.write() функционирует по назначению , Кроме того, нет заметной разницы в производительности между различными методами в последней версии Firefox. Конечно, в кэше аппаратного / программного обеспечения, вероятно, происходит тонна кэширования, но это действительно так, пусть машина работает. Однако это может повлиять на дешевый смартфон. Ура!

2
ответ дан bebopalooblog 19 August 2018 в 02:53
поделиться

Причина, по которой ваш HTML заменяется, связана с злой функцией JavaScript: document.write() .

Это определенно «плохая форма». Он работает только с веб-страницами, если вы используете его при загрузке страницы; и если вы используете его во время выполнения, он заменит весь документ на вход.


проблема:

document.write записывает в поток документов. Вызов document.write в закрытом (или загруженном) документе автоматически вызывает document.open , который очистит документ.

- цитата из MDN

document.write() имеет двух приспешников, document.open() и document.close() . Когда документ HTML загружается, документ «открыт». Когда документ закончил загрузку, документ «закрыт». Используя document.write() в этот момент, вы удалите весь (закрытый) HTML-документ и замените его новым (открытым) документом. Это означает, что ваша веб-страница стерла себя и начала писать новую страницу - с нуля.

Полагаю, что document.write() также приводит к снижению производительности браузера ( исправьте меня, если я ошибаюсь).


пример:

В этом примере записывается вывод в HTML-документ после загрузки страницы. Смотреть злые силы document.write() очищают весь документ, когда вы нажимаете кнопку «истребить»:

I am an ordinary HTML page.  I am innocent, and purely for informational purposes. Please do not <input type="button" onclick="document.write('This HTML page has been succesfully exterminated.')" value="exterminate"/>
me!

< hr>

альтернативы:

  • .innerHTML Это замечательная альтернатива, но этот атрибут должен быть прикреплен к элементу, в который вы хотите поместить текст.

Пример: document.getElementById('output1').innerHTML = 'Some text!';

  • .createTextNode() является альтернативой, рекомендованной W3C .

Пример: var para = document.createElement('p'); para.appendChild(document.createTextNode('Hello, '));

ПРИМЕЧАНИЕ. Известно, что снижение производительности (медленнее, чем .innerHTML). Вместо этого рекомендую использовать .innerHTML.


пример с альтернативой .innerHTML:

I am an ordinary HTML page. 
I am innocent, and purely for informational purposes. 
Please do not 
<input type="button" onclick="document.getElementById('output1').innerHTML = 'There was an error exterminating this page. Please replace <code>.innerHTML</code> with <code>document.write()</code> to complete extermination.';" value="exterminate"/>
 me!
<p id="output1"></p>

59
ответ дан God is good 19 August 2018 в 02:53
поделиться
  • 1
    Значит, зло начинается только после закрытия документа? Кроме того, что это немодно, есть ли недостатки в использовании document.write до этого? Существует ли какая-либо практическая неопределенность в отношении , когда документ закрыт? – Bob Stein 29 May 2017 в 13:30
  • 2
    @ BobStein-VisiBone, да, document.write() можно вызвать во время загрузки страницы, в то время как браузер анализирует страницу. После того, как страница проанализирована / загружена, вызов этой функции также вызовет document.open(), который вытирает страницу и запускается с нуля. Я не могу думать о какой-либо причине не использовать функцию во время загрузки страницы, за исключением согласованности с тем, как вы добавляете контент на страницу в любой другой момент. И документ закрывается, как только браузер завершит разбор документа. – God is good 30 May 2017 в 14:19
  • 3
    document.write() по-прежнему представляется лучшим решением для cdn local fallback . Я еще не пробовал getElementById / innerHTML, но решение createElement / insertBefore оказывается несинхронным . – Bob Stein 30 May 2017 в 15:51
  • 4
    @Godisgood. Нет никакого способа, с помощью которого document.write имеет проблемы с производительностью, потому что его точно такая же функция используется браузерами при загрузке начального ответа HTTP. – Pacerier 16 October 2017 в 15:02
  • 5
    @Pacerier Да, но я имел в виду, что медленнее использовать document.write() после загрузки страниц, потому что он должен очистить документ, а затем снова открыть его каждый раз, когда вызывается функция. .innerHTML не делает ничего из этого, поэтому я уверен, что прочитал, что в конце концов, это быстрее. Я буду искать источник, чтобы поддержать это. Полагаю, что document.write() работает быстро, если вы используете его, пока страница уже открыта. – God is good 27 November 2017 в 05:07

Просто отбросьте здесь примечание, чтобы сказать, что, хотя использование document.write сильно недооценено из-за проблем производительности (синхронная инъекция и оценка DOM), также нет реальной альтернативы 1: 1, если вы используете document.write для инъекции меток скрипта по запросу.

Существует множество отличных способов избежать этого (например, загрузчики сценариев, такие как RequireJS , которые управляют вашей зависимостью цепи), но они более инвазивны и поэтому наилучшим образом используются на протяжении сайта / приложения.

8
ответ дан James M. Greene 19 August 2018 в 02:53
поделиться

Это, вероятно, самая правильная прямая замена: insertAdjacentHTML .

5
ответ дан Justin Summerlin 19 August 2018 в 02:53
поделиться

Я не уверен, что это будет работать точно, но я думал о

var docwrite = function(doc) {               
    document.write(doc);          
};

. Это решило проблему с сообщениями об ошибках для меня.

-1
ответ дан Rory O'Kane 19 August 2018 в 02:53
поделиться
1
ответ дан Sorin 19 August 2018 в 02:53
поделиться
  • 1
    Хотя это может ответить на вопрос, пожалуйста, предоставьте любое объяснение, которое вы можете сделать, чтобы люди поняли, что здесь происходит. – Randy 12 August 2016 в 09:32

Попробуйте использовать getElementById () или getElementsByName () для доступа к определенному элементу, а затем для использования свойства innerHTML:

<html>
    <body>
        <div id="myDiv1"></div>
        <div id="myDiv2"></div>
    </body>

    <script type="text/javascript">
        var myDiv1 = document.getElementById("myDiv1");
        var myDiv2 = document.getElementById("myDiv2");

        myDiv1.innerHTML = "<b>Content of 1st DIV</b>";
        myDiv2.innerHTML = "<i>Content of second DIV element</i>";
    </script>
</html>
3
ответ дан user 19 August 2018 в 02:53
поделиться
  • 1
    Не совсем получается. Вам не нужно & lt; body & gt; теги и <\/b> в скрипте? – DarkLightA 27 December 2010 в 11:22
  • 2
    thx, DarkLightA! я его исправил – user 27 December 2010 в 11:35
  • 3
    все еще не работает .. jsfiddle.net/8XwRH – DarkLightA 27 December 2010 в 11:36
  • 4
    @DarkLightA: введенный вами источник по умолчанию не совпадает с верхним. и, с другой стороны, он находится в окне javascript! источник имеет оба типа источников, html & amp; JavaScript. создайте html-файл на рабочем столе с этим источником и откройте его. – user 27 December 2010 в 11:40
  • 5
    Но вы не вводите скрипты вне тела или головы ... – DarkLightA 27 December 2010 в 11:41

Вопрос зависит от того, что вы на самом деле пытаетесь сделать.

Обычно вместо document.write вы можете использовать someElement.innerHTML или лучше, document.createElement с someElement.appendChild.

Вы также можете использовать библиотеку jQuery и использовать там функции модификации: http://api.jquery.com/category/manipulation/

7
ответ дан Wolph 19 August 2018 в 02:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: