Dlaczego warto podzielić tag <script> podczas pisania go za pomocą document.write ()?

268

Dlaczego niektóre witryny (lub reklamodawcy, którzy podają klientom kod javascript) stosują technikę dzielenia <script>i / lub </script>tagów w ramach document.write()połączeń?

Zauważyłem, że Amazon też to robi, na przykład:

<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>
Duńczyk
źródło

Odpowiedzi:

373

</script>musi zostać rozbity, ponieważ w przeciwnym razie <script></script>zbyt wcześnie zakończyłby blok otaczający . Naprawdę powinien być podzielony na <i /, ponieważ blok skryptu powinien (zgodnie z SGML) być zakończony dowolną sekwencją otwartego znacznika końcowego (ETAGO) (tj. </) :

Chociaż elementy STYLE i SCRIPT używają CDATA w swoim modelu danych, w przypadku tych elementów CDATA musi być obsługiwany w inny sposób przez aplikacje klienckie. Znaczniki i jednostki muszą być traktowane jako nieprzetworzony tekst i przekazywane do aplikacji w niezmienionej postaci. Pierwsze wystąpienie sekwencji znaków „ </” (otwarty separator znacznika końcowego) jest traktowane jako zakończenie końca zawartości elementu. W prawidłowych dokumentach byłby to znacznik końcowy elementu.

Jednak w praktyce przeglądarki kończą tylko parsowanie bloku skryptu CDATA na rzeczywistym </script>tagu close.

W XHTML nie ma takiej specjalnej obsługi bloków skryptów, więc każdy <(lub &) znak w nich musi być &escaped;taki sam jak w każdym innym elemencie. Jednak przeglądarki, które analizują XHTML jako oldschoolowy HTML, będą się mylić. Istnieją obejścia dotyczące bloków CDATA, ale najłatwiej jest po prostu uniknąć używania tych znaków bez zmian. Lepszym sposobem napisania elementu skryptu ze skryptu, który działa na dowolnym parserze, jest:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>
Bobin
źródło
30
\/jest prawidłową sekwencją zmiany znaczenia /, więc dlaczego po prostu jej nie użyć zamiast tych literowych znaków ucieczki <? Np document.write('<script src=foo.js><\/script>');. Ponadto </script>nie jest to jedyna sekwencja znaków, która może zamknąć <script>element. Więcej informacji tutaj: mathiasbynens.be/notes/etago
Mathias Bynens
11
@Mathias: <\/script>w tym przypadku jest w porządku, ale działałoby tylko w HTML; w XHTML bez dodatkowego owijania sekcji CDATA jest to nadal dobrze sformułowany błąd. Możesz także użyć \x3Cwbudowanych atrybutów obsługi zdarzeń gdzie< byłyby niepoprawne zarówno w HTML, jak i XHTML, więc ma szersze zastosowanie: gdybym wybrał jeden, łatwo zautomatyzowany sposób na ucieczkę wrażliwych znaków w literałach ciągów JS dla wszystkich kontekstów, to jest to ten, na który bym poszedł.
bobince
3
W HTML <można go używać w atrybutach wbudowanej procedury obsługi zdarzeń. html5.validator.nu/… I masz rację co do kompatybilności \x3Csich z XHTML , ale ponieważ XHTML nie obsługuje document.write(lub innerHTML) i tak nie wiem, jak to jest istotne.
Mathias Bynens,
2
@ MathiasBynens— nie document.writema znaczenia, po prostu jest przykładem. OP mógł użyć innerHTML, chodzi o ukrywanie ukrywania </sekwencji znaków przed parserem znaczników, gdziekolwiek się pojawi. Po prostu większość parserów toleruje to w elemencie skryptu, gdy ściśle tego nie powinno (ale parsery HTML są bardzo tolerancyjne). Masz rację, ale <\/we wszystkich przypadkach wystarczy HTML.
RobG
3
Nie sądzę, aby ucieczka przed otwarciem była konieczna .... document.write('<script src="foo.js">\x3C/script>')wydaje się wystarczająca we wszystkich przeglądarkach z powrotem do IE6. (Pominąłem atrybut type, ponieważ nie jest wymagany w HTML5, ani nie jest egzekwowany zgodnie z wymaganiami jakiejkolwiek przeglądarki).
Matt Browne
34

Oto kolejna odmiana, której użyłem, gdy chcę wygenerować znacznik skryptu w wierszu (więc wykonuje się on natychmiast) bez potrzeby stosowania jakichkolwiek znaków zmiany znaczenia:

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

(Uwaga: w przeciwieństwie do większości przykładów w sieci, nie ustawiam type="text/javascript" ani tagu zamykającego, ani generowanego: nie ma przeglądarki, która nie ma tej wartości domyślnej, więc jest redundantna, ale też nie zaszkodzi, jeśli się nie zgadzasz).

Stoffe
źródło
Dobra uwaga: typ. Wartość domyślna jest zdefiniowana jako „text / javascript” od HTML5, więc jest to bezużyteczny atrybut. w3.org/html/wg/drafts/html/master/…
Łukasz
8
Ta jest lepsza niż zaakceptowana odpowiedź, ponieważ tę odmianę można faktycznie zminimalizować. Ten „x3C / script>” zmieni się w „</script>” po zmniejszeniu.
Zoltan Kochan,
20

Myślę, że ma to na celu zapobieganie interpretacji przez parser HTML HTML <script>, a przede wszystkim </script> jako znacznika zamykającego rzeczywistego skryptu, jednak nie sądzę, że użycie document.write to doskonały pomysł na ocenę skryptu bloki, dlaczego nie użyć DOM ...

var newScript = document.createElement("script");
...
CMS
źródło
4
Konieczne jest, aby parser nie zamknął przedwcześnie bloku skryptu ...
roenving 25.10.08
9

Rozwiązanie, które opublikował Bobince, działa dla mnie idealnie. Chciałem zaoferować alternatywną metodę również przyszłym użytkownikom:

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);
    })();
}

W tym przykładzie uwzględniłem ładowanie warunkowe dla jQuery, aby zademonstrować przypadek użycia. Mam nadzieję, że jest to przydatne dla kogoś!

Jongosi
źródło
3
nie ma potrzeby wykrywania protokołu - URI bez protokołu działa dobrze ('//foo.com/bar.js' itp.)
dmp
3
nie trzeba też ustawiać asynchronizacji. jest włączony dla wszystkich dynamicznie tworzonych tagów skryptowych.
Noishe,
Uratował mnie! Podczas korzystania z document.write (<script>) moja witryna wyświetlała pusty ekran. To zadziałało.
Tomas Gonzalez
@dmp z wyjątkiem uruchamiania z systemu plików, w którym protokołem będzie plik: //
mplungjan
9

</script>Wewnątrz łańcucha litteral JavaScript jest interpretowany przez parser HTML jako znacznika zamykającego, powodując nieoczekiwane zachowanie ( patrz przykład na JSFiddle ).

Aby tego uniknąć, możesz umieścić javascript między komentarzami (ten styl kodowania był powszechną praktyką, kiedy JavaScript był słabo obsługiwany przez przeglądarki). To działałoby ( patrz przykład w 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>

... ale szczerze mówiąc, używanie document.writenie jest czymś, co uważam za najlepszą praktykę. Dlaczego nie manipulować bezpośrednio 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>
Mathieu Rodic
źródło
1
Jeśli chcesz używać czystego JS, nadal będziesz chciał używać document.write;)
Nirav Zaveri
Przepraszam, chciałem napisać - to wymaga biblioteki jQuery, prawda? Kiedy użyłem document.body.append, zgłosił błąd, że document.body.append nie jest funkcją.
Nirav Zaveri
1
Przepraszam, mój błąd: napisałem appendzamiast appendChild. Poprawiłem odpowiedź. Dziękujemy za zauważenie!
Mathieu Rodic
1
To wygląda o wiele bardziej ogólnie niż ręczne dzielenie łańcucha. Na przykład dzielenie nie jest możliwe, jeśli ciąg jest wstawiany przez silnik szablonu.
w1th0utnam3