ต้องบอกก่อนว่าผมไม่ใช้ Guru ด้าน Javascript นะครับ เป็นเพียงแค่มือใหม่ที่เพิ่งหัดศึกษาเท่านั้น ส่วนมากก็จะตามเทรนด์ไปเรื่อยๆ เน้นอ่าน Documentation แล้วเอามาประยุกต์ใช้กับงาน เช่น Node.js, Meteor.js, Sails.js, Express.js, Angular.js, Ember.js, jQuery, Backbone, etc. อีกเยอะแยะเลย มีอะไรใหม่ๆ ผมก็ชอบไปลองเล่นไปเรื่อยๆ จนตอนหลังรู้สึกว่าหากเราไม่มีพวก Library พวกนี้ เราจะเขียน Javascript เองได้หรือเปล่านะ ? (รู้สึกเหมือนตัวเองเป็นแค่ User ที่อ่านเอกสารคู่มือเท่านั้น) ตอนหลังก็เลยเริ่มอ่านบทความที่เป็น Javascript จริงๆจังๆ แต่ก็อ่านเฉพาะ Javascript จนถึงวันนี้ ได้อ่านเรื่อง Variable Hoisting แล้วรู้สึกมันทำให้ผมสับสนดียิ่งนัก ก็เลยถือโอกาสนี้เขียนเป็นบล็อคสรุปสิ่งที่เข้าใจไว้ เผื่อไว้อ่านในอนาคต

Hoisting คืออะไร ?

ก่อนที่จะขยายนิยามคำว่า Hoist หรือ Hoisting ใน Javascript ว่ามันคืออะไร ลองดูโค๊ดด้านล่างนี้ก่อน

for (var i = 1; i < 10; i++) {
    // ...
}
console.log(i);

จากโค๊ดด้านบนหากให้ลองตอบคำถาม ว่า console.log(i) จะได้คำตอบเป็นอะไร เชื่อว่าหลายๆคนต้องตอบว่า undefined หรือบางคนอาจจะตอบว่า error แน่นอนซึ่งคำตอบทั้งหมดนั้น ผิดครับ (ผมเห็นทีแรกก่อนลองเทส ผมก็ตอบว่า undefined :))

จริงๆแล้วคำตอบของมันคือ 10 ไม่เชื่อเทสกับ Browser ดู แล้วทำไมเป็นยังงั้นละ?

มันคือส่วนหนึ่งของ Hoisting ไงละครับ Hoisting ก็คือ การเลื่อนหรือย้ายตัวแปรไปไว้ที่ส่วนบนสุดของสโคป/ฟังค์ชัน

อ่านมาถึงตรงนี้บางคนอาจจะงงหรือยังไม่เห็นภาพ ผมก็ยังงง อ่านต่อครับ :)

Global Scope กับ Local Scope

Global Scope คือตัวแปรที่สามารถเข้าถึงได้ทุกที่ตราบใดที๋โปรแกรมยังคงทำงานอยู่ ประกาศตัวแปร ทำได้โดยไม่ต้องมี var เช่น

name = 'Chai';

ส่วน Local Scope คือตัวแปรที่ทำงานอยู่ภายใน scope ของมันเช่น ภายใน Loop ภายใน Function (, [ หรือ [ เช่น

var name = 'Chai';

โดยถ้าหากว่าเราทำการอ้างถึงตัวแปร Local นอก Scope ก็จะทำให้เกิด Error เช่นโค๊ดด้านล่างนี้

function sayHello() {
	var name = 'Chai';
}

console.log(name); // ReferenceError: name is not defined

แต่ว่าการกำหนดตัวแปรด้วยการใส่คีย์เวิร์ด var ก็เป็นตัวแปร Global ได้เช่นกันนะครับ ถ้ากำหนดไว้ที่นอกสุดของ scope เช่น

var name = 'Chai';

function sayHello() {
	console.log(name);
}

sayHello();  // Chai

ภายในฟังค์ชั่น sayHello() ทั้งที่ไม่ได้ประกาศตัวแปร Local ใดๆไว้เลย แต่สามารถเข้าถึงตัวแปร name ได้ ก็เพราะว่าตัวแปร name เป็น Global Variable นั่นเอง

เอ๊แล้วแบบนี้ โค๊ดด้านล่างนี้ ตัวแปร name ใช่ Global Variable หรือเปล่า?

function sayHello() {

  for (var i = 1; i < 5; i++) {
      var name = 'Chai';
  }
 
  console.log(name); 
}
 
sayHello(); // Name

ตอบเลยว่า ไม่ใช่ครับ ตัวแปร name ด้านบนเป็น Local Variable

แต่ทำไม สามารถที่จะเข้าถึง name ได้ทั้งๆที่มันเป็นตัวแปร Local อยู่ภายใน For Loop เท่านั้น ? คำตอบคือ เพราะ hoists ครับ

ทำให้ตัวแปร name ไปอยู่บนสุดของ Scope อยู่ภายในฟังค์ชั่น sayHello ก่อน For Loop ทีนี้เวลา console.log(name) ก็เลยทำให้สามารถพิมพ์ค่าออกมาได้นั่นเอง

Variable Declaration vs Variable Assigment

ใน Javascript การประกาศตัวแปร จะทำทั้งหมด 2 ขั้นตอนครับคือ ขั้นแรก

  1. Declaration : คือการประกาศชื่อตัวแปร เช่น var name;
  2. Assignment : คือขั้นตอนการกำหนดค่า เช่น name = 'Chai';
var name = 'Chai';

โค๊ดด้านบนคือ Declare ก่อนและทำการ Assign ค่า

ทีนี้ไอ้เจ้า Declaration และ Assignment มันเกี่ยวอะไรกับ Hoists ละ ก็เพราะว่าจุดสำคัญของ Hoists เลยคือมันเคลื่อนย้ายตัวแปร ไปยังจุดบนสุดของ Scope แต่มีข้อแม้ว่า ย้ายเฉพาะตัวแปรในขั้น Variable Declaration เท่านั้น

จากโค๊ดเดิม

function sayHello() {

  for (var i = 1; i < 5; i++) {
      var name = 'Chai';
  }
 
  console.log(name); 
}
 
sayHello(); // Name

เมื่อมัน Hoists ตัวแปร name (Declaration) ก็จะย้ายไปอยู่บนสุดของสโคป ส่วนขั้นตอน Assignment จะไม่ถูกย้ายไปด้วย เพราะมันไม่ใช่ Hoists จะกลายเป็น

function sayHello() {
  var name;  // Declaration
  for (var i = 1; i < 5; i++) {
      name = 'Chai';
  }
 
  console.log(name); 
}
 
sayHello(); // Name

สรุป

สรุปประเด็นจากคำถามที่ผมจั่วหัวไว้ครับ ว่าทำไมถึงได้ 10 คงหายสงสัยกันแล้วนะครับ

for (var i = 1; i < 10; i++) {
    // ...
}
console.log(i);

ถึงได้คำตอบ 10 ก็เพราะว่า ตรง for loop for (var i = 1; i <= 10; i++) ได้ประกาศตัวแปร i (declaration) ฉะนั้นตัวแปร i ก็จะถูก hoisting คือการย้ายไปส่วนบนสุด ก็เลยกลายเป็น

var i;
for (i = 1; i < 10; i++) {
    // ...
}
console.log(i);

หลังจากนั้นก็วนลูป console.log(i) ก็เลยมีค่าเท่ากับ 10 ด้วยประกาศฉะนี้

ฉะนั้นจึงสรุปได้ว่า เมื่อใดก็ตามที่เราเริ่มทำการประกาศตัวแปร (Declaration Variable) ก็จะเกิดการเคลื่อนย้ายตัวแปร ไปยังด้านบนสุดของสโคปทันที เราเรียกสิ่งนี้ว่า Hoists

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

References :