รู้จักกับ Variable Hoisting ใน Javascript กันดีกว่า

Published on
JavaScript
2014/12/javascript-variable-hoisting
Discord

ต้องบอกก่อนว่าผมไม่ใช้ 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 :

Buy Me A Coffee
Authors
Discord