Зачем разделять тег <script> при написании его с помощью document.write ()?

268

Почему некоторые сайты (или рекламодатели, которые предоставляют клиентам код JavaScript) используют технику разделения тегов <script>и / или </script>внутри document.write()вызовов?

Я заметил, что Amazon также делает это, например:

<script type='text/javascript'>
  if (typeof window['jQuery'] == 'undefined') document.write('<scr'+'ipt type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></sc'+'ript>');
</script>
датчанин
источник

Ответы:

373

</script>должен быть разбит, потому что в противном случае это приведет к <script></script>преждевременному завершению блока. На самом деле он должен быть разделен между <и /, потому что предполагается, что блок скрипта (согласно SGML) завершается любой последовательностью открытия конечного тега (ETAGO) (то есть </) :

Хотя элементы STYLE и SCRIPT используют CDATA для своей модели данных, для этих элементов пользовательские агенты должны обрабатывать CDATA по-разному. Разметка и сущности должны обрабатываться как необработанный текст и передаваться приложению как есть. Первое вхождение последовательности символов " </" (открытый разделитель конечных тегов) обрабатывается как завершение конца содержимого элемента. В допустимых документах это будет конечный тег для элемента.

Однако на практике браузеры заканчивают парсинг блока сценариев CDATA только фактическим </script>тегом close.

В XHTML такой специальной обработки для блоков скриптов нет, поэтому любой <(или &) символ внутри них должен быть таким же, &escaped;как и в любом другом элементе. Однако тогда запутанные браузеры, анализирующие XHTML как устаревший HTML. Существуют обходные пути, связанные с блоками CDATA, но проще всего избежать использования этих символов без экранирования. Лучший способ написания элемента script из скрипта, который работает с любым типом парсера:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>
bobince
источник
30
\/является допустимой escape-последовательностью /, так почему бы не использовать ее вместо этих строковых литералов <? Например document.write('<script src=foo.js><\/script>');. Кроме того, </script>это не единственная последовательность символов, которая может закрыть <script>элемент. Немного больше информации здесь: mathiasbynens.be/notes/etago
Матиас
11
@Mathias: <\/script>хорошо в этом случае, но он будет работать только в HTML; в XHTML без дополнительной обертки разделов CDATA это все еще ошибка правильной формы. Кроме того, вы можете использовать \x3Cвстроенные атрибуты обработчика событий, <которые также будут недопустимыми как в HTML, так и в XHTML, поэтому он имеет более широкую применимость: если бы я выбирал один, легко автоматизируемый способ экранирования чувствительных символов в строковых литералах JS для всех контекстов, это тот, за которым я бы пошел.
bobince
3
В HTML <могут использоваться встроенные атрибуты обработчика событий. html5.validator.nu/… И вы правы насчет совместимости XHTML \x3Cдля sich, но поскольку XHTML не поддерживает document.write(или innerHTML) в любом случае, я не понимаю, насколько это актуально.
Матиас Биненс
2
@ MathiasBynens document.write- не имеет значения, это просто пример. ОП мог бы использовать innerHTML, он о том, чтобы скрыть </последовательность символов от анализатора разметки, где бы он ни происходил. Просто большинство синтаксических анализаторов допускают это внутри элемента скрипта, когда строго не должны (но анализаторы HTML очень терпимы). Вы правы, хотя этого <\/достаточно во всех случаях для HTML.
RobG
3
Я не думаю, что экранирование открытия <необходимо .... document.write('<script src="foo.js">\x3C/script>')кажется достаточным во всех браузерах до IE6. (Я пропустил атрибут type, потому что он не требуется в HTML5, и при этом он не применяется в соответствии с требованиями любого браузера.)
Matt Browne
34

Вот еще один вариант, который я использовал, когда хотел сгенерировать встроенный тег script (чтобы он выполнялся немедленно) без необходимости экранирования:

<script>
    var script = document.createElement('script');
    script.src = '/path/to/script.js';
    document.write(script.outerHTML);
</script>

(Примечание: вопреки большинству примеров в сети, я не устанавливаю type="text/javascript"ни теги включения, ни сгенерированный тег: нет браузера, не имеющего этого по умолчанию, и поэтому он является избыточным, но также не повредит, если вы не согласны).

Stoffe
источник
Хорошая точка зрения: тип. По умолчанию определяется как «text / javascript», начиная с HTML5, поэтому это бесполезный атрибут. w3.org/html/wg/drafts/html/master/…
Люк
8
Этот лучше, чем принятый ответ, потому что это изменение может быть фактически уменьшено. Этот 'x3C / script>' станет '</ script>' после минификации.
Золтан Кочан
20

Я думаю, что для предотвращения того, чтобы синтаксический анализатор HTML браузера интерпретировал <script>, и главным образом </ script>, как закрывающий тег реального скрипта, однако я не думаю, что использование document.write является отличной идеей для оценки скрипта блоки, почему бы не использовать DOM ...

var newScript = document.createElement("script");
...
CMS
источник
4
Это необходимо , чтобы предотвратить парсер от закрытия скрипт-блок преждевременно ...
roenving
9

Решение, опубликованное Bobince, отлично подходит для меня. Я хотел предложить альтернативный метод для будущих посетителей:

if (typeof(jQuery) == 'undefined') {
    (function() {
        var sct = document.createElement('script');
        sct.src = ('https:' == document.location.protocol ? 'https' : 'http') +
          '://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js';
        sct.type = 'text/javascript';
        sct.async = 'true';
        var domel = document.getElementsByTagName('script')[0];
        domel.parentNode.insertBefore(sct, domel);
    })();
}

В этом примере я включил условную загрузку для jQuery, чтобы продемонстрировать вариант использования. Надеюсь, это кому-нибудь пригодится!

Jongosi
источник
3
не нужно обнаруживать протокол - URI без протокола работает нормально ('//foo.com/bar.js' и т. д.)
dmp
3
нет необходимости устанавливать асинхронные либо. он включен для всех динамически создаваемых тегов скрипта.
Noishe
Спас меня! При использовании document.write (<script>) мой сайт показывал пустой экран. Это сработало.
Томас Гонсалес
@dmp за исключением случаев запуска из файловой системы, где протокол будет файл: //
mplungjan
9

</script>Внутри строки litteral Javascript интерпретируется HTML парсер в качестве закрывающего тега, вызывая неожиданное поведение ( см пример на JSFiddle ).

Чтобы избежать этого, вы можете размещать свой javascript между комментариями (этот стиль кодирования был обычной практикой, когда Javascript плохо поддерживался браузерами). Это будет работать ( см. Пример в JSFiddle ):

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        document.write('<script type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></script>');
    }
    // -->
</script>

... но, если честно, использование document.write- это не то, что я бы посчитал лучшей практикой. Почему бы не манипулировать DOM напрямую?

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('src', 'http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js');
        document.body.appendChild(script);
    }
    // -->
</script>
Матье Родик
источник
1
Если вы хотите использовать чистый JS, вы все равно захотите использовать document.write;)
Нирав Завери
Извините, я хотел написать - для этого нужна библиотека jQuery, верно? Когда я использовал, document.body.append, он выдавал ошибку, что document.body.append не является функцией.
Нирав Завери
1
Извините, моя ошибка: я написал appendвместо appendChild. Исправил ответ. Спасибо, что заметили!
Матье Родик,
1
Это выглядит намного более общим, чем разделение строки вручную. Например, разделение невозможно, если строка вставлена ​​механизмом шаблонов.
w1th0utnam3