¿Por qué dividir la etiqueta <script> al escribirla con document.write ()?

268

¿Por qué algunos sitios (o anunciantes que le dan a los clientes código javascript) emplean una técnica para dividir <script>y / o </script>etiquetar dentro de las document.write()llamadas?

Noté que Amazon también hace esto, por ejemplo:

<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>
danés
fuente

Respuestas:

373

</script>tiene que separarse porque, de lo contrario, terminaría el <script></script>bloque de cierre demasiado pronto. Realmente debería dividirse entre el <y el /, porque se supone que un bloque de script (de acuerdo con SGML) debe ser terminado por cualquier secuencia de etiqueta final abierta (ETAGO) (es decir </) :

Aunque los elementos STYLE y SCRIPT usan CDATA para su modelo de datos, para estos elementos, los agentes de usuario deben manejar CDATA de manera diferente. El marcado y las entidades deben tratarse como texto sin formato y pasarse a la aplicación tal cual. La primera aparición de la secuencia de caracteres " </" (delimitador abierto de etiqueta final) se trata como terminando el final del contenido del elemento. En documentos válidos, esta sería la etiqueta final para el elemento.

Sin embargo, en la práctica, los navegadores solo terminan de analizar un bloque de script CDATA en una </script>etiqueta de cierre real .

En XHTML no existe un manejo especial para los bloques de script, por lo que cualquier <(o &) carácter dentro de ellos debe ser &escaped;como en cualquier otro elemento. Sin embargo, los navegadores que analizan XHTML como HTML de la vieja escuela se confundirán. Existen soluciones alternativas que involucran bloques CDATA, pero es más fácil simplemente evitar el uso de estos caracteres sin escape. Una mejor manera de escribir un elemento de script desde un script que funcione en cualquier tipo de analizador sería:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>
bobince
fuente
30
\/es una secuencia de escape válida para /, entonces, ¿por qué no usar eso en lugar de esos escapes literales de cadena para <? Por ej document.write('<script src=foo.js><\/script>');. Además, </script>no es la única secuencia de caracteres que puede cerrar un <script>elemento. Más información aquí: mathiasbynens.be/notes/etago
Mathias Bynens
11
@Mathias: <\/script>está bien en este caso, pero solo funcionaría en HTML; en XHTML sin envoltura adicional de la sección CDATA, sigue siendo un error de buena formación. También puede usar los \x3Catributos del controlador de eventos en línea donde <también serían inválidos en HTML y XHTML, por lo que tiene una aplicabilidad más amplia: si estuviera eligiendo una forma fácil y automática de escapar de caracteres sensibles en literales de cadena JS para todos los contextos, eso es por el que yo iría.
bobince
3
En HTML, <se puede utilizar en los atributos del controlador de eventos en línea. html5.validator.nu/… Y tienes razón sobre la compatibilidad XHTML de \x3Cun sich, pero dado que XHTML no es compatible document.write(o innerHTML) de todos modos, no veo cómo eso es relevante.
Mathias Bynens
2
@ MathiasBynens: document.writees irrelevante, simplemente es el ejemplo. El OP podría haber usado innerHTML, se trata de ocultar ocultar la </secuencia de caracteres del analizador de marcado, donde sea que ocurra. Es solo que la mayoría de los analizadores lo toleran dentro de un elemento de script cuando estrictamente no deberían (pero los analizadores HTML son muy tolerantes). Sin embargo, tienes razón, eso es <\/suficiente en todos los casos para HTML.
RobG
3
No creo que sea necesario escapar de la apertura <... document.write('<script src="foo.js">\x3C/script>')parece ser suficiente en todos los navegadores de vuelta a IE6. (Omití el atributo type porque no es obligatorio en HTML5, ni se aplica como lo requiere ningún navegador).
Matt Browne
34

Aquí hay otra variación que he usado cuando quiero generar una etiqueta de script en línea (para que se ejecute inmediatamente) sin necesidad de ninguna forma de escape:

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

(Nota: al contrario de la mayoría de los ejemplos en la red, no estoy configurando type="text/javascript"ni la etiqueta adjunta ni la generada: no hay ningún navegador que no tenga eso como predeterminado, por lo que es redundante, pero tampoco dañará, si no estás de acuerdo)

Stoffe
fuente
Buen punto re: tipo. El valor predeterminado se define como "texto / javascript" a partir de HTML5, por lo que es un atributo inútil. w3.org/html/wg/drafts/html/master/…
Lucas
8
Esta es mejor que la respuesta aceptada porque esta variación puede ser realmente minimizada. Este 'x3C / script>' se convertirá en '</script>' después de la minificación.
Zoltan Kochan
20

Creo que es para evitar que el analizador HTML del navegador interprete el <script>, y principalmente el </script> como la etiqueta de cierre del script real, sin embargo, no creo que usar document.write sea una excelente idea para evaluar el script bloques, por qué no usar el DOM ...

var newScript = document.createElement("script");
...
CMS
fuente
44
Es necesario evitar que el analizador cierre prematuramente el bloque de script ...
rovenving
9

La solución que Bobince publicó funciona perfectamente para mí. También quería ofrecer un método alternativo para futuros visitantes:

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

En este ejemplo, he incluido una carga condicional para jQuery para demostrar el caso de uso. Espero que sea útil para alguien!

Jongosi
fuente
3
no es necesario detectar el protocolo: el URI sin protocolo funciona bien ('//foo.com/bar.js', etc.)
dmp
3
tampoco es necesario configurar asíncrono. está configurado para todas las etiquetas de script creadas dinámicamente.
Noishe
¡Me salvó! Al usar document.write (<script>) mi sitio mostraba una pantalla en blanco. Esto funcionó.
Tomás González
@dmp excepto cuando se ejecuta desde un sistema de archivos donde el protocolo será file: //
mplungjan
9

El </script>analizador HTML interpreta el interior de la cadena Javascript literalmente como una etiqueta de cierre, causando un comportamiento inesperado ( ver ejemplo en JSFiddle ).

Para evitar esto, puede colocar su javascript entre comentarios (este estilo de codificación era una práctica común, cuando Javascript no era compatible con los navegadores). Esto funcionaría ( ver ejemplo en 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>

... pero para ser honesto, usar document.writeno es algo que consideraría una mejor práctica. ¿Por qué no manipular el DOM directamente?

<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
fuente
1
Si desea usar JS puro, aún querrá usar document.write;)
Nirav Zaveri
Lo siento, tenía la intención de escribir, esto requiere jQuery Library, ¿verdad? Cuando usé, document.body.append, arrojó un error que document.body.append no es una función.
Nirav Zaveri
1
Lo siento, mi error: escribí en appendlugar de appendChild. Corrigió la respuesta. ¡Gracias por notarlo!
Mathieu Rodic
1
Esto parece mucho más general que dividir la cadena manualmente. Por ejemplo, la división no es factible si la cadena es insertada por un motor de plantillas.
w1th0utnam3