JavaScript Loader คืออะไร + สอนวิธีใช้ LABjs

Suranart Niamcome
SiamHTML
Published in
5 min readOct 27, 2014

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

  • HTML parser จะหยุดการทำงานชั่วคราวทุกครั้งเมื่อมันเจอ script tag และจะกลับมาทำงานต่อหลังจากที่ดาวน์โหลดและ execute ไฟล์ script นั้นๆ เสร็จแล้ว
  • การที่ HTML parser หยุดทำงาน หรือที่เรียกว่า parser blocking นั้นทำให้เว็บโหลดช้าลง
  • การใส่ async หรือ defer เอาไว้ที่ script tag จะช่วยให้ HTML parser ไม่หยุดทำงาน
  • async จะทำให้ external script ถูก execute ทันทีที่ดาวน์โหลดเสร็จแล้ว แต่เราจะไม่รู้ว่า script ตัวไหน execute ก่อนกัน
  • defer จะทำให้ external script ถูก execute หลังจากที่ HTML parser ทำงานเสร็จแล้ว และยัง execute ตรงตามลำดับ แต่ติดตรงที่มันจะเริ่ม execute ช้าไปหน่อย
  • JavaScript Loader เป็น tool สำหรับโหลด external script เข้ามาในหน้าเว็บ
  • LABjs เป็น JavaScript Loader ตัวหนึ่ง ที่จะช่วยให้การโหลด JS มีประสิทธิภาพมากยิ่งขึ้น โดยเราสามารถกำหนดได้ว่าจะให้ script ตัวไหน เป็นแบบ async บ้าง และยังกำหนดได้ด้วยว่าจะให้ script ตัวนี้รอ execute ต่อจาก script ตัวไหน

เพราะ JavaScript มีส่วนทำให้เว็บช้า

ในการแสดงผลหน้าเว็บนั้น web browser จะใช้สิ่งที่เรียกว่า HTML parser ในการไล่ดูโค้ดที่เราเขียนเพื่อเอามาจัดรูปแบบให้เหมาะสมต่อการแสดงผลครับ โดยการไล่ดูโค้ดของ HTML parser นั้น มันก็จะไล่จากบนลงล่าง เวลาเจอพวกไฟล์รูปหรือ css/js ภายนอก มันก็จะรู้ว่าต้องไปโหลดไฟล์เหล่านั้นมาใช้

แต่ประเด็นก็คือ HTML parser จะต้องหยุดการทำงานชั่วคราวทุกครั้งเมื่อมันไล่มาจนถึง script tag และจะกลับมาทำงานต่อหลังจากที่ดาวน์โหลด(หากเป็น external script) และ execute ไฟล์ script นั้นๆ เสร็จเรียบร้อยแล้ว

ฟังดูเหมือนเป็นเรื่องเล็กๆ นะครับ แต่การที่ HTML parser หยุดทำงานนี้สามารถทำให้เว็บแสดงผลช้าลงอย่างเห็นได้ชัดเลย สมมติว่าเราเอาไฟล์ js อันนึงขนาด 100KB วางเอาไว้เหนือรูป

<head>
.
.
.
<script type="text/javascript" src="http://static.siamhtml.com/script.js"></script>
</head>
<body>
<img src="http://static.siamhtml.com/image.jpg">
</body>
จากโค้ดด้านบน ผลที่ได้ก็คือ web browser จะไปโหลดไฟล์ js ขนาด 100KB นั้นมาแล้วจึง execute ครับ ในระหว่างนั้น รูปที่อยู่ใต้ js ดังกล่าวจะยังไม่ถูกโหลดมาเพราะว่า HTML parser ยังไล่มาไม่ถึง เราจะเรียกเหตุการณ์นี้ว่า Parser Blocking ครับ
js in head
แล้วทำไมต้อง Block ด้วย ?การที่ HTML parser ต้องหยุดทำงานเวลาที่โหลดและ execute ไฟล์ script นั้นเป็นเพราะ web browser ไม่แน่ใจว่า script ที่มันเจอนั้น เราตั้งใจจะให้ execute ทันที ณ ตำแหน่งนั้นๆ เลยหรือเปล่า เพื่อความปลอดภัย มันก็เลยหยุดทำงานซะเลย แล้วหันมา execute เจ้า script นี้ให้เสร็จซะก่อน แล้วค่อยมาทำต่อดีกว่า จริงๆ แล้ว web browser นั้นหวังดีกับเรานะครับ ถึงได้ block เพราะมันจะทำให้เรารู้ลำดับการ execute ของ web browser ที่ชัดเจนเลยว่า script ไหนจะถูก execute ก่อนกันสมมติว่าเรามี js อยู่ 2 ไฟล์ โดยไฟล์หนึ่งเป็น framework ส่วนอีกไฟล์หนึ่งเป็น plugin ที่อาศัย framework ดังกล่าว หากไม่มีกลไกของการ block เราก็จะไม่สามารถมั่นใจได้เลยว่าไฟล์ framework นั้นจะ execute เสร็จก่อนไฟล์ plugin หรือเปล่า แม้ว่าเราจะใส่ไฟล์ framework เอาไว้เหนือไฟล์ plugin ก็ตามหรือบางทีเราต้องการให้ execute ไฟล์ js ให้เรียบร้อยก่อนแล้วค่อยแสดงผลหน้าเว็บออกมาไม่งั้นมันอาจจะทำให้ UX เปลี่ยนไป เช่น การที่ slider กางออกมาในตอนแรกก่อนแล้วค่อยหุบหลังจากที่ execute ไฟล์ js เสร็จจะเห็นว่าการ block สามารถช่วยแก้ปัญหาเหล่านี้ได้ครับ แต่ข้อเสียก็คือหากมีการ block เยอะๆ มันก็จะทำให้หน้าเว็บแสดงผลช้าลงไปนั่นเอง คำถามคือ แล้วถ้าไฟล์ script ของเราไม่ต้องไปอาศัยไฟล์อื่นๆ เลย หรือถ้ามันไม่ได้มีผลต่อ UX เลย พอจะมีวิธีที่ทำให้มันไม่ต้องไป block ชาวบ้านเค้ามั้ย ?Async vs. Deferมีครับ โดยการใส่ attribute async หรือ defer เอาไว้ที่ script tag เพียงเท่านี้การดาวน์โหลดและ execute ของ external script นั้นๆ ก็จะไม่ทำให้ HTML parser หยุดทำงานแล้วล่ะครับ อย่างไรก็ตาม ความแตกต่างในการใช้ 2 attribute นี้ก็คือเวลาที่ web browser จะเริ่ม execute script ครับ

Async

โดยการใส่ attribute async เอาไว้ที่ script tag นั้นจะทำให้ external script ถูก execute ทันทีที่ดาวน์โหลดเสร็จแล้ว ซึ่งข้อดีของวิธีนี้ก็คือมันจะทำงานเสร็จไว(เพราะเริ่ม execute เร็ว) แต่ข้อเสียก็คือเราไม่สามารถการันตีลำดับของการ execute ได้เลยว่า external script ตัวไหนจะเริ่มทำก่อน-หลัง เพราะมันขึ้นอยู่กับว่า script ตัวไหนจะโหลดเสร็จก่อนกัน
js with async
// แบบนี้อาจเกิด error ได้ เพราะ script ตัวหลังต้องอาศัย script ตัวแรก
<script src="jquery.js" async></script>
<script src="jquery.plugin.js" async></script>
// แบบนี้ปลอดภัย เพราะ script ไม่อาศัยกันและกัน
<script src="thirdParty1.js" async></script>
<script src="thirdParty2.js" async></script>
จากลักษณะข้างต้น เรามักจะพบการใช้ async กับ script ที่จบในตัว ไม่ต้องไปพึ่งพาอาศัย script ตัวอื่นๆ และไม่มี script ตัวอื่นๆ พึ่งพาอาศัยด้วย ตัวอย่างเช่น script สำหรับเก็บสถิติต่างๆ แต่เนื่องจาก async นั้นสามารถใช้ได้ตั้งแต่ IE10+ และ Android Browser 4+ เวลาจะเอาไปใช้จริง เราก็เลยต้องเขียนแบบนี้
(function(d, t) {
var js = d.createElement(t),
s = d.getElementsByTagName(t)[0];
js.src = '//static.siamhtml.com/script.js';
s.parentNode.insertBefore(js, s);
}(document, 'script'));
แม้ผลที่ได้จะเหมือนกับการใช้ async แต่การรองรับของ web browser นั้นหลากหลายกว่ามาก วิธีนี้จึงกลายเป็นรูปแบบที่ script จาก third-party ต่างๆ นิยมใช้กันครับ

Defer

ส่วนการใส่ attribute defer เอาไว้ที่ script tag นั้นจะทำให้ external script ถูก execute หลังจากที่ HTML parser ทำงานเสร็จแล้ว (อารมณ์ประมาณ $(document).ready() ของ jQuery เลยครับ แต่จะเริ่มทำก่อนนิดนึง) และที่สำคัญก็คือมันสามารถรักษาลำดับของการ execute เอาไว้ให้ตรงตามลำดับของการวาง script tag อีกด้วยครับ ข้อเสียเพียงอย่างเดียวของการใช้ defer ก็คือมันจะเริ่ม execute ช้ากว่าการใช้ async เท่านั้นเอง (คือโหลดของมารอไว้แล้ว แต่หน่วงเวลาเอาไว้ไม่ให้ execute)
js with defer
// แบบนี้ OK เพราะจะ execute ตามลำดับ เพียงแต่จะเริ่ม execute ช้าหน่อยเท่านั้นเอง
<script src="jquery.js" defer></script>
<script src="jquery.plugin.js" defer></script>
แต่ที่เราไม่ค่อยพบเห็นการใช้ defer มากเท่าไรนักก็คงเป็นเพราะว่ามันยังคงมี bug เกี่ยวกับการ execute ไม่ตรงตาม order ใน IE<10 อยู่ (ซึ่งหมายความว่ามันอาจจะทำให้เกิด error ได้ ไม่คุ้มๆ)

ทำไมเค้านิยมใส่ JavaScript ไว้ล่างสุด ?

ก่อนหน้านี้เราอาจจะเคยได้ยินมาว่าให้เราใส่ js ไว้ล่างสุด เพราะจะทำให้โหลดหน้าเว็บได้ไวกว่า ก็ถือว่าถูกนะครับ เพียงแต่มันไม่เสมอไป การเอา js ไว้ล่างสุดนั้นมีข้อดีตรงที่มันจะไม่ไป block การแสดงผลของหน้าครับ (จริงๆ แล้วมันก็ยัง block นะครับ เพียงแต่ไม่มีอะไรอยู่ใต้มันแล้วก็เลยดูเหมือนไม่ได้ block อะไร) สาเหตุที่คนแนะนำวิธีนี้กันเยอะก็เพราะว่ามันปลอดภัยครับ ไม่ต้องไปกังวลเรื่องลำดับของการ execute ใดๆ แถมยังทำให้หน้าเว็บแสดงผลออกมาได้เร็วกว่าการเอา js ไว้ใน head tag อีกด้วย ผมขอเรียกมันว่าเป็นวิธีที่ "คุ้ม" ละกันครับ
js at bottom
แต่ข้อเสียของวิธีนี้ก็คือมันจะเริ่มดาวน์โหลดและ execute ช้าไปหน่อยครับ (ก็แหงละ กว่า HTML parser จะไล่ไปถึง อยากใส่ไว้ล่างสุดเองทำไม!!!) ตัวอย่างที่เห็นได้ชัดเลยก็คือ สมมติเราใส่ script สำหรับทำเนื้อหาให้เป็นแบบ slider เอาไว้ล่างสุดเลย เรามักจะพบว่าเนื้อหาจะแสดงผลออกมาแบบปกติก่อนในตอนแรกแล้วค่อยกลายเป็น slider ทีหลังครับ หรือถ้าเราเอา script ที่มี click event listener ไว้ล่างสุด หากมีบางคนลอง click ก่อนที่ web browser จะ execute เสร็จก็จะพบว่าไม่มีอะไรเกิดขึ้นครับ ซึ่งเหตุการณ์เหล่านี้แทบจะไม่เกิดขึ้นเลยถ้าเราใส่ script เอาไว้ใน head tag (แต่มันก็จะไป block การแสดงผลเต็มๆ)แล้วแบบนี้ JavaScript ควรจะอยู่บนหรือล่าง ?ด้วยเหตุผลดังกล่าว การตัดสินใจว่าควรจะเอา script ตัวไหนไว้บนหรือล่าง เราต้องดูเป็นกรณีๆ ไปครับ ไม่ใช่ว่าต้องบนสุดทุกอันหรือล่างสุดทุกอันเสมอไป ผมมีหลักง่ายๆ เลยก็คือ "เอาไว้บนจะเริ่ม execute เร็ว vs. เอาไว้ล่าง หน้าเว็บแสดงผลเร็ว" ให้เราดูครับว่า script นั้นๆ จำเป็นต้องเริ่ม execute เร็วหรือเปล่า ?
  • ถ้าจำเป็นให้เอาไว้บนสุดครับ แล้วดูต่อว่ามันต้องพึ่งพาใครหรือมีใครพึ่งพามันมั่งมั้ย ถ้าไม่มีเลย ก็ใส่ async เข้าไปครับ
  • ถ้าไม่จำเป็นให้เอาไว้ล่างสุดครับ
คำถามที่ตามมาก็คือ ถ้าต้องการให้ script นั้นเริ่ม execute ไวๆ แต่ script นั้นดันไปเกี่ยวข้องกับ script อื่นๆ ด้วย เราจะทำอย่างไร ? ใช้ async ก็ไม่ชัวร์ ใช้ defer ก็เริ่มทำช้า ถ้าไม่ใส่อะไรเลยก็ไป block การแสดงผลของหน้าเว็บอีก

รู้จักกับ JavaScript Loader

เข้าเรื่องซะทีนะครับ T__T ปัญหาดังกล่าวเราสามารถแก้ด้วยสิ่งที่เรียกว่า JavaScript Loader ครับ โดยหน้าที่หลักของมันก็คือการโหลด external script เข้ามาในหน้าเว็บนั่นเอง เมื่อลองค้นหาใน Google ดู เราก็จะเจอ JavaScript Loader หลายตัวเลยนะครับ ซึ่งแต่ละตัวก็จะมีลักษณะเด่นที่แตกต่างกันออกไป ผมจึงขอพูดถึงเฉพาะตัวที่เด่นๆ แล้วกันครับ
  • HeadJSตัวนี้สามารถโหลดแบบไม่ block ได้ทั้ง css และ js เลยครับ จุดเด่นของมันก็คือสามารถกำหนด condition ในการโหลดได้ด้วย เช่น โหลด js ตัวนี้ก็ต่อเมื่อหน้าจอมีขนาดใหญ่กว่า 800px นะ หรือจะสร้าง condition ขึ้นมาใช้เองก็สามารถทำได้ครับ
  • LABjsถึงแม้ฟีเจอร์ของ LABjs จะไม่เยอะเหมือน HeadJS แต่จุดเด่นของมันก็คือความเร็วและสามารถกำหนดลำดับของการ execute ได้ครับ น่าเสียดายที่รองรับเฉพาะ js เท่านั้น
  • RequireJSสำหรับ RequireJS นั้นจริงๆ แล้ว มันเป็น tool สำหรับ dependency management ครับ ส่วนความสามารถในการโหลด script นั้นถือเป็นเพียงแค่ส่วนหนึ่งของมันเท่านั้นเอง ดังนั้นการใช้งานจึงค่อนข้างยุ่งยากกว่าตัวอื่นๆ ครับ ผมเลยขอยกไปพูดถึงในบทความ สอนวิธีใช้ RequireJS แทนนะครับ
สำหรับบทความนี้ ผมขอเลือกใช้ LABjs แล้วกันนะครับ เนื่องจากทำความเข้าใจได้ง่าย แล้วปัญหาของเราก็แค่อยากใช้ async ที่สามารถคุมลำดับของการ execute ได้ด้วยเท่านั้นเอง ซึ่ง LABjs สามารถตอบโจทย์นี้ได้เป็นอย่างดีแล้วครับ

JavaScript Loader กับ UX

ก่อนที่เราจะลองใช้ JavaScript Loader ในการโหลด js แบบไม่ block การแสดงผลหน้าเว็บนั้น เราจะต้องยอมรับข้อเสียของมันด้วยนะครับ นั่นก็คือ หากเรามีการใช้ js ในการตกแต่งการแสดงผลของเนื้อหา เราอาจจะเห็นเนื้อหาแบบที่ยังไม่ผ่านการตกแต่งด้วย js โผล่ออกมาแวบนึงได้ครับ วิธีแก้ปัญหานี้ก็คือ เราอาจจะต้องซ่อนเนื้อหาเหล่านั้นด้วย css ไว้ก่อน แล้วเราค่อยไปกำหนดให้แสดงเนื้อหาเหล่านั้นในโค้ด js เอาครับ

วิธีโหลด JS ด้วย LABjs

เรามาลองใช้ LABjs กันเลยดีกว่าครับ สมมติว่าเราจะใช้ jQuery plugin สำหรับทำเนื้อหาให้เป็นแบบ slider โดยปกติแล้วเราก็จะเขียนแบบนี้ครับ<script src="jquery.js"></script>
<script src="jquery.slider.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('.someClass').slider();
});
</script>
แน่นอนว่าการเขียนแบบนี้จะไปทำให้ HTML parser หยุดการทำงานชั่วคราวครับ ทีนี้เราจะมาลองเปลี่ยนโค้ดนี้มาเขียนโดยใช้ LABjs ดู

1. เปลี่ยน src มาเป็น .script()

ก่อนอื่นให้เราไปโหลด LAB.js มาก่อนครับ จะได้ใช้ความสามารถของ LABjs ได้ จากนั้นให้เราเปลี่ยนการโหลด external script ทุกจุดมาโหลดโดยใช้ .script() ของ LABjs แทนแบบนี้ครับ<script src="LAB.js"></script>
<script>
$LAB
.script("jquery.js") // execute ทันทีที่โหลดเสร็จ
.script("jquery.slider.js"); // execute ทันทีที่โหลดเสร็จ
</script>
.
.
.
เมื่อลองรันดูจะพบว่าว่ามัน error ครับ เพราะ jquery.slider.js นั้นเป็น script ที่ต้องอาศัย jquery.js ในการ execute วิธีแก้คือเราจะต้องกำหนดลำดับของการ execute ให้มันใหม่ครับ

2. ใส่ .wait() หลัง script ที่ต้อง execute ก่อน

ให้เราใส่ .wait() ตามหลัง jquery.js ครับ เพื่อเป็นการห้ามไม่ให้ script ตัวหลังจากนี้เริ่ม execute จนกว่า jquery.js จะถูก execute จนเสร็จแล้ว<script src="LAB.js"></script>
<script>
$LAB
.script("jquery.js").wait() // รอให้ตัวนี้ execute เสร็จก่อน แล้วถึงจะ execute ตัวต่อไปได้
.script("jquery.slider.js");
</script>
.
.
.
เมื่อลองรันดูอีกทีก็จะพบ error อยู่ดีครับ เนื่องจากโค้ดในส่วนของ $(document).ready(); นั้นยังคง execute ก่อนที่จะโหลด jquery.js มาอยู่ดี (เพราะมันเป็น inline ไม่ต้องเสียเวลาไปโหลดมา)

3. ย้าย inline code มาไว้ใน .wait(function(){ })

ให้เราย้าย $(document).ready(); มาไว้ใน .wait(function(){  }) หลังการโหลด jquery.slider.js ครับ โดยโค้ดที่อยู่ในส่วนนี้มันจะเริ่ม execute หลังจากที่ jquery.slider.js ถูก execute เสร็จแล้ว<script src="LAB.js"></script>
<script>
$LAB
.script("jquery.js").wait()
.script("jquery.slider.js")
.wait(function(){
// โค้ดที่จะ execute ต่อ หลังจากที่ execute "jquery.slider.js" เสร็จ
$(document).ready(function() {
$('.someClass').slider();
});
});
</script>
เมื่อลองรันดูอีกครั้ง เราก็จะไม่พบ error ใดๆ แล้วล่ะครับ

ใช้ JavaScript Loader แล้วเร็วกว่าจริงหรือ ?

เพื่อเป็นการลองวิชา เราลองมาวัดความเร็วของแต่ละวิธีกันดูครับ วิธีที่ผมวัดก็คือสร้างหน้าเทสมา 3 แบบด้วยกัน ผลการเทสเป็นแบบนี้ครับ
  • เอา script ไว้ใน headพบว่าวิธีนี้ช้าสุดครับ ตรงตามทฤษฎีเป๊ะเลย
  • เอา script ไว้ล่างสุดวิธีนี้เร็วกว่าวิธีแรกชัดเจนครับ แต่จะพบว่ารูปทั้งหมดจะกางออกมาก่อนแล้วค่อยกลายเป็น slider ซึ่งต่างจากวิธีแรกที่แสดงผลออกมาเป็น slider เลยตั้งแต่แรก
  • ใช้ LABjsวิธีนี้ผลที่ได้คล้ายกับวิธีเอา script ไว้ล่างสุดครับ คือรูปจะกางออกมาก่อนเหมือนกัน แต่จะพบว่าเร็วกว่าโดยเฉพาะหน้าที่มีของเยอะๆ เพราะมันเริ่ม execute เร็วนั่นเอง อย่างไรก็ตามหากหน้านั้นมีพวกรูปหรือ script ไม่เยอะมากนักกลับพบว่าวิธีนี้จะช้ากว่าครับ เพราะว่ามันจะต้องโหลดตัว LABjs เองเพิ่มเข้ามาด้วย

บทสรุปการใช้ JavaScript Loader

หลังจากที่ได้ทดสอบแล้ว พบว่าการใช้ JavaScript Loader นั้นช่วยให้หน้าเว็บโหลดเร็วขึ้นได้จริงๆ ครับ อย่างไรก็ตาม การจะตัดสินใจว่าจะเลือกใช้ JavaScript Loader หรือไม่นั้น เราคงจะต้องมาดู script แต่ละตัวว่ามันทำหน้าที่อะไรกันบ้างหากองค์ประกอบส่วนใหญ่ของหน้าเว็บจำเป็นต้องอาศัย script นั้น เราก็สามารถเลือกโหลดโดยใช้ JavaScript Loader ได้ครับ เพราะเราต้องการให้มันรีบ execute โดยเร็วที่สุดและยังสามารถคุมลำดับการ execute ได้ด้วย แต่หากหน้าเว็บนั้นสามารถแสดงผลได้อย่างสมบูรณ์โดยไม่ต้องอาศัย script นั้นเลย เราก็อาจเลือกเอา script นั้นไว้ล่างสุดก็ได้ เพราะว่ามันจะทำให้เนื้อหาสามารถแสดงผลออกมาได้เร็วกว่านั่นเองสุดท้ายแล้ว ไม่ว่าเราจะเลือกวิธีไหนก็ตามอย่าลืมทำการทดสอบความเร็วครับ  ให้เราลองวัดดูเลยว่าวิธีไหนมันเร็วที่สุด แล้ว UX ยังโอเคด้วย เพราะที่เล่ามาทั้งหมดนั้นมันก็เป็นเพียงแค่ทฤษฎี !!!

--

--