Por que dividir a tag <script> ao escrevê-la com document.write ()?

268

Por que alguns sites (ou anunciantes que fornecem código javascript aos clientes) empregam uma técnica de dividir as tags <script>e / ou </script>tags nas document.write()chamadas?

Notei que a Amazon também faz isso, por exemplo:

<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>
dinamarquês
fonte

Respostas:

373

</script>precisa ser interrompido porque, caso contrário, encerraria o <script></script>bloco anexo muito cedo. Realmente, ele deve ser dividido entre the <e the /, porque um bloco de script deve (de acordo com SGML) terminar com qualquer sequência de fim de tag aberta (ETAGO) (ou seja </) :

Embora os elementos STYLE e SCRIPT usem CDATA para seu modelo de dados, para esses elementos, o CDATA deve ser tratado de maneira diferente pelos agentes do usuário. A marcação e as entidades devem ser tratadas como texto bruto e passadas para o aplicativo como estão. A primeira ocorrência da sequência de caracteres " </" (delimitador aberto da tag final) é tratada como finalização do final do conteúdo do elemento. Em documentos válidos, essa seria a tag final do elemento.

No entanto, na prática, os navegadores apenas finalizam a análise de um bloco de script CDATA em uma </script>marca de fechamento real .

No XHTML não existe um tratamento especial para blocos de script, portanto, qualquer <(ou &) caractere dentro deles deve ser &escaped;semelhante a qualquer outro elemento. No entanto, os navegadores que estão analisando XHTML como HTML da velha escola ficarão confusos. Existem soluções alternativas que envolvem blocos CDATA, mas é mais fácil simplesmente evitar o uso desses caracteres sem escape. Uma maneira melhor de escrever um elemento de script a partir de um script que funcione em qualquer tipo de analisador seria:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>
bobince
fonte
30
\/é uma sequência de escape válida para /, então, por que não apenas usá-la em vez dessas sequências literais de escape <? Por exemplo document.write('<script src=foo.js><\/script>');. Além disso, </script>não é a única sequência de caracteres que pode fechar um <script>elemento. Mais informações aqui: mathiasbynens.be/notes/etago
Mathias Bynens
11
@ Matias: <\/script>está bem neste caso, mas só funcionaria em HTML; no XHTML sem quebra de seção CDATA extra, ainda é um erro de formação. Além disso, você pode usar \x3Catributos de manipulador de eventos em linha, onde <também seria inválido tanto em HTML quanto em XHTML, para ter uma aplicabilidade mais ampla: se eu estivesse escolhendo uma maneira fácil e automatizada de escapar de caracteres sensíveis em literais de string JS para todos os contextos, isso é o que eu escolheria.
bobince
3
Em HTML, <pode ser usado em atributos de manipulador de eventos embutidos. html5.validator.nu/… E você está certo sobre a compatibilidade XHTML de \x3Cum sich, mas como o XHTML não suporta document.write(ou innerHTML) de qualquer maneira, não vejo como isso é relevante.
Mathias Bynens
2
@ MathiasBynens - document.writeé irrelevante, apenas é o exemplo. O OP poderia ter usado innerHTML, é sobre como ocultar a </sequência de caracteres do analisador de marcação, onde quer que ocorra. É que a maioria dos analisadores o tolera dentro de um elemento de script quando estritamente não deveriam (mas os analisadores de HTML são muito tolerantes). Você está correto, porém, <\/em todos os casos, basta para HTML.
RobG
3
Eu não acho que escapar da abertura <é necessário .... document.write('<script src="foo.js">\x3C/script>')parece ser suficiente em todos os navegadores do IE6. (Eu deixei de fora o atributo tipo porque não é exigido em HTML5, nem é imposta como exigido por qualquer browser.)
Matt Browne
34

Aqui está outra variação que eu usei ao querer gerar uma tag de script embutida (para que seja executada imediatamente) sem precisar de nenhuma forma de escape:

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

(Observação: ao contrário da maioria dos exemplos na rede, não estou definindo type="text/javascript"nem a tag anexa nem a gerada: não há navegador que não a tenha como padrão e, portanto, é redundante, mas também não prejudicará, se você discordar).

Stoffe
fonte
Bom ponto re: type. O padrão é definido como "text / javascript" a partir do HTML5, portanto, é um atributo inútil. w3.org/html/wg/drafts/html/master/…
Lucas
8
Essa é melhor que a resposta aceita, porque essa variação pode ser realmente minificada. Este 'x3C / script>' se tornará '</script>' após a minificação.
Zoltan Kochan
20

Eu acho que é para impedir que o analisador HTML do navegador interprete o <script> e, principalmente, o </script> como a tag de fechamento do script real, no entanto, não acho que usar document.write seja uma excelente idéia para avaliar o script blocos, por que não usar o DOM ...

var newScript = document.createElement("script");
...
CMS
fonte
4
É necessário evitar que o analisador de fechar o script do bloco prematuramente ...
roenving
9

A solução que Bobince postou funciona perfeitamente para mim. Eu também queria oferecer um 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);
    })();
}

Neste exemplo, incluí uma carga condicional para o jQuery demonstrar caso de uso. Espero que seja útil para alguém!

Jongosi
fonte
3
não há necessidade de detectar protocolo - o URI sem protocolo funciona muito bem ('//foo.com/bar.js' etc)
dmp
3
não há necessidade de definir assíncrono também. está ativado para todas as tags de script criadas dinamicamente.
Noishe
Me salvou! Ao usar document.write (<script>), meu site estava mostrando uma tela em branco. Isso funcionou.
Tomas Gonzalez
@dmp exceto quando correndo de sistema de arquivos em que o protocolo será file: //
mplungjan
9

A parte </script>interna da string Javascript é interpretada pelo analisador HTML como uma tag de fechamento, causando comportamento inesperado ( veja o exemplo no JSFiddle ).

Para evitar isso, você pode colocar seu javascript entre os comentários (esse estilo de codificação era uma prática comum, quando o Javascript era pouco suportado pelos navegadores). Isso funcionaria ( veja o exemplo no 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>

... mas, para ser sincero, usar document.writenão é algo que eu consideraria uma prática recomendada. Por que não manipular o DOM diretamente?

<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
fonte
1
Se você quiser usar JS puro, você vai querer ainda usam document.write;)
Nirav Zaveri
Desculpe, eu pretendia escrever - isso requer a biblioteca jQuery, certo? Quando usei, document.body.append, gerou um erro que document.body.append não é uma função.
Nirav Zaveri
1
Desculpe, meu erro: escrevi em appendvez de appendChild. Corrigida a resposta. Obrigado por perceber!
Mathieu Rodic
1
Isso parece muito mais geral do que dividir a string manualmente. Por exemplo, a divisão não é possível se a string for inserida por um mecanismo de modelo.
precisa saber é o seguinte