JavaScript stuff

ลำดับการวาง

มีคนถามเราว่า <script> วางไว้ตรงไหนในหน้าเพจดี

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <!-- ตรงนี้ -->
    </head>
    <body>
        <h1>Hello, world</h1>
        <!-- หรือตรงนี้ -->
    </body>
</html>

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

ทีนี้ ลำดับการโหลดมีผลสำคัญด้วย คือ

<script>
$(function(){
    // ..
});
</script>
<script src="jquery.js"></script>

โค้ดนี้จะ error เพราะเวลามันอ่านโค้ด มันรันจากบนลงล่าง และใน script ตัวบนจะเห็นว่า jQuery ยังไม่ถูกเรียกออกมาจึงทำให้ไม่มีฟังก์ชั่น $

Magical $

ฟังก์ชั่น $ เป็นฟังก์ชั่นหลักของ jQuery ซึ่งจริงๆ แล้วก็คือฟังก์ชั่นชื่อ jQuery ที่เปลี่ยนชื่อมาให้พิมพ์ง่าย (ใน Javascript ใช้ตัว $, _ ขึ้นหน้าฟังก์ชั่นได้)

ทีนี้เราจะใส่อะไรลงไปเป็นอากิวเมนต์ของ $ ได้บ้าง?

หังก์ชั่น

$(function(){...}); คือรูปย่อของ $(document).ready(function(){..})); ก็คือเอาฟังก์ชั่นที่เราใส่เข้าไปคิวไว้รันอีกทีเมื่อเว็บโหลดเสร็จแล้ว

ถ้าเราลองใส่ tag <script> ไว้ด้านบนหน้าแล้วสั่ง $("div") จะเห็นว่าผลลัพท์เป็น array เปล่า เพราะว่าเว็บยังโหลดถึงแค่ <script> ยังโหลดไม่ถึงจุดที่มีการวาง <div>

Selector string

อธิบายสั้นๆ คือเป็นตัวระบุว่าต้องการหา tag (element) อะไรในหน้านั้น ซึ่งจะเหมือนกับใน CSS แต่มีเพิ่มเติมมาอีก ตัวอย่างเช่น $("div .active span") จะเจอ

<div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div class="active"><span>จะเจอตรง tag นี้</span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
</div>

คร่าวๆ คือ ใส่ชื่อ tag ไว้ เว้นวรรคเพื่อหาข้างใน tag นั้นต่อ และมีตัว .name เพื่อหา class และ #id เพื่อหา id

(class, id เป็น tag attribute คือใส่ไว้หลังชื่อ tag <div class="test" id="mydiv"> โดย class มีหลายอันได้ <div class="a b c d e"> และมีซ้ำกันใน 1 หน้าได้ ส่วน ID ทั้งหน้าต้องมีอันเดียว ห้ามซ้ำ)

ถ้าเป็นไปได้ควรจะหาด้วย ID อย่างเดียวเพื่อประสิทธิภาพสูงสุด

<div>
    <p>ยอดเงินคงเหลือคือ <span id="amount">0</span> บาท</p>
</div>
$("#amount").text("50");

กรณีที่ selector ตรงกับหลาย tag การใช้คำสั่งส่วนมากจะมีผลต่อทุก tag ที่ตรงตามเงื่อนไข

Tag

เราสามารถสร้าง tag ใหม่เข้าไปในหน้าได้ด้วย โดยเขียน HTML ลงไปใน $

$("<div class='active'></div>").appendTo("p");

ผลลัพท์ของ $("tag") มีผลเหมือนการใส่ selector string (หัวข้อบน) คือ สามารถใช้คำสั่งพวก .text() .css() ฯลฯ ได้หมด

เมื่อสร้างแล้วจะเอาไปใส่ในหน้า สามารถใช้ได้ 4 ฟังก์ชั่น โดยทุกอันรับอากิวเมนต์เป็น selector string (แบบหัวข้อบน)

  • prependTo: ใส่ไว้ ข้างใน แรกสุด ของ selector นั้น
  • appendTo: ใส่ไว้ ข้างใน หลังสุด ของ selector นั้น
  • insertBefore: ใส่ไว้ ก่อน selector นั้น
  • insertBefore: ใส่ไว้ หลัง selector นั้น

กรณี selector นั้นตรงกับหลาย tag จะมีผลต่ออันที่มีลำดับแรกสุดในหน้า

Element

(หัวข้อนี้ยาก ข้ามไปเถอะ)

บางกรณีเราอาจจะใช้ library อื่นๆ แล้วได้ HTMLElement มาตรงๆ เช่น ใช้คำสั่ง document.getElementById("amount") ออกมา ถ้าต้องการใช้คำสั่ง jQuery กับ element นั้นๆ ให้เอา $() ครอบชื่อตัวแปร

กรณี $(document) ก็เป็นการใช้งานในลักษณะนี้

Global leak

การประกาศตัวแปรใน JavaScript จำเป็นต้องใส่ var เพื่อประกาศตัวแปรว่าอยู่ใน scope นี้ด้วย กรณีที่ใช้ตัวแปรลอยๆ โดยไม่ได้ใส่ var ผลคือ จะเกิด global leak

global leak คือการที่ตัวแปรนั้นๆ จะไปอยู่กับ scope นอกสุดในโปรแกรม (ใน browser คือตัวแปรชื่อ window ซึ่งจะเห็นว่า คำสั่งส่วนมากอยู่ในนี้ด้วย เช่นเราสามารถเรียก window.console.log)

ผลคืออาจจะทำให้โปรแกรมทำงานผิดพลาด เช่น

["a","b","c"].forEach(function(i){
    x = i; // x ไม่ได้ใส่ var
    window[x] = function(){
        return x;
    };
});

ถ้าเราเรียกฟังก์ชั่น window.a() b() c() ผลที่ได้จะพบว่าได้ "c" ทั้งหมด เพราะมันเอา x ที่ถูกเขียนทับในลูปรอบต่อๆ ไปมาตอบ

แต่ถ้าเราเติม var

["a","b","c"].forEach(function(i){
    var x = i;
    window[x] = function(){
        return x;
    };
});

จะเห็นว่าคำตอบจะเป็น "a" "b" "c" ถูกต้อง เพราะตัวแปร x มี scope แค่ใน forEach แต่ละรอบ