เหตุใดจึงต้องแยกแท็ก <script> เมื่อเขียนด้วย document.write ()


268

เหตุใดบางเว็บไซต์ (หรือผู้โฆษณาที่ให้รหัสลูกค้า JavaScript) ใช้เทคนิคการแยก<script>และ / หรือ</script>แท็กขึ้นภายในการdocument.write()โทร

ฉันสังเกตเห็นว่า Amazon ทำเช่นนี้เช่น:

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

คำตอบ:


373

</script>จะต้องถูกทำลายเนื่องจากไม่เช่นนั้นจะทำให้<script></script>บล็อกที่ล้อมรอบเร็วเกินไป จริง ๆ แล้วมันควรจะแยกระหว่าง<และกับ/เพราะบล็อกสคริปต์ควร (ตาม SGML) ที่จะถูกยกเลิกโดยลำดับ end-tag open (ETAGO) ใด ๆ (เช่น</) :

แม้ว่าองค์ประกอบสไตล์และสคริปต์ใช้ CDATA สำหรับตัวแบบข้อมูล แต่สำหรับองค์ประกอบเหล่านี้ CDATA จะต้องได้รับการจัดการแตกต่างกันโดยตัวแทนผู้ใช้ มาร์กอัปและเอนทิตีต้องได้รับการปฏิบัติเสมือนเป็นข้อความดิบและส่งผ่านไปยังแอปพลิเคชันตามที่เป็นอยู่ การเกิดขึ้นครั้งแรกของลำดับอักขระ " </" (ตัวคั่นการเปิดแท็กสิ้นสุด) จะถือว่าเป็นการสิ้นสุดจุดสิ้นสุดของเนื้อหาขององค์ประกอบ ในเอกสารที่ถูกต้องนี่จะเป็นแท็กปิดท้ายสำหรับองค์ประกอบ

อย่างไรก็ตามในทางปฏิบัติเบราว์เซอร์จะจบการแยกวิเคราะห์บล็อกสคริปต์ CDATA บน</script>แท็กปิดจริงเท่านั้น

ใน XHTML ไม่มีการจัดการพิเศษสำหรับบล็อกสคริปต์ดังนั้นอักขระใด ๆ<(หรือ&) ภายในนั้นจะต้อง&escaped;เหมือนในองค์ประกอบอื่น ๆ อย่างไรก็ตามเบราว์เซอร์ที่แยกวิเคราะห์ XHTML เนื่องจาก HTML ของโรงเรียนเก่าจะสับสน มีวิธีแก้ไขเฉพาะหน้าที่เกี่ยวข้องกับบล็อก CDATA แต่วิธีที่ง่ายที่สุดก็คือหลีกเลี่ยงการใช้อักขระที่ไม่ใช้ Escape วิธีที่ดีกว่าในการเขียนองค์ประกอบสคริปต์จากสคริปต์ที่ทำงานกับ parser ทั้งสองประเภทคือ:

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

30
\/เป็นลำดับ escape ที่ถูกต้องสำหรับ/ดังนั้นทำไมไม่ใช้เพียงแค่นั้นแทนที่จะใช้สตริงตามตัวอักษรเหล่านั้นเพื่อ<? document.write('<script src=foo.js><\/script>');เช่น นอกจากนี้ยัง</script>ไม่ได้เรียงลำดับตัวละครเท่านั้นที่สามารถปิด<script>องค์ประกอบ ข้อมูลบางอย่างเพิ่มเติมได้ที่นี่: mathiasbynens.be/notes/etago
Mathias Bynens

11
@Mathias: ใช้ได้<\/script>ในกรณีนี้ แต่ใช้ได้กับ HTML เท่านั้น ใน XHTML ที่ไม่มีการตัดส่วน CDATA เพิ่มเติมมันยังคงเป็นข้อผิดพลาดที่เกิดขึ้นได้ดี นอกจากนี้คุณสามารถใช้\x3Cในแอตทริบิวต์ตัวจัดการเหตุการณ์แบบอินไลน์ซึ่ง<จะไม่ถูกต้องทั้งใน HTML และ XHTML ดังนั้นจึงมีการบังคับใช้ที่กว้างขึ้น: ถ้าฉันเลือกวิธีที่ง่ายและหลีกเลี่ยงอักขระที่มีความละเอียดอ่อนในสตริงสตริง JS สำหรับบริบททั้งหมดนั่นคือ สิ่งที่ฉันต้องการ
bobince

3
ใน HTML <สามารถใช้ในแอตทริบิวต์ตัวจัดการเหตุการณ์แบบอินไลน์ html5.validator.nu/และคุณพูดถูกเกี่ยวกับความเข้ากันได้ของ XHTML ของ\x3Csich แต่เนื่องจาก XHTML ไม่รองรับdocument.write(หรือinnerHTML) อย่างไรก็ตามฉันไม่เห็นว่ามันเกี่ยวข้องกันอย่างไร
Mathias Bynens

2
@ MathiasBynens— document.writeไม่เกี่ยวข้องมันเป็นเพียงตัวอย่างเท่านั้น OP อาจใช้ innerHTML มันเป็นเรื่องเกี่ยวกับการซ่อน</ลำดับอักขระจากตัวแยกวิเคราะห์มาร์กอัปทุกที่ที่มันเกิดขึ้น เป็นเพียงที่ parsers ส่วนใหญ่ทนอยู่ในองค์ประกอบสคริปต์เมื่อพวกเขาไม่ควร (แต่ parsers HTML มีความอดทนมาก) คุณถูกต้องแม้ว่าจะ<\/เพียงพอสำหรับ HTML ทุกกรณี
RobG

3
ฉันไม่คิดว่าการหลีกเลี่ยงการเปิด <จำเป็น .... document.write('<script src="foo.js">\x3C/script>')ดูเหมือนว่าจะเพียงพอในทุกเบราว์เซอร์กลับไปที่ IE6 (ฉันไม่ได้ระบุแอตทริบิวต์เนื่องจากไม่จำเป็นใน HTML5 และไม่บังคับใช้ตามที่เบราว์เซอร์ใด ๆ ต้องการ)
Matt Browne

34

นี่เป็นอีกรูปแบบหนึ่งที่ฉันใช้เมื่อต้องการสร้างแท็กสคริปต์แบบอินไลน์ (ดังนั้นจึงเรียกใช้งานได้ทันที) โดยไม่จำเป็นต้องมีรูปแบบของการหลบหนีใด ๆ :

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

(หมายเหตุ: ตรงกันข้ามกับตัวอย่างส่วนใหญ่ในเน็ตฉันไม่ได้ตั้งค่าtype="text/javascript"ทั้งแท็กที่แนบมาหรือแท็กที่สร้างขึ้น: ไม่มีเบราว์เซอร์ที่ไม่มีค่าเริ่มต้นดังนั้นจึงซ้ำซ้อน แต่จะไม่ทำอันตรายใด ๆ ถ้าคุณไม่เห็นด้วย)


จุดดีอีกครั้ง: ประเภท ค่าเริ่มต้นถูกกำหนดเป็น "text / javascript" ในรูปของ HTML5 ดังนั้นจึงเป็นคุณสมบัติที่ไร้ประโยชน์ w3.org/html/wg/drafts/html/master/…
ลุค

8
อันนี้ดีกว่าคำตอบที่ยอมรับเพราะรูปแบบนี้สามารถลดขนาดได้จริง 'x3C / script>' นี้จะกลายเป็น '</script>' หลังจากการย่อ
Zoltan Kochan

20

ฉันคิดว่าเพื่อป้องกันตัวแยกวิเคราะห์ HTML ของเบราว์เซอร์จากการตีความ <script> และส่วนใหญ่ </script> เป็นแท็กปิดของสคริปต์จริง แต่ฉันไม่คิดว่าการใช้ document.write เป็นแนวคิดที่ยอดเยี่ยมสำหรับการประเมินสคริปต์ บล็อกทำไมไม่ใช้ DOM ...

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

4
มีความจำเป็นต้องป้องกันตัวแยกวิเคราะห์จากการปิดการบล็อกสคริปต์ก่อนกำหนด ...
# # # roenving roenving

9

Bobince โพสต์วิธีแก้ปัญหาทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ฉันต้องการเสนอวิธีการอื่นสำหรับผู้เข้าชมในอนาคต:

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

ในตัวอย่างนี้ฉันได้รวมการโหลดแบบมีเงื่อนไขสำหรับ jQuery เพื่อแสดงกรณีการใช้งาน หวังว่ามีประโยชน์สำหรับใครบางคน!


3
ไม่จำเป็นต้องตรวจสอบโปรโตคอล - URI ที่ใช้โปรโตคอลไม่ได้ทำงานได้ดี ('//foo.com/bar.js' ฯลฯ )
dmp

3
ไม่จำเป็นต้องตั้งค่า async เหมือนกัน มันตั้งไว้สำหรับแท็กสคริปต์ที่สร้างขึ้นแบบไดนามิกทั้งหมด
Noishe

ช่วยฉันด้วย! เมื่อใช้ document.write (<script>) เว็บไซต์ของฉันแสดงหน้าจอว่างเปล่า สิ่งนี้ใช้ได้ผล
Tomas Gonzalez

@dmp ยกเว้นเมื่อเรียกใช้จากระบบไฟล์ที่โปรโตคอลจะเป็นไฟล์: //
mplungjan

9

</script>ภายในสตริง litteral จาวาสคริปต์ที่ตีความโดย parser HTML เป็นแท็กปิดที่ก่อให้เกิดพฤติกรรมที่ไม่คาดคิด ( ดูตัวอย่างใน JSFiddle )

เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถวางจาวาสคริปต์ของคุณระหว่างความคิดเห็น (การเข้ารหัสรูปแบบนี้เป็นวิธีปฏิบัติทั่วไปกลับมาเมื่อ Javascript ได้รับการสนับสนุนอย่างไม่ดีในเบราว์เซอร์) สิ่งนี้จะใช้ได้ ( ดูตัวอย่างใน 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>

... แต่ความซื่อสัตย์การใช้document.writeไม่ใช่สิ่งที่ฉันจะพิจารณาวิธีปฏิบัติที่ดีที่สุด ทำไมไม่จัดการ 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
หากคุณต้องการใช้ JS บริสุทธิ์คุณจะยังคงใช้ document.write;)
Nirav Zaveri

ขออภัยฉันต้องการเขียน - ต้องใช้ jQuery Library ใช่ไหม เมื่อฉันใช้ document.body.append มันเกิดข้อผิดพลาดว่า document.body.append ไม่ใช่ฟังก์ชัน
Nirav Zaveri

1
ขออภัยผิดพลาดของฉัน: ผมเขียนแทนappend appendChildแก้ไขคำตอบ ขอบคุณสำหรับการสังเกต!
Mathieu Rodic

1
ลักษณะนี้ดูทั่วไปมากกว่าการแยกสตริงด้วยตนเอง เช่นการแยกเป็นไปไม่ได้หากสตริงถูกแทรกโดยเครื่องมือแม่แบบ
w1th0utnam3
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.