Warum das <script> -Tag teilen, wenn es mit document.write () geschrieben wird?


268

Warum verwenden einige Websites (oder Werbetreibende, die Kunden Javascript-Code geben) eine Technik zum Aufteilen der <script>und / oder </script>Tags innerhalb von document.write()Anrufen?

Mir ist aufgefallen, dass Amazon dies auch tut, zum Beispiel:

<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>

Antworten:


373

</script>muss aufgelöst werden, da sonst der umschließende <script></script>Block zu früh beendet würde. Eigentlich sollte es zwischen dem <und dem aufgeteilt werden /, da ein Skriptblock</ (laut SGML) durch eine beliebige ETAGO-Sequenz (End-Tag Open) (dh ) beendet werden soll :

Obwohl die STYLE- und SCRIPT-Elemente CDATA für ihr Datenmodell verwenden, muss CDATA für diese Elemente von Benutzeragenten unterschiedlich behandelt werden. Markups und Entitäten müssen als Rohtext behandelt und unverändert an die Anwendung übergeben werden. Das erste Auftreten der Zeichenfolge " </" (End-Tag Open Trennzeichen) wird als Abschluss des Endes des Elementinhalts behandelt. In gültigen Dokumenten wäre dies das End-Tag für das Element.

In der Praxis beenden Browser jedoch nur das Parsen eines CDATA-Skriptblocks auf einem tatsächlichen </script> Close-Tag.

In XHTML gibt es keine solche spezielle Behandlung für Skriptblöcke, daher muss jedes <(oder &) Zeichen in ihnen &escaped;wie in jedem anderen Element sein. Dann werden Browser, die XHTML als HTML der alten Schule analysieren, verwirrt. Es gibt Problemumgehungen für CDATA-Blöcke, aber es ist am einfachsten, die Verwendung dieser Zeichen ohne Flucht zu vermeiden. Eine bessere Möglichkeit, ein Skriptelement aus einem Skript zu schreiben, das für beide Parsertypen geeignet ist, ist:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>

30
\/ist eine gültige Escape-Sequenz für /, warum also nicht einfach diese anstelle dieser String-Literal- Escape- Zeichen verwenden <? ZB document.write('<script src=foo.js><\/script>');. Dies </script>ist auch nicht die einzige Zeichenfolge, die ein <script>Element schließen kann. Einige weitere Informationen hier: mathiasbynens.be/notes/etago
Mathias Bynens

11
@Mathias: <\/script>ist in diesem Fall in Ordnung, würde aber nur in HTML funktionieren; In XHTML ohne zusätzliche CDATA-Abschnittsumhüllung handelt es sich immer noch um einen wohlgeformten Fehler. Sie können auch \x3CInline-Event-Handler-Attribute verwenden, <die sowohl in HTML als auch in XHTML ungültig sind, sodass sie eine breitere Anwendbarkeit haben: Wenn ich eine einfach zu automatisierende Methode gewählt hätte, um vertrauliche Zeichen in JS-Zeichenfolgenliteralen für alle Kontexte zu umgehen, ist dies der Fall die, für die ich mich entscheiden würde.
Bobince

3
<Kann in HTML in Inline-Ereignishandlerattributen verwendet werden. html5.validator.nu/… Und Sie haben Recht mit der XHTML-Kompatibilität von \x3Csich selbst, aber da XHTML document.write(oder innerHTML) sowieso nicht unterstützt, sehe ich nicht, wie relevant das ist.
Mathias Bynens

2
@ MathiasBynens - document.writeist irrelevant, es ist nur das Beispiel. Das OP hätte innerHTML verwenden können. Es geht darum, die </Zeichenfolge vor dem Markup-Parser zu verstecken , wo immer sie auftritt. Es ist nur so, dass die meisten Parser es innerhalb eines Skriptelements tolerieren, wenn sie es strikt nicht sollten (aber HTML-Parser sind sehr tolerant). Sie haben Recht, aber das <\/reicht in allen Fällen für HTML aus.
RobG

3
Ich denke nicht, dass es notwendig ist, der Öffnung zu entkommen. Es document.write('<script src="foo.js">\x3C/script>')scheint in allen Browsern ausreichend zu sein, zurück zum IE6 zu gelangen. (Ich habe das Typattribut weggelassen, weil es in HTML5 nicht erforderlich ist und von keinem Browser wie erforderlich erzwungen wird.)
Matt Browne

34

Hier ist eine weitere Variante, die ich verwendet habe, um ein Skript-Tag inline zu generieren (damit es sofort ausgeführt wird), ohne dass Escapezeichen erforderlich sind:

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

(Hinweis: Im Gegensatz zu den meisten Beispielen im Internet setze ich type="text/javascript"weder das einschließende noch das generierte Tag ein: Es gibt keinen Browser, der dies nicht als Standard hat, und daher ist es redundant, wird aber auch nicht schaden. wenn Sie nicht einverstanden sind).


Guter Punkt bezüglich: Typ. Die Standardeinstellung ist ab HTML5 als "Text / Javascript" definiert, daher ist dies ein nutzloses Attribut. w3.org/html/wg/drafts/html/master/…
Luke

8
Diese ist besser als die akzeptierte Antwort, da diese Variation tatsächlich minimiert werden kann. Dieses 'x3C / script>' wird nach der Minimierung zu '</ script>'.
Zoltan Kochan

20

Ich denke, dies soll verhindern, dass der HTML-Parser des Browsers das <script> und hauptsächlich das </ script> als schließendes Tag des eigentlichen Skripts interpretiert. Ich denke jedoch nicht, dass die Verwendung von document.write eine hervorragende Idee für die Bewertung von Skripten ist Blöcke, warum nicht das DOM verwenden ...

var newScript = document.createElement("script");
...

4
Es ist notwendig zu verhindern, dass der Parser den
Skriptblock

9

Die von Bobince veröffentlichte Lösung funktioniert perfekt für mich. Ich wollte auch zukünftigen Besuchern eine alternative Methode anbieten:

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

In diesem Beispiel habe ich eine bedingte Last für jQuery eingefügt, um den Anwendungsfall zu demonstrieren. Hoffe das ist nützlich für jemanden!


3
Protokoll muss nicht erkannt werden - URI ohne Protokoll funktioniert einwandfrei ('//foo.com/bar.js' usw.)
dmp

3
Sie müssen auch keine asynchrone Einstellung vornehmen. Es ist für alle dynamisch erstellten Skript-Tags aktiviert.
Noishe

Rettete mich! Bei Verwendung von document.write (<script>) wurde auf meiner Website ein leerer Bildschirm angezeigt. Das hat funktioniert.
Tomas Gonzalez

@dmp außer wenn von Dateisystem ausgeführt, wo das Protokoll Datei sein wird: //
mplungjan

9

Das </script>Innere des Javascript-String-Wurfs wird vom HTML-Parser als schließendes Tag interpretiert, was zu unerwartetem Verhalten führt ( siehe Beispiel zu JSFiddle) ).

Um dies zu vermeiden, können Sie Ihr Javascript zwischen Kommentaren platzieren (diese Art der Codierung war gängige Praxis, als Javascript in Browsern nur unzureichend unterstützt wurde). Dies würde funktionieren ( siehe Beispiel in 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>

... aber um ehrlich zu sein, document.writewürde ich die Verwendung nicht als Best Practice betrachten. Warum nicht das DOM direkt manipulieren?

<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
Wenn Sie reines JS verwenden möchten, möchten Sie weiterhin document.write verwenden;)
Nirav Zaveri

Entschuldigung, ich wollte schreiben - dafür ist jQuery Library erforderlich, oder? Bei der Verwendung von document.body.append wurde der Fehler ausgegeben, dass document.body.append keine Funktion ist.
Nirav Zaveri

1
Entschuldigung, mein Fehler: Ich habe appendstatt geschrieben appendChild. Die Antwort wurde korrigiert. Danke, dass du es bemerkst!
Mathieu Rodic

1
Dies sieht so viel allgemeiner aus, als die Zeichenfolge manuell aufzuteilen. Eine Aufteilung ist beispielsweise nicht möglich, wenn die Zeichenfolge von einer Template-Engine eingefügt wird.
w1th0utnam3
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.