เจาะลึกความลึกลับของการโหลดสคริปต์

บทนำ

ในบทความนี้ เราจะสอนวิธีโหลด JavaScript บางส่วนในเบราว์เซอร์และเรียกใช้

เดี๋ยวก่อน กลับมา ฉันรู้ว่ามันฟังดูธรรมดาและธรรมดา แต่อย่าลืมว่าสิ่งนี้เกิดขึ้นในเบราว์เซอร์ซึ่งตามหลักทฤษฎีแล้วกลายเป็นหลุมพรางตัวเดิม การทราบข้อบกพร่องเหล่านี้จะช่วยให้คุณเลือกวิธีโหลดสคริปต์ที่เร็วที่สุดและรบกวนน้อยที่สุดได้ หากมีเวลาไม่มากนัก ให้ข้ามไปที่ข้อมูลอ้างอิงฉบับย่อ

สำหรับผู้เริ่มต้น ต่อไปนี้คือวิธีที่ข้อกำหนดกำหนดวิธีการต่างๆ ที่สคริปต์จะดาวน์โหลดและเรียกใช้ได้

WHATWG เกี่ยวกับการโหลดสคริปต์
WHATWG ในการโหลดสคริปต์

เช่นเดียวกับข้อกำหนดทั้งหมดของ WHATWG ตอนแรกเอกสารนี้ดูเหมือนซากปรักหักพังจากระเบิดลูกระเบิดหลายลูกในโรงงานสcrabble แต่เมื่ออ่านเป็นครั้งที่ 5 และเช็ดเลือดออกจากดวงตาแล้ว เอกสารนี้กลับน่าสนใจทีเดียว

สคริปต์แรกของฉันประกอบด้วย

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

ว้าว ความเรียบง่ายที่แสนสุข ในกรณีนี้ เบราว์เซอร์จะดาวน์โหลดสคริปต์ทั้ง 2 รายการพร้อมกันและเรียกใช้สคริปต์โดยเร็วที่สุดโดยคงลำดับไว้ "2.js" จะไม่ทํางานจนกว่า "1.js" จะทํางาน (หรือทํางานไม่สําเร็จ) "1.js" จะไม่ทํางานจนกว่าสคริปต์หรือสไตล์ชีตก่อนหน้าจะทํางาน เป็นต้น

แต่เบราว์เซอร์จะบล็อกการแสดงผลหน้าเว็บเพิ่มเติมขณะที่เหตุการณ์ทั้งหมดนี้เกิดขึ้น สาเหตุคือ DOM API จาก "ยุคแรกๆ ของเว็บ" ที่อนุญาตให้เพิ่มสตริงต่อท้ายเนื้อหาที่โปรแกรมแยกวิเคราะห์กำลังประมวลผล เช่น document.write เบราว์เซอร์รุ่นใหม่จะยังคงสแกนหรือแยกวิเคราะห์เอกสารในเบื้องหลังและเรียกให้ดาวน์โหลดเนื้อหาภายนอกที่อาจต้องใช้ (js, รูปภาพ, css ฯลฯ) แต่ยังคงบล็อกการแสดงผล

ด้วยเหตุนี้ ผู้เชี่ยวชาญด้านประสิทธิภาพจึงแนะนำให้ใส่องค์ประกอบสคริปต์ไว้ที่ท้ายเอกสาร เนื่องจากจะบล็อกเนื้อหาให้น้อยที่สุด นั่นหมายความว่าเบราว์เซอร์จะมองไม่เห็นสคริปต์ของคุณจนกว่าจะดาวน์โหลด HTML ทั้งหมด และเมื่อถึงจุดนั้นเอง เบราว์เซอร์ก็เริ่มดาวน์โหลดเนื้อหาอื่น เช่น CSS, รูปภาพ และ iframe เบราว์เซอร์สมัยใหม่ฉลาดพอที่จะให้ความสำคัญกับ JavaScript มากกว่าภาพ แต่เราทำได้ดีกว่านี้

ขอขอบคุณ IE (เราไม่ได้พูดจาประชดประชัน)

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

Microsoft ตระหนักถึงปัญหาด้านประสิทธิภาพเหล่านี้และได้เปิดตัว "เลื่อน" ใน Internet Explorer 4 ข้อความนี้หมายความว่า "ฉันสัญญาว่าจะไม่แทรกข้อมูลลงในโปรแกรมแยกวิเคราะห์โดยใช้สิ่งต่างๆ เช่น document.write หากฉันผิดสัญญา คุณมีสิทธิ์ลงโทษฉันด้วยวิธีใดก็ได้ที่เห็นว่าเหมาะสม" แอตทริบิวต์นี้ได้รวมอยู่ใน HTML4 และปรากฏในเบราว์เซอร์อื่นๆ

ในตัวอย่างนี้ เบราว์เซอร์จะดาวน์โหลดสคริปต์ทั้ง 2 รายการพร้อมกันและดำเนินการก่อนที่จะเรียกใช้ DOMContentLoaded โดยรักษาลําดับไว้

"เลื่อน" กลายเป็นเรื่องยุ่งเหยิงราวกับระเบิดลูกระเบิดในโรงงานเลี้ยงแกะ เรามีรูปแบบการเพิ่มสคริปต์ 6 รูปแบบ ได้แก่ แอตทริบิวต์ "src" และ "defer" รวมถึงแท็กสคริปต์กับสคริปต์ที่เพิ่มแบบไดนามิก แน่นอนว่าเบราว์เซอร์ต่างๆ ไม่ตรงกันกับลำดับที่ควรเรียกใช้ Mozilla เขียนบทความที่ยอดเยี่ยมเกี่ยวกับปัญหานี้ได้จากปี 2009

WHATWG ระบุลักษณะการทํางานอย่างชัดเจน โดยประกาศว่า "defer" ไม่มีผลกับสคริปต์ที่เพิ่มแบบไดนามิกหรือไม่มี "src" มิเช่นนั้น สคริปต์ที่เลื่อนไว้ควรทํางานหลังจากแยกวิเคราะห์เอกสารแล้ว ตามลําดับที่เพิ่ม

ขอบคุณนะ IE! (โอเค เราพูดเล่นนะ)

ให้แล้วก็ผลแพ้ ขออภัย เกิดข้อบกพร่องร้ายแรงใน IE4-9 ซึ่งอาจทําให้สคริปต์ทํางานในลําดับที่ไม่คาดคิด สิ่งที่เกิดขึ้นมีดังนี้

1.js

console.log('1');
document.getElementsByTagName('p')[0].innerHTML = 'Changing some content';
console.log('2');

2.js

console.log('3');

สมมติว่าหน้าเว็บมีย่อหน้า ลําดับที่คาดไว้ของบันทึกคือ [1, 2, 3] แต่คุณจะเห็น [1, 3, 2] ใน IE9 และเวอร์ชันที่ต่ำกว่า การดำเนินการ DOM บางรายการทําให้ IE หยุดการเรียกใช้สคริปต์ปัจจุบันชั่วคราวและเรียกใช้สคริปต์อื่นๆ ที่รอดําเนินการก่อนดำเนินการต่อ

อย่างไรก็ตาม แม้ในการใช้งานที่ไม่มีข้อบกพร่อง เช่น IE10 และเบราว์เซอร์อื่นๆ การดำเนินการของสคริปต์จะล่าช้าจนกว่าระบบจะดาวน์โหลดและแยกวิเคราะห์เอกสารทั้งฉบับ วิธีนี้อาจสะดวกในกรณีที่คุณจะต้องรอ DOMContentLoaded อยู่แล้ว แต่หากต้องการเพิ่มประสิทธิภาพอย่างจริงจัง คุณก็เริ่มเพิ่ม Listeners และ Bootstrap เร็วขึ้นได้…

HTML5 เข้ามาช่วยแก้ปัญหา

<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

HTML5 ให้แอตทริบิวต์ใหม่คือ "async" ซึ่งจะถือว่าคุณไม่ได้ใช้ document.write แต่ไม่รอจนกว่าจะมีการแยกวิเคราะห์เอกสาร เบราว์เซอร์จะดาวน์โหลดสคริปต์ทั้งสองพร้อมกันและเรียกใช้โดยเร็วที่สุด

น่าเสียดายที่ “2.js” อาจทำงานก่อน “1.js” นี้ก็ไม่เป็นไรหากแยกเป็นอิสระจากกัน อาจเป็น “1.js” เป็นสคริปต์ติดตามที่ไม่มีส่วนเกี่ยวข้องกับ “2.js” แต่หาก “1.js” เป็นสำเนา CDN ของ jQuery ที่ “2.js” ต้องใช้ หน้าเว็บของคุณก็จะไม่มีข้อผิดพลาด I-bomb นี้...

เรารู้แล้วว่าต้องใช้อะไร นั่นคือไลบรารี JavaScript

สิ่งที่จะเกิดขึ้นก็คือการดาวน์โหลดชุดสคริปต์นั้นทันทีโดยไม่มีการบล็อกการแสดงผล และทำงานโดยเร็วที่สุดตามลำดับที่เพิ่มเข้ามา ขออภัย HTML ไม่ชอบคุณและจะไม่อนุญาตให้คุณทำเช่นนั้น

ปัญหานี้ได้รับการแก้ไขด้วย JavaScript ในบางรูปแบบ บางรายการกำหนดให้คุณทำการเปลี่ยนแปลง JavaScript โดยรวมไว้ในการเรียกกลับที่ไลบรารีเรียกตามลำดับที่ถูกต้อง (เช่น RequireJS) แต่บางโปรแกรมจะใช้ XHR เพื่อดาวน์โหลดพร้อมกัน จากนั้นใช้ eval() ในลำดับที่ถูกต้อง ซึ่งใช้ไม่ได้กับสคริปต์ในโดเมนอื่นเว้นแต่ว่าจะมีส่วนหัว CORS และเบราว์เซอร์รองรับ บางคนยังใช้แฮ็กสุดเจ๋งอย่าง LabJS ด้วย

การแฮ็กดังกล่าวเกี่ยวข้องกับการหลอกให้เบราว์เซอร์ดาวน์โหลดทรัพยากรในลักษณะที่จะทริกเกอร์เหตุการณ์เมื่อเสร็จสมบูรณ์ แต่หลีกเลี่ยงการดำเนินการ ใน LabJS ระบบจะเพิ่มสคริปต์ด้วยประเภท MIME ที่ไม่ถูกต้อง เช่น <script type="script/cache" src="..."> เมื่อดาวน์โหลดสคริปต์ทั้งหมดแล้ว ระบบจะเพิ่มสคริปต์เหล่านั้นอีกครั้งด้วยประเภทที่ถูกต้อง โดยหวังว่าเบราว์เซอร์จะดึงสคริปต์เหล่านั้นจากแคชโดยตรงและดำเนินการตามลำดับโดยทันที ซึ่งขึ้นอยู่กับลักษณะการทำงานที่สะดวกแต่ไม่ได้ระบุไว้ และใช้งานไม่ได้เมื่อ HTML5 ประกาศว่าเบราว์เซอร์ไม่ควรดาวน์โหลดสคริปต์ที่มีประเภทที่ไม่รู้จัก โปรดทราบว่า LabJS ปรับตัวตามการเปลี่ยนแปลงเหล่านี้และตอนนี้ใช้วิธีการต่างๆ ในบทความนี้ร่วมกัน

อย่างไรก็ตาม ตัวโหลดสคริปต์ก็มีปัญหาด้านประสิทธิภาพเช่นกัน คุณจะต้องรอให้ JavaScript ของไลบรารีดาวน์โหลดและแยกวิเคราะห์ก่อน สคริปต์ใดก็ตามที่จัดการจึงจะเริ่มดาวน์โหลดได้ นอกจากนี้ เราจะโหลดโปรแกรมโหลดสคริปต์อย่างไร เราจะโหลดสคริปต์ที่บอกโปรแกรมโหลดสคริปต์ว่าจะโหลดอะไรได้อย่างไร ใครเป็นผู้เฝ้าดูผู้เฝ้าดู Why am I naked? คำถามเหล่านี้ล้วนแต่เป็นคำถามที่ยาก

พูดง่ายๆ ก็คือ ถ้าคุณต้องดาวน์โหลดไฟล์สคริปต์เพิ่มอีก 1 ไฟล์ก่อนที่จะคิดดาวน์โหลดสคริปต์อื่นๆ คุณแพ้การแข่งขันด้านประสิทธิภาพตรงนั้นเลย

DOM เข้ามาช่วย

คําตอบอยู่ในข้อกําหนดของ HTML5 แม้ว่าจะซ่อนอยู่ด้านล่างของส่วนการโหลดสคริปต์

เรามาแปลความหมายของ "Earthling" กัน

[
  '//other-domain.com/1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  document.head.appendChild(script);
});

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

[
  '//other-domain.com/1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

ซึ่งทำให้สคริปต์ของเรามีการทำงานแบบผสมผสานที่ HTML ธรรมดาทำไม่ได้ เมื่อทำงานไม่พร้อมกันอย่างชัดแจ้ง ระบบจะเพิ่มสคริปต์ลงในคิวการดำเนินการ ซึ่งเป็นคิวเดียวกับที่เพิ่มในตัวอย่าง HTML ธรรมดารายการแรกของเรา อย่างไรก็ตาม การสร้างแบบไดนามิกจะทำให้สคริปต์ทำงานนอกการแยกวิเคราะห์เอกสาร ดังนั้นจึงไม่มีการบล็อกการแสดงผลขณะที่ดาวน์โหลด (อย่าสับสนระหว่างการโหลดสคริปต์แบบไม่ใช้ Async กับ XHR แบบซิงค์ ซึ่งเป็นสิ่งที่ไม่ควรทำ)

สคริปต์ด้านบนควรรวมไว้กับส่วนหัวของหน้าเว็บ โดยจัดคิวการดาวน์โหลดสคริปต์โดยเร็วที่สุดโดยไม่รบกวนการแสดงผลแบบเป็นขั้นเป็นตอน และดำเนินการโดยเร็วที่สุดตามลำดับที่คุณระบุ “2.js” สามารถดาวน์โหลดได้ฟรีก่อน “1.js” แต่จะไม่ทำงานจนกว่า “1.js” จะดาวน์โหลดและเรียกใช้สำเร็จ หรือไม่ดำเนินการอย่างใดอย่างหนึ่ง ฮึ่มม! ดาวน์โหลดแบบไม่พร้อมกันแต่สั่งการได้!

ทุกเบราว์เซอร์ที่รองรับแอตทริบิวต์ async รองรับการโหลดสคริปต์ด้วยวิธีนี้ ยกเว้น Safari 5.0 (5.1 ใช้ได้) นอกจากนี้ ระบบยังรองรับ Firefox และ Opera ทุกเวอร์ชัน เนื่องจากเวอร์ชันที่ไม่รองรับแอตทริบิวต์ async จะเรียกใช้สคริปต์ที่เพิ่มแบบไดนามิกตามลำดับที่เพิ่มลงในเอกสารได้อย่างสะดวก

นี่เป็นวิธีที่เร็วที่สุดในการโหลดสคริปต์ใช่ไหม ถูกต้องไหม

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

เราสามารถเพิ่มการค้นพบได้ด้วยการใช้ข้อความนี้ในส่วนหัวของเอกสาร

<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">

ซึ่งบอกให้เบราว์เซอร์ทราบว่าหน้าเว็บต้องใช้ 1.js และ 2.js link[rel=subresource] คล้ายกับ link[rel=prefetch] แต่มีความหมายต่างกัน ขออภัย ปัจจุบันเครื่องมือนี้สนับสนุนเฉพาะใน Chrome และคุณต้องประกาศว่าสคริปต์ใดที่จะโหลด 2 ครั้ง ครั้งแรกผ่านองค์ประกอบของลิงก์ และอีกครั้งในสคริปต์ของคุณ

การแก้ไข: ก่อนหน้านี้เราระบุว่าข้อมูลเหล่านี้ได้รับการสแกนโดยเครื่องมือสแกนการโหลดล่วงหน้า แต่ไม่ใช่ ข้อมูลเหล่านี้ได้รับการสแกนโดยโปรแกรมแยกวิเคราะห์ทั่วไป อย่างไรก็ตาม ตัวสแกนการโหลดล่วงหน้าอาจตรวจพบรายการเหล่านี้ได้ แต่ยังไม่ได้ดำเนินการ ส่วนสคริปต์ที่รวมอยู่ในโค้ดที่เรียกใช้งานได้จะโหลดล่วงหน้าไม่ได้ ขอขอบคุณ Yoav Weiss ที่แก้ไขให้ในความคิดเห็น

ฉันคิดว่าบทความนี้น่าหวาดหวั่น

สถานการณ์กำลังซึมเศร้าและคุณน่าจะรู้สึกซึมเศร้า ไม่มีวิธีใดที่ใช้ซ้ำๆ ได้แต่ใช้ประกาศไม่ได้ ในการดาวน์โหลดสคริปต์อย่างรวดเร็วและไม่พร้อมกันในขณะที่ควบคุมลำดับการดำเนินการ เมื่อใช้ HTTP2/SPDY คุณจะลดค่าใช้จ่ายเพิ่มเติมของคำขอได้จนทำให้การส่งสคริปต์ในไฟล์ขนาดเล็กหลายไฟล์ที่แคชแยกกันได้เป็นวิธีที่เร็วที่สุด ลองจินตนาการว่า

<script src="dependencies.js"></script>
<script src="enhancement-1.js"></script>
<script src="enhancement-2.js"></script>
<script src="enhancement-3.js"></script>
…
<script src="enhancement-10.js"></script>

สคริปต์การเพิ่มประสิทธิภาพแต่ละรายการจะจัดการกับคอมโพเนนต์หน้าเว็บที่เฉพาะเจาะจง แต่ต้องใช้ฟังก์ชันยูทิลิตีใน dependencies.js ตามหลักการแล้วเราต้องการดาวน์โหลดแบบไม่พร้อมกันทั้งหมด แล้วเรียกใช้สคริปต์การปรับปรุงโดยเร็วที่สุด โดยเรียงลำดับอย่างไรก็ได้ แต่อยู่หลัง Dependencies.js เรียกได้ว่าเป็นการเพิ่มประสิทธิภาพแบบต่อเนื่อง น่าเสียดายที่ไม่มีวิธีประกาศให้บรรลุผลดังกล่าว เว้นแต่จะมีการแก้ไขตัวสคริปต์ให้ติดตามสถานะการโหลดของ Dependencies.js แม้แต่ async=false ก็ยังแก้ปัญหานี้ไม่ได้ เนื่องจากการดำเนินการ enhancement-10.js จะบล็อกที่ 1-9 มีเบราว์เซอร์เพียงชนิดเดียวที่สามารถทำเช่นนี้ได้โดยไม่มีการแฮ็ก...

IE มีไอเดียดีๆ

IE จะโหลดสคริปต์แตกต่างจากเบราว์เซอร์อื่นๆ

var script = document.createElement('script');
script.src = 'whatever.js';

IE จะเริ่มดาวน์โหลด "whatever.js" ในตอนนี้ แต่เบราว์เซอร์อื่นๆ จะไม่เริ่มดาวน์โหลดจนกว่าจะเพิ่มสคริปต์ลงในเอกสาร IE ยังมีเหตุการณ์ “readystatechange” และพร็อพเพอร์ตี้ “readystate” ซึ่งบอกความคืบหน้าในการโหลดให้เราทราบด้วย ซึ่งมีประโยชน์มากจริงๆ เพราะช่วยให้เราสามารถควบคุมการโหลดและเรียกใช้สคริปต์ได้อย่างอิสระ

var script = document.createElement('script');

script.onreadystatechange = function() {
  if (script.readyState == 'loaded') {
    // Our script has download, but hasn't executed.
    // It won't execute until we do:
    document.body.appendChild(script);
  }
};

script.src = 'whatever.js';

เราสามารถสร้างรูปแบบการพึ่งพาที่ซับซ้อนได้โดยเลือกเวลาที่จะเพิ่มสคริปต์ลงในเอกสาร IE รองรับรูปแบบนี้มาตั้งแต่เวอร์ชัน 6 น่าสนใจทีเดียว แต่ยังคงมีปัญหาการค้นพบตัวโหลดล่วงหน้าเช่นเดียวกับ async=false

พอแล้ว ฉันควรโหลดสคริปต์อย่างไร

เข้าใจแล้ว หากต้องการโหลดสคริปต์ในลักษณะที่ไม่บล็อกการแสดงผล ไม่เกี่ยวข้องกับการซ้ำ และรองรับเบราว์เซอร์ได้อย่างยอดเยี่ยม เราขอแนะนําให้ทําดังนี้

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

นั่นคือ ที่ท้ายองค์ประกอบ body ใช่ การเป็นนักพัฒนาเว็บก็เหมือนกับการเป็นกษัตริย์ซิซิฟัส (บูม คะแนนฮิปสเตอร์ 100 คะแนนสำหรับการอ้างอิงตำนานกรีก ข้อจำกัดใน HTML และเบราว์เซอร์ช่วยป้องกันไม่ให้เราทำงานได้ดีขึ้นมาก

เราหวังว่าโมดูล JavaScript จะช่วยแก้ปัญหานี้ด้วยวิธีการโหลดสคริปต์แบบประกาศที่ไม่บล็อกและควบคุมลําดับการดําเนินการได้ แม้ว่าจะต้องเขียนสคริปต์เป็นโมดูลก็ตาม

อุ้ย ต้องมีวิธีอื่นที่ดีกว่านี้ไหม

เข้าใจแล้ว เคล็ดลับเพิ่มเติมคือหากต้องการเพิ่มประสิทธิภาพอย่างจริงจังและไม่สนใจความซับซ้อนและความซ้ำซาก คุณก็ใช้เคล็ดลับข้างต้นร่วมกันได้

ก่อนอื่น เราจะเพิ่มการประกาศทรัพยากรย่อยสำหรับตัวโหลดล่วงหน้า ดังนี้

<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">

จากนั้นแทรกในบรรทัดในส่วนหัวของเอกสาร เราจะโหลดสคริปต์ของเราด้วย JavaScript โดยใช้ async=false และกลับไปใช้การโหลดสคริปต์แบบ Readystate ของ IE ที่ย้อนกลับไปถ่วงเวลา

var scripts = [
  '1.js',
  '2.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];

// Watch scripts load in IE
function stateChange() {
  // Execute as many scripts in order as we can
  var pendingScript;
  while (pendingScripts[0] && pendingScripts[0].readyState == 'loaded') {
    pendingScript = pendingScripts.shift();
    // avoid future loading events from this script (eg, if src changes)
    pendingScript.onreadystatechange = null;
    // can't just appendChild, old IE bug if element isn't closed
    firstScript.parentNode.insertBefore(pendingScript, firstScript);
  }
}

// loop through our script urls
while (src = scripts.shift()) {
  if ('async' in firstScript) { // modern browsers
    script = document.createElement('script');
    script.async = false;
    script.src = src;
    document.head.appendChild(script);
  }
  else if (firstScript.readyState) { // IE<10
    // create a script and add it to our todo pile
    script = document.createElement('script');
    pendingScripts.push(script);
    // listen for state changes
    script.onreadystatechange = stateChange;
    // must set src AFTER adding onreadystatechange listener
    // else we'll miss the loaded event for cached scripts
    script.src = src;
  }
  else { // fall back to defer
    document.write('<script src="' + src + '" defer></'+'script>');
  }
}

หลังจากใช้เทคนิคและการทำให้เป็นไฟล์ขนาดเล็กแล้ว ไฟล์มีความยาว 362 ไบต์ + URL ของสคริปต์

!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
  "//other-domain.com/1.js",
  "2.js"
])

การใช้การรวมสคริปต์แบบง่ายคุ้มค่ากับการเพิ่มจำนวนไบต์ไหม หากคุณใช้ JavaScript เพื่อโหลดสคริปต์แบบมีเงื่อนไขอยู่แล้ว เช่นเดียวกับที่ BBC ทำ คุณก็อาจได้รับประโยชน์จากการเรียกให้ดาวน์โหลดเหล่านั้นให้เร็วขึ้นด้วย หรืออาจไม่จำเป็น ให้ใช้วิธีการสิ้นสุดข้อความแบบง่าย

ว้าว ตอนนี้ฉันรู้แล้วว่าทำไมส่วนการโหลดสคริปต์ WHATWG จึงมีขนาดใหญ่มาก ฉันอยากดื่ม

ข้อมูลอ้างอิงโดยย่อ

องค์ประกอบสคริปต์ทั่วไป

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

ข้อกำหนดระบุว่า: ดาวน์โหลดพร้อมกัน ดำเนินการตามลำดับหลัง CSS ที่รอดำเนินการ บล็อกการแสดงผลจนกว่าจะเสร็จสมบูรณ์ เบราว์เซอร์ตอบว่า: ได้เลย

เลื่อน

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

ข้อกำหนดระบุว่า ดาวน์โหลดพร้อมกัน โดยดำเนินการตามลำดับก่อน DOMContentLoaded ละเว้น "defer" ในสคริปต์ที่ไม่มี "src" IE < 10 ระบุว่า: ฉันอาจเรียกใช้ 2.js ในช่วงครึ่งทางของการเรียกใช้ 1.js สนุกไหม เบราว์เซอร์สีแดงระบุว่า: ฉันไม่ทราบว่าสิ่งที่ "เลื่อนออกไป" นี้คืออะไร ฉันจะโหลดสคริปต์เสมือนว่าไม่มีอยู่ เบราว์เซอร์อื่นๆ พูดว่า: โอเค แต่ฉันอาจไม่ละเว้น "defer" ในสคริปต์ที่ไม่มี "src"

Async

<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

ข้อมูลจำเพาะระบุว่า: ดาวน์โหลดพร้อมกัน เรียกใช้ตามลำดับที่ดาวน์โหลด เบราว์เซอร์สีแดงจะแสดงข้อความว่า "async" คืออะไร ฉันจะโหลดสคริปต์ราวกับว่าไม่มี เบราว์เซอร์อื่นๆ บอกให้ทำดังนี้

Async เท็จ

[
  '1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

ข้อกำหนดระบุว่า: ดาวน์โหลดพร้อมกัน ดำเนินการตามลำดับทันทีที่ดาวน์โหลดทั้งหมด Firefox < 3.6, Opera บอกว่า: ฉันไม่ทราบว่าสิ่งที่ "ไม่พร้อมกัน" นี้คืออะไร แต่บังเอิญฉันเรียกใช้สคริปต์ที่เพิ่มผ่าน JS ตามลำดับที่เพิ่มเข้ามา Safari 5.0 บอกว่า: เราเข้าใจ "async" แต่ไม่เข้าใจการตั้งค่าเป็น "false" ด้วย JS เราจะเรียกใช้สคริปต์ทันทีที่ผู้ใช้เข้าชม ไม่ว่าจะเข้าชมในลำดับใดก็ตาม IE < 10 แสดงข้อความว่า: ไม่เข้าใจ "async" แต่มีวิธีแก้ปัญหาโดยใช้ "onreadystatechange" เบราว์เซอร์อื่นๆ ในสีแดงแสดงข้อความว่า: ฉันไม่เข้าใจ "async" นี้ ฉันจะเรียกใช้สคริปต์ทันทีที่โหลด ไม่ว่าจะโหลดในลำดับใดก็ตาม ส่วนข้อความอื่นๆ หมายความว่า เราเป็นเพื่อนกัน เราจะทำตามขั้นตอนที่ถูกต้อง