ดำดิ่งสู่ Hoisting ใน JavaScript

Mar 27 2023
Hoisting เป็นคำที่ใช้ใน JavaScript เพื่ออธิบายการประมวลผลของตัวแปรและการประกาศฟังก์ชันระหว่างขั้นตอนการคอมไพล์ของโค้ด ซึ่งเกิดขึ้นก่อนที่โค้ดจะถูกดำเนินการ ก่อนที่จะเริ่มต้น มาดูที่โค้ดนี้กันก่อน

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

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

console.log(tempStr);
// Outputs: undefined
var tempStr = “Hello World!!”;
console.log(tempStr);
// Outputs: “Hello World!”

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

ใน JavaScript การยกจะส่งผลต่อวงจรชีวิตของตัวแปร ซึ่งเกี่ยวข้องกับสามขั้นตอน:

  1. การประกาศ — สร้างตัวแปรใหม่โดยใช้คีย์เวิร์ด เช่น ให้ เช่นให้ myValue
  2. Initialisation — การกำหนดค่าเริ่มต้นให้กับตัวแปร เช่น myValue = 150
  3. การใช้งาน — การเข้าถึงและการใช้ค่าตัวแปร เช่น alert(myValue)

แม้ว่าฟีเจอร์นี้จะมีประโยชน์ในบางกรณี แต่ก็อาจทำให้เกิดความสับสนได้หากไม่เข้าใจอย่างถูกต้อง

เราจะพูดถึงการยกสามประเภทในโพสต์บล็อกนี้:

  1. การยกตัวแปร
  2. ยกฟังก์ชั่น
  3. ยกชั้น

เริ่มกันเลย!

การยกตัวแปร

เมื่อใดก็ตามที่ตัวแปรใน JavaScript ถูกประกาศด้วยคีย์เวิร์ด “var” ตัวแปรนั้นจะถูกย้ายไปยังจุดเริ่มต้นของขอบเขตโดยอัตโนมัติ ซึ่งอาจเป็นขอบเขตร่วมหรือขอบเขตของฟังก์ชันก็ได้

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

ตัวอย่างเช่น พิจารณารหัสต่อไปนี้:

console.log(myVariable); // undefined
var myVariable = 10;

สถานการณ์หนึ่งที่อาจเป็นกับดักทั่วไปสำหรับนักพัฒนา JavaScript ใหม่คือเมื่อพวกเขาใช้ชื่อตัวแปรเดียวกันทั้งภายในและภายนอก

ใช้ตัวอย่างต่อไปนี้:

var name = "Lalit";

(() => {
console.log(My name is ${name}); // Outputs: "My name is undefined"
var name = "Ankit";
console.log(`New name is ${name}`); // Outputs: "My name is Ankit"
})();

ดังนั้น สิ่งสำคัญคือต้องจำไว้ว่าการยกขึ้นนั้นใช้ได้กับการประกาศเท่านั้น ไม่ใช่การเริ่มต้น

ES6 ซึ่งเป็นเวอร์ชันล่าสุดของ JavaScript นำเสนอคำหลักใหม่สองคำ ได้แก่ “let” และ “const” ซึ่งมีตัวเลือกการกำหนดขอบเขตที่ดีกว่าคำหลัก “var” แบบเก่า ไม่เหมือนกับตัวแปรที่ประกาศด้วย "var" ตัวแปรที่ประกาศด้วย "let" และ "const" นั้นอยู่ในขอบเขตบล็อก หมายความว่าตัวแปรเหล่านั้นมีอยู่ในบล็อกของโค้ดที่มีการประกาศเท่านั้น และไม่อยู่ในขอบเขตภายนอก

อย่างไรก็ตาม ความแตกต่างที่สำคัญประการหนึ่งระหว่าง “var” และ “let”/”const” คือวิธีการทำงานของการยก

ตัวแปร “let” และ “const” จะไม่ถูกยกขึ้นไปไว้ด้านบนสุดของขอบเขต และการเข้าถึงก่อนที่จะประกาศจะส่งผลให้เกิดข้อผิดพลาดในการอ้างอิง เนื่องจากอยู่ในTemporal Dead Zone (TDZ)ซึ่งเป็นขอบเขตระหว่างจุดเริ่มต้นของบล็อกและบรรทัดการเริ่มต้นของตัวแปร

การใช้ “let” และ “const” แทน “var” สามารถช่วยหลีกเลี่ยงจุดบกพร่องที่อาจเกิดขึ้นในโค้ดของคุณ เนื่องจากบังคับให้คุณประกาศและเริ่มต้นตัวแปรของคุณก่อนที่จะใช้งาน สิ่งนี้ทำให้รหัสของคุณเข้าใจง่ายขึ้นและเกิดความสับสนน้อยลงสำหรับผู้อื่นที่อาจอ่าน

ดังนั้นจึงเป็นความคิดที่ดีที่จะใช้ “let” และ “const” ในโค้ดของคุณ เนื่องจากจะช่วยตรวจจับข้อบกพร่องได้ตั้งแต่เนิ่นๆ

ตัวอย่าง:

console.log(myVariable); // ReferenceError: myVariable is not defined
let myVariable = 10;

ยกฟังก์ชั่น

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

ตัวอย่าง:

foo(); // "Hello, world!"

function foo() {
console.log("Hello, world!");
}

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

ตัวอย่าง:

sayHiHoisted(); // Hi

function sayHiHoisted() {
console.log('Hi');
};

sayHiNotHoisted(); //Output: "TypeError: sayHi is not a function

var sayHiNotHoisted = function() {
console.log('Hi');
};

ขั้นแรก ฟังก์ชัน sayHiHoisted() ถูกเรียกก่อนที่จะประกาศในโค้ด แต่เนื่องจากการประกาศฟังก์ชันถูกยกขึ้นไปไว้ด้านบนสุดของขอบเขต ฟังก์ชันทั้งหมดจึงถูกย้ายไปที่ด้านบนสุดของขอบเขต ทำให้เรียกใช้ได้ ดังนั้นผลลัพธ์คือ "สวัสดี"

อย่างไรก็ตาม เมื่อเราพยายามเรียกใช้ฟังก์ชัน sayHiNotHoisted() ซึ่งกำหนดโดยใช้นิพจน์ของฟังก์ชัน เราได้รับ "TypeError" นี่เป็นเพราะเฉพาะการประกาศตัวแปรเท่านั้นที่ยกขึ้นไปไว้ด้านบนสุดของขอบเขต ไม่ใช่นิยามของฟังก์ชัน ดังนั้นตัวแปรจึงไม่ได้ถูกกำหนดในขณะที่เรียกใช้ฟังก์ชัน

คุณอาจสงสัยว่าจะเกิดอะไรขึ้นเมื่อใช้นิพจน์ฟังก์ชันที่มีชื่อ

funcName(); // ReferenceError: funcName is not defined
varName(); // TypeError: undefined is not a function

var varName = function funcName() {
console.log("Definition not hoisted!");
};

ลำดับความสำคัญ:

ใน JavaScript มีกฎที่กำหนดลำดับความสำคัญเมื่อมีการประกาศตัวแปรและฟังก์ชัน กฎเหล่านี้อาจส่งผลต่อผลลัพธ์ของโค้ดของคุณ และสิ่งสำคัญคือต้องทำความเข้าใจกฎเหล่านี้

  1. การกำหนดตัวแปรจะมีความสำคัญเหนือการประกาศฟังก์ชัน ซึ่งหมายความว่าหากมีการกำหนดค่าตัวแปรและต่อมามีการประกาศฟังก์ชันที่มีชื่อเดียวกัน การกำหนดตัวแปรจะมีผลเหนือกว่าและเขียนทับฟังก์ชัน
  2. การประกาศตัวแปรมาหลังการประกาศฟังก์ชันตามลำดับความสำคัญ เมื่อมีการประกาศฟังก์ชันและตัวแปรที่มีชื่อเดียวกันในขอบเขตเดียวกัน ฟังก์ชันจะมีความสำคัญเหนือกว่า และตัวแปรจะถูกละเว้น
  3. การประกาศฟังก์ชันจะอยู่เหนือการประกาศตัวแปร แต่ไม่เกินการกำหนดตัวแปร ซึ่งหมายความว่าการประกาศฟังก์ชันจะถูกย้ายไปที่ด้านบนสุดของขอบเขต แต่การกำหนดตัวแปรจะไม่เป็นเช่นนั้น ดังนั้น ถ้าตัวแปรถูกกำหนดค่าก่อนการประกาศฟังก์ชัน ฟังก์ชันจะยังคงถูกยกขึ้น แต่การกำหนดค่าตัวแปรจะมีความสำคัญกว่า

ตัวอย่างที่ 1: การกำหนดตัวแปรผ่านการประกาศฟังก์ชัน

var square = 10;

function square(num) {
return (num*num);
}
console.log(typeof square); // Output: number

ตัวอย่างที่ 2: การประกาศฟังก์ชันเหนือการประกาศตัวแปร

var square;

function square(num) {
return (num*num);
}
console.log(typeof square); // Output: function

สิ่งสำคัญคือต้องคำนึงถึงกฎเหล่านี้เมื่อเขียนโค้ด JavaScript เพื่อหลีกเลี่ยงผลลัพธ์ที่ไม่คาดคิด

ยกชั้น

คลาส JavaScript สามารถแบ่งได้เป็นสองคลาส:

  • การประกาศคลาส
  • การแสดงออกของชั้นเรียน

var carOne = new car();
carOne.height = 5;
carOne.weight = 500;

console.log(carOne); // Output: ReferenceError: car is not defined

class car {
  constructor(height, weight) {
  this.height = height;
  this.weight = weight;
  }
}

class car {
  constructor(height, weight) {
  this.height = height;
  this.weight = weight;
  }
}
var carOne = new car();
carOne.height = 5;
carOne.weight = 500;
console.log(carOne); // Output: car {height: 5, weight: 500}

var rectangle = new shapes();
rectangle.height = 10;
rectangle.width = 20;
console.log(rectangle); // Output: TypeError: shapes is not a constructor

var shapes = class {
  constructor(height, width) {
  this.height = height;
  this.width = width;
  }
};

​​var shapes = class {
  constructor(height, width) {
  this.height = height;
  this.width = width;
  }
};
var rectangle = new shapes();
rectangle.height = 10;
rectangle.width = 20;
console.log(rectangle); // Output: shapes {height: 10, width: 20}

การชักรอกไม่ว่าจะสนุกแค่ไหนก็อาจส่งผลให้เกิดจุดบกพร่องได้เนื่องจากมองข้ามได้ง่าย

  1. เพื่อหลีกเลี่ยงข้อบกพร่อง ให้ประกาศตัวแปรทั้งหมดที่จุดเริ่มต้นของทุกขอบเขตเสมอ เนื่องจากนี่คือวิธีที่ Javascript ตีความรหัส จึงเป็นกฎที่ดีเสมอ
  2. ใช้โหมดเข้มงวดของ JavaScript โดยใช้คำสั่ง "ใช้เข้มงวด" ที่ด้านบน โหมดเข้มงวดของ JavaScript ไม่อนุญาตให้มีตัวแปรที่ไม่ได้ประกาศ
  3. ใช้วิธีการใหม่ในการกำหนด การยกไม่ได้เกิดขึ้นกับ let หรือ const การใช้มันจะเพิ่มโอกาสที่โค้ดของคุณจะปลอดบั๊ก (มาก)

ขอบคุณสำหรับการอ่านและมีความสุขในการเขียนโค้ด!