Из решений в этой нити я придумал следующее, вероятно, сверхсложное решение, которое позволяет вам отложить рендеринг любых html (скриптов) в блоке использования.
@using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
}
@using (Html.Delayed()) {
show me multiple times, @Model.Whatever
}
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`
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));
}
}
В качестве рекомендуемой альтернативы document.write
вы можете использовать DOM-манипуляцию для прямого запроса и добавления элементов узла в DOM.
Вот код, который должен заменить document.write на месте:
document.write=function(s){
var scripts = document.getElementsByTagName('script');
var lastScript = scripts[scripts.length-1];
lastScript.insertAdjacentHTML("beforebegin", s);
}
Я не вижу проблемы с 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. Конечно, в кэше аппаратного / программного обеспечения, вероятно, происходит тонна кэширования, но это действительно так, пусть машина работает. Однако это может повлиять на дешевый смартфон. Ура!
Причина, по которой ваш HTML заменяется, связана с злой функцией JavaScript: document.write()
.
Это определенно «плохая форма». Он работает только с веб-страницами, если вы используете его при загрузке страницы; и если вы используете его во время выполнения, он заменит весь документ на вход.
blockquote>
document.write
записывает в поток документов. Вызовdocument.write
в закрытом (или загруженном) документе автоматически вызываетdocument.open
, который очистит документ.
document.write()
имеет двух приспешников,document.open()
иdocument.close()
. Когда документ HTML загружается, документ «открыт». Когда документ закончил загрузку, документ «закрыт». Используяdocument.write()
в этот момент, вы удалите весь (закрытый) HTML-документ и замените его новым (открытым) документом. Это означает, что ваша веб-страница стерла себя и начала писать новую страницу - с нуля.Полагаю, что
document.write()
также приводит к снижению производительности браузера ( исправьте меня, если я ошибаюсь).
пример:
В этом примере записывается вывод в HTML-документ после загрузки страницы. Смотреть злые силы
document.write()
очищают весь документ, когда вы нажимаете кнопку «истребить»:
< hr>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!
альтернативы:
.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>
document.write()
можно вызвать во время загрузки страницы, в то время как браузер анализирует страницу. После того, как страница проанализирована / загружена, вызов этой функции также вызовет document.open()
, который вытирает страницу и запускается с нуля. Я не могу думать о какой-либо причине не использовать функцию во время загрузки страницы, за исключением согласованности с тем, как вы добавляете контент на страницу в любой другой момент. И документ закрывается, как только браузер завершит разбор документа.
– God is good
30 May 2017 в 14:19
document.write()
по-прежнему представляется лучшим решением для cdn local fallback . Я еще не пробовал getElementById / innerHTML, но решение createElement / insertBefore оказывается несинхронным .
– Bob Stein
30 May 2017 в 15:51
document.write()
после загрузки страниц, потому что он должен очистить документ, а затем снова открыть его каждый раз, когда вызывается функция. .innerHTML
не делает ничего из этого, поэтому я уверен, что прочитал, что в конце концов, это быстрее. Я буду искать источник, чтобы поддержать это. Полагаю, что document.write()
работает быстро, если вы используете его, пока страница уже открыта.
– God is good
27 November 2017 в 05:07
Просто отбросьте здесь примечание, чтобы сказать, что, хотя использование document.write
сильно недооценено из-за проблем производительности (синхронная инъекция и оценка DOM), также нет реальной альтернативы 1: 1, если вы используете document.write
для инъекции меток скрипта по запросу.
Существует множество отличных способов избежать этого (например, загрузчики сценариев, такие как RequireJS , которые управляют вашей зависимостью цепи), но они более инвазивны и поэтому наилучшим образом используются на протяжении сайта / приложения.
Это, вероятно, самая правильная прямая замена: insertAdjacentHTML .
Я не уверен, что это будет работать точно, но я думал о
var docwrite = function(doc) {
document.write(doc);
};
. Это решило проблему с сообщениями об ошибках для меня.
document.currentScript.insertAdjacentHTML('beforebegin', 'this is the document.write alternative');
https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript https: // developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
Попробуйте использовать 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>
<\/b>
в скрипте?
– DarkLightA
27 December 2010 в 11:22
Вопрос зависит от того, что вы на самом деле пытаетесь сделать.
Обычно вместо document.write
вы можете использовать someElement.innerHTML
или лучше, document.createElement
с someElement.appendChild
.
Вы также можете использовать библиотеку jQuery и использовать там функции модификации: http://api.jquery.com/category/manipulation/