Tại sao phải chia thẻ <script> khi viết nó bằng document.write ()?


268

Tại sao một số trang web (hoặc nhà quảng cáo cung cấp cho khách hàng mã javascript) sử dụng kỹ thuật phân tách <script>và / hoặc </script>gắn thẻ trong document.write()các cuộc gọi?

Tôi nhận thấy rằng Amazon cũng làm điều này, ví dụ:

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

Câu trả lời:


373

</script>phải chia tay vì nếu không nó sẽ kết thúc <script></script>khối bao vây quá sớm. Thực sự nó nên được phân chia giữa </, bởi vì một khối tập lệnh được cho là (theo SGML) bị chấm dứt bởi bất kỳ chuỗi mở thẻ cuối (ETAGO) nào (ví dụ </) :

Mặc dù các phần tử STYLE và SCRIPT sử dụng CDATA cho mô hình dữ liệu của chúng, nhưng đối với các phần tử này, CDATA phải được xử lý khác nhau bởi các tác nhân người dùng. Đánh dấu và các thực thể phải được coi là văn bản thô và được chuyển đến ứng dụng. Sự xuất hiện đầu tiên của chuỗi ký tự " </" (dấu phân cách mở thẻ cuối) được coi là chấm dứt kết thúc nội dung của phần tử. Trong các tài liệu hợp lệ, đây sẽ là thẻ kết thúc cho phần tử.

Tuy nhiên, trong thực tế, các trình duyệt chỉ kết thúc phân tích cú pháp khối CDATA trên </script>thẻ đóng thực tế .

Trong XHTML, không có xử lý đặc biệt nào cho các khối tập lệnh, do đó, bất kỳ ký tự <(hoặc &) nào bên trong chúng phải &escaped;giống như trong bất kỳ phần tử nào khác. Tuy nhiên, sau đó các trình duyệt đang phân tích XHTML dưới dạng HTML trường học cũ sẽ bị nhầm lẫn. Có các cách giải quyết liên quan đến các khối CDATA, nhưng đơn giản nhất là tránh sử dụng các ký tự này không thoát. Cách tốt hơn để viết một phần tử tập lệnh từ tập lệnh hoạt động trên một trong hai loại trình phân tích cú pháp sẽ là:

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

30
\/là một chuỗi thoát hợp lệ cho /, vậy tại sao không sử dụng chuỗi đó thay vì các chuỗi thoát theo nghĩa đen <? Ví dụ document.write('<script src=foo.js><\/script>');. Ngoài ra, </script>không phải là chuỗi ký tự duy nhất có thể đóng một <script>phần tử. Một số thông tin thêm ở đây: mathiasbynens.be/notes/etago
Mathias Bynens

11
@Mathias: <\/script>tốt trong trường hợp này, nhưng nó sẽ chỉ hoạt động trong HTML; trong XHTML mà không có gói phần CDATA bổ sung, đó vẫn là một lỗi định dạng tốt. Ngoài ra, bạn có thể sử dụng \x3Ctrong các thuộc tính của trình xử lý sự kiện nội tuyến, nơi <cũng sẽ không hợp lệ trong cả HTML và XHTML, vì vậy nó có khả năng áp dụng rộng hơn: nếu tôi chọn một cách dễ dàng tự động để thoát các ký tự nhạy cảm trong các chuỗi ký tự JS cho tất cả các ngữ cảnh, đó là người mà tôi đi
bobince

3
Trong HTML, <có thể được sử dụng trong các thuộc tính xử lý sự kiện nội tuyến. html5.validator.nu/ Từ Và bạn nói đúng về khả năng tương thích XHTML của \x3Cmột sich, nhưng vì XHTML không hỗ trợ document.write(hoặc innerHTML) dù sao tôi cũng không thấy điều đó có liên quan như thế nào.
Mathias Bynens

2
@ MathiasBynens Trò chơi điện tử document.writekhông liên quan, nó chỉ là ví dụ. OP có thể đã sử dụng InternalHTML, đó là về việc ẩn ẩn </chuỗi ký tự khỏi trình phân tích cú pháp đánh dấu, bất cứ nơi nào nó xảy ra. Chỉ là hầu hết các trình phân tích cú pháp chấp nhận nó bên trong một phần tử script khi chúng không nên (nhưng các trình phân tích cú pháp HTML rất khoan dung). Bạn đúng mặc dù điều đó <\/đủ cho tất cả các trường hợp cho HTML.
RobG

3
Tôi không nghĩ thoát khỏi việc mở <là cần thiết .... document.write('<script src="foo.js">\x3C/script>')dường như là đủ trong tất cả các trình duyệt trở lại IE6. (Tôi đã bỏ thuộc tính loại vì nó không bắt buộc trong HTML5 và cũng không được thi hành theo yêu cầu của bất kỳ trình duyệt nào.)
Matt Browne

34

Đây là một biến thể khác mà tôi đã sử dụng khi muốn tạo một thẻ script nội tuyến (để nó thực thi ngay lập tức) mà không cần bất kỳ hình thức thoát nào:

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

(Lưu ý: trái với hầu hết các ví dụ trên mạng, tôi không thiết lập type="text/javascript"trên cả thẻ kèm theo, cũng không phải là tạo ra một: không có trình duyệt không có mà như là mặc định, và vì vậy nó là không cần thiết, nhưng sẽ không làm tổn thương một trong hai, nếu bạn không đồng ý).


Điểm tốt lại: loại. Mặc định được định nghĩa là "văn bản / javascript" như HTML5, vì vậy đây là một thuộc tính vô dụng. w3.org/html/wg/dcraft/html/master/ từ
Luke

8
Câu trả lời này tốt hơn câu trả lời được chấp nhận vì biến thể này thực sự có thể được thu nhỏ. 'X3C / script>' này sẽ trở thành '</ script>' sau khi thu nhỏ.
Zoltan Kochan

20

Tôi nghĩ là để ngăn trình phân tích cú pháp HTML của trình duyệt giải thích <script> và chủ yếu là </ script> làm thẻ đóng của tập lệnh thực tế, tuy nhiên tôi không nghĩ rằng sử dụng document.write là một ý tưởng tuyệt vời để đánh giá tập lệnh các khối, tại sao không sử dụng DOM ...

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

4
Nó là cần thiết để ngăn chặn việc phân tích cú pháp từ đóng kịch bản-block sớm ...
roenving

9

Giải pháp Bobince đăng hoạt động hoàn hảo cho tôi. Tôi muốn cung cấp một phương pháp thay thế cho khách truy cập trong tương lai:

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

Trong ví dụ này, tôi đã bao gồm một tải có điều kiện cho jQuery để chứng minh trường hợp sử dụng. Hy vọng điều đó hữu ích cho ai đó!


3
không cần phát hiện giao thức - URI không có giao thức chỉ hoạt động tốt ('//foo.com/bar.js', v.v.)
dmp

3
không cần thiết lập async. nó được đặt cho tất cả các thẻ script được tạo động.
Noishe

Cứu tôi! Khi sử dụng document.write (<script>) trang web của tôi đã hiển thị một màn hình trống. Điều này đã làm việc.
Tomas Gonzalez

@dmp ngoại trừ khi chạy từ hệ thống tệp nơi giao thức sẽ là tệp: //
mplungjan

9

Phần </script>bên trong chuỗi Javascript được trình phân tích cú pháp HTML hiểu là thẻ đóng, gây ra hành vi không mong muốn ( xem ví dụ trên JSFiddle ).

Để tránh điều này, bạn có thể đặt javascript của mình giữa các bình luận (kiểu mã hóa này là thông lệ, trở lại khi Javascript được hỗ trợ kém giữa các trình duyệt). Điều này sẽ hoạt động ( xem ví dụ trong 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>

... nhưng thành thật mà nói, sử dụng document.writekhông phải là điều tôi sẽ xem xét thực hành tốt nhất. Tại sao không thao tác trực tiếp 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
Nếu bạn muốn sử dụng JS thuần túy, bạn sẽ vẫn muốn sử dụng document.write;)
Nirav Zaveri

Xin lỗi, tôi muốn viết - điều này đòi hỏi Thư viện jQuery, phải không? Khi tôi sử dụng, document.body.append, nó đã đưa ra một lỗi rằng document.body.append không phải là một hàm.
Nirav Zaveri

1
Xin lỗi, lỗi của tôi: Tôi đã viết appendthay vì appendChild. Sửa câu trả lời. Cảm ơn đã chú ý!
Mathieu Rodic

1
Điều này có vẻ tổng quát hơn nhiều so với việc tách chuỗi bằng tay. Ví dụ, việc chia tách là không khả thi nếu chuỗi được chèn bởi công cụ mẫu.
w1th0utnam3
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.