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

เกริ่นนำ

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

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

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

WHATWG ในการโหลดสคริปต์
WHATWG ในการโหลดสคริปต์

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

สคริปต์แรกของฉันมี

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

อ่า ความเรียบง่ายแสนสุขสันต์ ในส่วนนี้ เบราว์เซอร์จะดาวน์โหลดสคริปต์ทั้งสองพร้อมกันและเรียกใช้สคริปต์โดยเร็วที่สุดเท่าที่จะเป็นไปได้ โดยรักษาลำดับไว้ “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 และปรากฏในเบราว์เซอร์อื่น

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

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

WHATWG ทำให้ลักษณะการทำงานโจ่งแจ้งโดยประกาศว่า "เลื่อน" ไม่ให้ส่งผลต่อสคริปต์ที่เพิ่มแบบไดนามิกหรือไม่มี "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] แม้ว่าใน IE9 หรือต่ำกว่านั้น คุณจะได้รับ [1, 3, 2] การดำเนินการ DOM บางอย่างทำให้ IE หยุดการทำงานของสคริปต์ปัจจุบันชั่วคราว และเรียกใช้สคริปต์อื่นๆ ที่รอดำเนินการก่อนดำเนินการต่อ

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

HTML5 ช่วยเหลือคุณได้

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<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 ครั้ง โดย 1 ครั้งผ่านองค์ประกอบลิงก์ และอีกครั้งในสคริปต์

การแก้ไข: ตอนแรกฉันระบุว่าตัวสแกนการโหลดล่วงหน้านั้นดึงข้อมูลเหล่านี้มา แต่ไม่ใช่ โปรแกรมแยกวิเคราะห์ปกติดึงข้อมูลมาให้ อย่างไรก็ตาม เครื่องสแกนการโหลดล่วงหน้าอาจรับรู้ได้ แต่ยังไม่สามารถโหลดสคริปต์ล่วงหน้าได้ ในขณะที่สคริปต์ที่อยู่ในโค้ดสั่งการจะไม่สามารถโหลดล่วงหน้าได้ ขอขอบคุณ 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 ตามหลักการแล้ว เราต้องการดาวน์โหลดแบบไม่พร้อมกัน จากนั้นเรียกใช้สคริปต์การเพิ่มประสิทธิภาพโดยเร็วที่สุดเท่าที่เป็นไปได้ โดยเรียงลำดับอย่างไรก็ได้ แต่อยู่ต่อจากว่าคุณต้องการ Dependency .js ซึ่งเป็นการเพิ่มประสิทธิภาพแบบก้าวหน้าไปเรื่อยๆ ขออภัย ไม่มีวิธีที่แน่นอนที่จะทำเช่นนั้นได้ เว้นแต่สคริปต์จะถูกแก้ไขให้ติดตามสถานะการโหลดของทรัพยากร Dependency.js แม้แต่ async=false ก็ไม่อาจแก้ไขปัญหานี้ได้ เนื่องจากการดำเนินการ enhancedment-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>

นั่นไง อยู่ที่จุดสิ้นสุดขององค์ประกอบเนื้อหา ใช่ การเป็นนักพัฒนาเว็บก็เหมือนกับการเป็น King Sisyphus (บูม! คะแนนฮิปสเตอร์ 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 ละเว้น “เลื่อน” ในสคริปต์ที่ไม่มี “src” IE < 10 บอกว่า: ฉันอาจเรียกใช้ 2.js ในช่วงกลางของการเรียกใช้ 1.js น่าสนใจใช่ไหม เบราว์เซอร์สีแดงบอกว่า ฉันไม่รู้ว่า "ถ่วงเวลา" นี้คืออะไร ฉันจะโหลดสคริปต์ให้เหมือนว่าไม่มีอยู่ เบราว์เซอร์อื่นๆ บอกว่าก็ไม่เป็นไร แต่ฉันอาจไม่ได้เพิกเฉยต่อ "เลื่อน" สคริปต์ที่ไม่มี "src"

Async

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

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

Async false

[
  '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" แต่มีวิธีแก้ปัญหาชั่วคราวโดยใช้ "onอาจไม่ได้จัดสถานะ" เบราว์เซอร์อื่นๆ ที่เป็นสีแดงบอกว่า ฉันไม่เข้าใจสิ่งที่ "ไม่พร้อมกัน" นี้ ฉันจะเรียกใช้สคริปต์ทันทีที่ลงพื้น ไม่ว่าจะเรียงลำดับอย่างไรก็ตาม ทุกอย่างที่เหลือบอกว่า ฉันเป็นเพื่อนของคุณ เราจะทำตามหนังสือเล่มนี้ในหนังสือ