09 กันยายน, 2556

jQuery in 30 days - ตอนที่ 2 not so fast

อ่านบทอื่นๆ ในซีรีย์นี้ได้ที่ jQuery in 30 days 

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

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

หลักการใช้ javascript ควมคุม html

เว็บทำงานยังไงของมัน?

การเขียน code ต้องมีเป้าหมาย .. เป้าหมายหลักของ javascript คือการทำให้ html แห้งๆ ที่ทำอะไรไม่ได้เลยมันมี behavior ตอบสนองกับผู้ใช้ได้ ซึ่งหน้าที่นี้เป็นของ code ที่เรียกกันว่า Client-Side-Script หรือ script ที่ทำงานฝั่ง Client
แสดงขึ้นตอนการทำงานเมื่อเราเรียกดูเว็บ

จากรูปแสดงการทำงานของ Web นั่นคือเริ่มด้วยการที่ผู้ใช้แบบเราๆ นี่แหละ พิมพ์ url เช่น www.google.com เข้าไปใน browser จากนั้นก็นั่งตีพุงรอหน้าเว็บโหลดเสร็จ (ด้วยความเร็วเน็ตประเทศไทยก็ไม่ใช่ว่าจะได้เร็วนัก)
แต่เบื้องหลังก่อนที่จะเอาหน้าเว็บมาแสดงได้นั้นคือ..
  • Browser ของเรา เช่น FireFox หรือ Chrome ได้ส่งสิ่งที่เรียกว่า request ผ่านเครือข่ายอินเตอร์เน็ตจนไปเจอเครื่องเซิร์ฟเวอร์ปลายทางในที่สุด (ส่วนวิธีนั้นไม่ขอพูดละกัน มันยาว 55)
  • เมื่อเซอร์ฟเวอร์ได้รับคำขอ มันก็จะประมวลผลแล้วสร้างหน้าเว็บเพจสำหรับเราขึ้นมา จะด้วยการใช้ Algorithm แบบไหน ดึงข้อมูลจาก database แบบไหนก็แล้วแต่แหละ แต่สิ่งที่ทำมันก็คือ code ที่เรียกว่า Server-Side-Script เช่น php
  • สิ่งที่ได้ออกมาส่งใหญ่จะเป็น html + css + js ซึ่งเป็นโครงสร้างหลักของเว็บเพจ
    - html ทำหน้าที่เห็น Structure
    - css เป็น style เพิ่มความสวยงามและสีสันให้หน้าเว็บ
    - js หรือ javascript เป็น behavior ทำให้เว็บตอบโต้กับผู้ใช้ได้
  • โดยในส่วนนี้ js จะยังไม่ถูกเรียกให้ทำงานเพราะชื่อมันก็บอกอยู่แล้วว่ามันเป็น Client-Side-Script หรือ script ที่จะรันในฝั่ง Client (ฝั่งผู้ใช้) เท่านั้น
  • เมื่อก้อน html + css + js ถูกส่งกลับมาถึงฝั่งผู้่ใช้ ก็จะเป็นหน้าที่ของ Browser ที่จะทำการ "Render" หรือขึ้นรูปร่างจากส่งที่เป็น code คนทั่วไปอ่านไม่ได้ อี๊..หยะแหยง ให้กลายเป็นสิ่งสวยงาม เป็นหน้าเว็บที่เห็นแล้วสบายลูกตาหน่อย ฟิน~
ซึ่งพื้นที่ฝั่ง client นี่แหละที่เป็นอาณาจักรในครอบครองของ javascript เพราะมันเกือบจะคุม+ทำได้ทุกอย่าง!!

คุยกันก่อนว่าเราใช้ javascript เพื่ออะไร

จากหัวข้อที่แล้ว เราคุยกันไปว่าข้อมูลที่เซิร์ฟเวอร์ตอบกลับมานั้นมี
  • html
  • css
  • js
ซึ่ง 2 อย่างแรกนั้นมันทำได้แค่อยู่เฉยๆ โต้ตอบกับผู้ใช้ไม่ได้ .. การจะเขียน js ก็เพื่อคุมเจ้าสองอย่างที่ว่านั่นแหละ
และเมื่อจะคุมเจ้าพวกนั้นได้ ก่อนอื่นก่อนต้องหาให้ได้ก่อนว่า html หรือ css ที่เราจะสั่งงานให้มันอยู่ตรงไหน ซึ่งคำสั่งที่ปกติเราจะคุ้นเคยกันดีก็คือ
document.getElementsBy...('wrapper');
คำสั่งตระกูล get element ซึ่งบอกว่าเรากำลังยุ่งกับ html tags อันนี้อยู่นะ (ส่วนจะเอาไปใช้ทำไรต่อก็เรื่องของคุณ) นี่เป็นสเต็ปแรกเวลาเราเขียน js เสมอๆ
และเมื่อมันเป็นสิ่งที่เราทำบ่อยที่สุด ดังนั้น jQuery (กว่าจะกลับเข้ามาเรื่องนี้ ร่ายไปยาวมาก) จึงเริ่มต้องการเขียนด้วยสิ่งที่เรียกว่า Selector เช่นที่เกริ่นไปนิดหน่อยในบทแรก
$('li')             //เลือก element ทุกตัวที่เป็นแท็ก li
$('.momo')          //เลือก element ทุกตัวที่มี class momo อยู่
$('#momo')          //เลือก element ตัวที่มี id เป็น momo
$('li.momo')        //เลือก element li ทุกตัวที่เป็น class momo
$('ul > li.momo ')  //เลือก element li ทุกตัวที่เป็น class momo ซึ่งเป็น node ลูกของ ul
$('ul  li.momo ')   //เลือก element li ทุกตัวที่เป็น class momo ซึ่งอยู่ใน node ของ ul
                 
คำสั่งพวกนี้ถูกเอามาใช้แทน
  • document.getElementById()
  • document.getElementsByName()
  • document.getElementsByClassName()
  • document.getElementsByTagName()
ที่มันใช้ค่อนข้างยาก ส่วนจะ get แบบไหนเราจะพูดกันต่อไปในบทต่อไปเรื่อง Selector นะจ๊ะ :P

Variable in .js

เรื่องของตัวแปรใน javascript

เรื่องของตัวแปรหรือ variable นั้นเป็นสิ่งที่มีในทุกภาษา สำหรับ javascript แล้วตัวแปรที่เราใช้กันเป็นประเภท dynamic นั่นคือตัวแปรเราแค่ประกาศ var มันก็สามารถเก็บค่าได้ทุกอย่าง (หรือไม่ประกาศ อยู่ๆ เรียกใช้เลยก็ยังได้!) โดย type ของมันจะเปลี่ยนไปตามค่าที่มันเก็บอยู่ เช่นเก็บ 5 ดังนั้น type ก็จะเป็น number ประมาณนั้น
การประกาศ function ก็ง่ายๆ เช่นกันตามตัวอย่างข้างล่างนี่นะ จะมี return หรือไม่ก็ตามใจท่าน
*หมายเหตุ string ของ javascript ไม่แยกประเภท char vs. string และใช้ได้ทั้ง '..' และ ".." ตามสบายใจคนเขียนเลย มีค่าเท่ากัน ไม่เหมือน PHP หรือ Java ที่ single-quote กับ double-quote ใช้ต่างกัน
var x = 5;
var name = "momo";
var language = 'javascript';
var prog = true;

function echo(sth){
    alert(sth);
}    
                 
แต่รู้ไหมว่า... javascript สามารถเก็บตัวแปรได้มากกว่าแค่ ตัวเลข ตัวอักษร หรือ ค่า ถูก-ผิด ?
var arr = new Array('a', 'b', 'c', 'd');

//หรือรูปย่อ
var arr = ['a', 'b', 'c', 'd']; 
สำหรับ Array นั้นอาจจะไม่ใช่เรื่องแปลกสำหรับคนที่เคยเขียนโปรแกรมมาก่อน แต่อาจจะแลดูง่ายขึ้น (เอ๊ะ?) สำหรับคนที่เขียนโปรแกรมตระกูล C/Java ที่จะเป็นต้องกำหนดค่าที่ตายตัวให้ Array ตั้งแต่สร้างมัน เพราะ javascript อนุญาตให้เราประกาศ Array แบบไม่ต้องบอกขนาดได้
ภาษาพวก dynamic type ส่วนใหญ่จะไม่ค่อยซีเรียสเรื่องชนิดตัวแปร และ ขนาดที่ตายตัวอยู่แล้ว
ตัวอย่างภาษาที่เป็นแบบนี้ก็เช่น php

แต่ยังมีชนิดข้อมูลอีก 2 อย่างที่ใช้กันเยอะ และเป็นกระโยชน์มากนั่นคือ object และ function
var obj = {
    name: "momo",
    id: 1234
};

var myfunc = function(sth){
    alert(sth);
}  
    
นี่มันอะไรกัน O_O ... ตัวแปร 2 ชนิดนี้เป็นสิ่งที่ค่อนข้างจะเข้าใจยากสำหรับมือใหม่ที่ยังไม่เคยเขียนโปรแกรมแบบนี้มาก่อน
  • Object: เป็นการบอกว่าตัวแปรตัวนี้ มาค่าอีกหลายๆ ค่า pack เป็นกลุ่มอยู่ข้างในตัวมัน (คล้ายๆ ตัวแปรประเภท struct ในภาษา C ซึ่งจะใช้รูปแบบของ JSON ในการเก็บ .. แล้วมันคือ~อัลไล ?
  • Function: จากเดิมปกติที่เราประกาศ function แบบมาตราฐานและมาเจอตัวนี้ เขาตั้งชื่อให้มันเท่ๆ ว่า Anonymous Function .. แล้วมันคือ~อัลไล ?

variable Object-type

เป็นการเต็มตัวแปรเป็นเซ็ต คล้ายๆ กับ struct ในภาษาซี หรือ class ในจาวา โดยมีฟอร์มการเขียนในลักษณะนี้
{[key:value[,key:value]]}
ตัวอย่างเช่น
var profile = {
    name: 'nartra',
    block: 'nartra.blogspot.com',
    number: 19,
    programmingSkill: ['php', 'js', 'java', 'c/c++'],
    working: true
}
ส่วนการ access เข้าไปใช้ properties (หรือก็คือตัวย่อยที่เก็บเอาไว้) แต่ละตัวก็จะใช้ . ในการเรียกคล้ายๆ ภาษาอื่น เช่น
alert(profile.name);
profile.number = 10;
ซึ่งการดรียกใช้นั้นได้ทั้ง get และ set

function

ความจริง JavaScript นั้นสามารถประกาศ function ได้อย่างง่ายๆ ด้วยรูปแบบ
function myFunction(){
   ...
}
ได้อยู่แล้ว แต่ภาษานี้ยังอนุญาตให้เก็บ function เป็นตัวแปรแทนได้ด้วย
var f1 = function myFunction(){
   ...
}
เรียกฟังก์ชั่นที่สร้างขึ้นมา (มีชื่อด้วยตัวของมันเองคือ myFunction อยู่) ว่า f1 นะ เวลาเรียกใช้ก็ใช้ได้ทั้ง myFunction(); หรือ f1(); 
อ่าว แล้วมั้นต่างกันอย่างไรล่ะ?
ก่อนจะพูดเรื่องนั้นขอยกตัวอย่างการ bind event ให้ปุ่มก่อนละกัน 
ป.ล.การ bind event หมายความว่าเราใช้ JavaScript เพื่อบอกว่า element ของ html ตัวนี้ เมื่อโดน$%&*จะให้มันทำอะไร
document.getElementById("button").onclick = "myFunction";
function myFunction(){
   ...
}
การสั่งแบบนี้หมายความ element ที่มี id=button เมื่อโดน "คลิก" ให้ทำงานตามที่ ฟังก์ชั่นชื่อ myFunction เขียนเอาไว้
แต่การเขียนแบบนี้มันเวิ่นเว้อเกินไป โปรแกรมเมอร์มักจะไม่ชอบกัน เพราะอะไรน่ะเหรอ ส่วนใหญ่ฟังก์ชั่นที่เตรียมไว้เพื่อบอกว่าเมื่อปุ่มนี้โดนกดจะให้ทำอะไรนั้นมันมักจะทำงานแค่กรณีเดียว (อย่างเช่นตัวอย่างบน myFunction จะเตรียมไว้สำหรับ event "onclick" แทบจะอย่างเดียว ) ฟังก์ชั่นประเภทนี้เราเรียกมันว่า callback ความหมายก็คือ ฟังก์ชั่นที่เตรียมไว้ก่อน แต่ยังไม่ทำงานทันที จะทำงานก็ต่อเมื่อมีเงื่อนไขที่วางไว้เกินขึ้นมาเท่านั้น ซึ่งส่วนใหญ่ callback จะไม่ค่อยถูกเรียกใช้จากใครคนอื่นอีกแล้ว
document.getElementById("button").onclick = function myFunction(){
   ...
}
;
JavaScript เลยอนุญาตให้เราเขียนเหลือแค่นี้ได้ สังเกตว่าเอาฟังก์ชั่นทั้งตัวไปแทนที่เลย ... แต่ถ้าแค่นี้ยังย่อไม่พอ JavaScript จะใช้รูปแบบต่อไปนี้ได้ คือในเมื่อชื่อฟังก์ชั่น myFunction นั้นน่ะมันไม่จำเป็นต้องมีก็ได้ (ไม่ต้องมีเพราะมันใช้แค่ตรงนี้ที่เดียวเท่านั้น ประกาศชื่อไปยังไงก็ไม่ได้เรียกใช้อยู่ดี และอาจจะไปชนกับฟังก์ชั่นอื่นที่ชื่อซ้ำกันในหน้านี้ก็เป็นได้ อย่างนี้ไม่ประกาศชื่อไปเลยดีกว่า)
document.getElementById("button").onclick = function(){
   ...
}
;
ก็จะเหลือแค่นี้ เราเรียกฟังก์ชั่นแบบนี้ว่า Anonymous Function หรือฟังก์ชั่นนิรนาม (ส่วนใหญ่จะเป็นฟังก์ชั่นชนิด callback แทนจะทั้งหมด)
เอาล่ะ กลับมาที่เดิม
var f1 = function(){
   ...
}

f1();
ในเมื่อเราสั่งแบบที่พูดง่ายมาได้ ทำไมเราจะจับฟังก์ชั่นยัดใส่ตัวแปรไม่ได้ล่ะ แต่การประกาศใช้ได้ 2 วิธีแน่นอนว่ามันต้องมีข้อดีข้อเสีย
เรามายกตัวอย่างข้อดีกันก่อน
var f1 = function(){
    alert("function 1");
};
var f2 = function(){
    alert("function 2");
};

function runIt(callback){
    callback();
}

runIt(f1);
runIt(f2);
ข้อดีที่สุดของการเก็บฟังก์ชั่นไว้เป็นตัวแปรคือเราสามารถใช้มันในสิ่งที่ตัวแปรทำได้ทั้งหมด เช่นการส่งค่าผ่าน parameter ของ function เป็นต้น
แต่มีข้อดี มันก็ต้องมีข้อเสีย ไม่งั้นเขาก็คงใช้รูปแบบนี้กันหมดแล้ว
f1(); //พัง เรียกใช้ไม่ได้
f2();

f1 = function(){
    alert("function 1");
};
function f2(){
    alert("function 2");
};
ในกรณีนี้เราจะไม่สามารถเรียกใช้ f1 ได้ เพราะค่า f1 เพิ่งจะมีการประกาศ (declare) ข้างล่าง ต่างกับ f2 ที่ตัว JavaScript จะมองออกว่าตัวนี้เป็นฟังก์ชั่นเรียกใช้ได้นะ แม้ส่วนที่ declare ฟังก์ชั่นจะอยู่ข้างล่างส่วนที่เรียกใช้ก็ตาม

ready

เริ่มทำงานเมื่อหน้าเพจ "พร้อม" แล้วเท่านั้น!

สมมุติว่าเรามี ul อยู่ตัวหนึ่ง แล้วต้องการจะสั่งให้ li ใน ul ตัวนั้นกลายเป็นสีแดงเราก็จัดการสั่ง jQuery ให้ทำงานแบบนี้ (จริงๆ ไม่ใช่เคย jQuery ที่เป็นนะ javascript ปกติก็เป็นเหมือนกันล่ะ)
<html>
<script type="text/javascript" src="jquery.js"></script>
<script>
    $('ul > li').css('color','#f00');
</script>

<ul>
<li>โมโมหมายเลข1</li>
<li>โมโมหมายเลข2</li>
<li>โมโมหมายเลข3</li>
</ul>
</html>    
                
จาก code ชุดนี้เราก็คิดว่าผลที่ได้ควรจะออกมาเป็นแบบนี้
  • โมโมหมายเลข1
  • โมโมหมายเลข2
  • โมโมหมายเลข3
แต่เมื่อเอาไปรันแล้วผลที่ออกมา li ทั้ง 3 ตัวกลับไม่กลายเป็นสีแดงอย่างที่สั่งไป แต่ดันออกมาแบบนี้
  • โมโมหมายเลข1
  • โมโมหมายเลข2
  • โมโมหมายเลข3
นั่นเป็นเพราะ คำสั่ง javascript จะเริ่มทำงานเลยเมื่อ browser อ่านไปถึงส่วน code js ของมัน ซึ่งในตอนนั้น ... พวก node ของ li ยังโหลดไม่เสร็จ เลยทำให้ jQuery ไม่สามารถหา node พวกนั้นเจอ

วิธีแก้คือสั่งให้ ชุดคำสั่งนั้นทำงานเมื่อ หน้าเพจโหลดเสร็จแล้ว
 
เราต้องการจะสั่งให้ jQuery ทำงานหลังจาก document ของเราโหลดเสร็จแล้ว เราจึง select ตัว document มาแล้วเรียกใช้คำสั่ง .ready()
<html>
<script type="text/javascript" src="jquery.js"></script>
<script>
    $(document).ready(function(){
        $('ul > li').css('color','#f00');       
    });
</script>

<ul>
<li>โมโมหมายเลข1</li>
<li>โมโมหมายเลข2</li>
<li>โมโมหมายเลข3</li>
</ul>
</html>     
               
parameter ของคำสั่ง .ready() จะรับไปเป็น function ซึ่งจะรันเมื่อหน้าเพจทั้งหน้าโหลดเสร็จแล้ว พอสั่งแบบนี้ jQuery จะหา li เจอเนื่องจาก HTML DOM ของทั้งหน้านี้สร้างเสร็จเรียบร้อยแล้ว
การสั่ง $(document).ready(function(){...}); สามารถเขียนย่อได้เป็น
    $(function(){
        $('ul > li').css('color','#f00');       
    });   
                 
ซึ่งตัว jQuery จะมีการเช็กเองว่าถ้าสิ่งที่ parse เข้ามาให้เป็น function แล้วจะมีการสั่งให้ทำงานแบบ .ready() ไม่ใช่เป็น selector

.load

เพราะแค่ "พร้อม" ยังไม่พอ ต้องรอโหลดทุกอย่างเสร็จด้วย

แต่ก็ยังมีอีกคำสั่งที่ทำงานคล้ายๆ กัน นั่นคือ .load() ซึ่งจะใช้กับ selector ของ window
<html>
<script type="text/javascript" src="jquery.js"></script>
<script>
    $(window).load(function(){
        $('ul > li').css('color','#f00');       
    });
</script>

<ul>
<li>โมโมหมายเลข1</li>
<li>โมโมหมายเลข2</li>
<li>โมโมหมายเลข3</li>
</ul>
</html>    
                
เขียนเหมือน .ready เลย!?
ผลออกมาก็จะเหมือนกับ document.readyด้วย แต่ข้อแตกต่างกันคือ...
  • $(document).ready() ทำงานเมื่อ content ของไฟล์ html โหลดครบและสร้าง DOM เรียบร้อยแล้ว (แต่รูปโหลดเสร็จหรือยัง cssเรนเดอร์หน้าเพจแล้วรึเปล้านั้นมันไม่สน)
  • $(window).load() ทำงานเมื่อ content ของไฟล์ html โหลดเสร็จและสร้าง DOM เรียบร้อยแล้ว และมีการโหลด img css iframe ทุกอย่างเสร็จแล้ว
พูดง่ายๆ คือ window.load จะทำงานหลัง document.ready ซึ่งขอแค่โหลด html srouce ของหน้าเว็บเสร็จก็ถือว่าได้แล้ว

Self-invoking functions

ฟังก์ชั่นนิรนาม ... ไม่ระบุชื่อ ... เอาไว้กำหนดของเขต

บางครั้งเราจะมีการสร้าง function และเรียกใช้เลย เช่น
var the_momo = 'โมโม';
function momo(){
    alert(the_momo);
}
momo();    
                
ซึ่งรูปแบบข้างบนจะมีการประกาศให้ the_momo และ momo() จะถูกมองเป็นตัวแปรแบบ global ...การเขียนแบบนี้อาจจะมีปัญหาได้เพราะว่า ในกรณีที่เป็นโปรเจคใหญ่ๆ ชื่อตัวแปรอาจจะตีกันได้ ยิ่งถ้ามีการ includeไฟล์เยอะๆ แล้วด้วยจะยิ่งเกิดได้บ่อย ตัวอย่างเช่น
//ไฟล์ .js อันนี้ถูก include เข้ามาในโปรเจคก่อน
var the_momo = 'โมโมของคนอื่น';
function momo(){
    alert('งงเลย');
}

...

//หลังจากเขียนโปรเจคไปเรื่อยๆ เราก็สร้างตัวแปรพวกนี้ขึ้นมาโดยไม่รู้เลยว่ามีการสร้างไปแล้ว
var the_momo = 'โมโม';
function momo(){
    alert(the_momo);
}
momo();             
      
วิธีแก้คือทำให้ตัวแปรของเรากลายเป็นแบบ local ซะด้วย Self-invoking functions
(function(){
    var the_momo = 'โมโม';
    function momo(){
        alert(the_momo);
    }
    momo();
})(); 
                   
ตัว Self-invoking functions นี้ก็คือการเรียกใช้ function แบบไม่ต้องประกาศชื่อ
จากรูปแบบ func_name(); กลายเป็น (...)();
สรุปคือมันเป็นคล้ายๆ กับ function ใช้ครั้งเดียเพื่อบอกว่า code ชุดตรงนี้น่ะ ทำงานแค่กลุ่มนี้ของมันพอนะ อย่าเอาไปตีกับ code ชุดอื่นที่อยู่ในหน้านี้แล้วดันชื่อเหมือนกัน

4 ความคิดเห็น:

  1. ขอแชร์ให้รุ่นน้องที่มหาลัยอ่านะครับ

    ตอบลบ
  2. สุดยอดครับ ขอบคุณมากๆครับ

    ตอบลบ
  3. ขอบคุณมากครับได้ความรู้ที่มีประโยชน์เยอะมากครับ

    ตอบลบ
  4. ไม่ระบุชื่อ21 ตุลาคม 2561 เวลา 20:34

    เขียนอ่านสนุกดีมากครับ ไม่มีต่อแล้วหราครับ เสียดาย

    ตอบลบ