+8

Sự thật về "hoisting" trong Javascript mà có thể bạn chưa biết

Từ lâu, chúng ta đã quen thuộc với khái niệm JavaScript tự động di chuyển (hay nâng) các khai báo biến và hàm lên đầu phạm vi hiện tại khi thực thi. Cơ chế này thường được gọi là "hoisting" trong JavaScript.

Hoisting is JavaScript's default behavior of moving declarations to the top. - Định nghĩa trên w3schools.

Tuy nhiên, liệu điều này có chính xác như chúng ta nghĩ không? Hãy cùng tìm hiểu sâu hơn về cơ chế này trong bài viết dưới đây.

Thông tin và ví dụ trong bài viết này được trích dẫn từ cuốn sách You Don't Know JS Yet của tác giả Kyle Simpson, một tác phẩm nổi bật mà mọi lập trình viên JavaScript đều nên đọc để hiểu rõ hơn về ngôn ngữ mình đang sử dụng.

Để nắm rõ cách hoạt động của JavaScript, chúng ta cần hiểu hai giai đoạn chính: biên dịchthực thi.

Trong giai đoạn biên dịch, JavaScript sẽ khai báo các biến thông qua Scope Manager để các biến này có thể được sử dụng trong giai đoạn thực thi. Quá trình này có thể được mô tả qua một đoạn hội thoại hài hước giữa ba nhân vật: Scope Manager, Engine, và Compiler.

var animals = ['🐕️', '🐒', '🐪']

function showAnimals() {
    console.log(animals)
    var animals = ['🐷', '🐔', '🐴']
}

showAnimals()
// undefined

1. Giai đoạn biên dịch

Compiler: *Bắt đầu đọc dòng code đầu tiên*

Compiler: Tại thời điểm này, hệ thống nhận thấy một biến tên là animals (var animals = ['🐕️', '🐒', '🐪']).

Scope Manager: Tôi chưa gặp biến này trước đây. Để tôi khai báo nó để sẵn sàng cho quá trình thực thi.

Compiler: Cảm ơn. Hãy tạo biến này nhé.

Compiler: *Tiếp tục đọc code*

Compiler: Còn hàm showAnimals thì sao?

Scope Manager: Tôi cũng chưa gặp hàm này. Để tôi khai báo và thiết lập một scope mới cho nó.

Scope Manager: *Khai báo hàm showAnimals và tạo một scope mới cho nó*

Compiler: *Chuyển sang scope mới của showAnimals và gặp Scope Manager trong phạm vi của hàm này*

Compiler: Scope Manager có biết biến animals trong phạm vi của showAnimals không? (var animals = ['🐷', '🐔', '🐴'])

Scope Manager: Không, tôi chưa thấy biến đó. Để tôi khai báo nó trong scope của mình.

Scope Manager: *Khai báo biến animals trong scope của showAnimals*

Kết thúc giai đoạn biên dịch

2. Giai đoạn thực thi

Engine: *Bắt đầu thực thi mã nguồn*

Engine: Biến animals hiện tại có giá trị gì? (Vẫn chưa được gán giá trị)

Compiler: Tôi đã khai báo biến đó trong giai đoạn biên dịch rồi. Đây là thông tin cho bạn.

Engine: Cảm ơn. Tôi sẽ gán giá trị ['🐕️', '🐒', '🐪'] cho biến này.

Engine: Scope Manager, bạn có biết hàm showAnimals không?

Scope Manager: Có, tôi đã khai báo hàm đó.

Engine: Vậy tôi sẽ thực thi hàm này.

Engine: *Thực thi showAnimals, vào trong scope của hàm và gặp Scope Manager trong phạm vi của hàm này*

Engine: *Đọc dòng code đầu tiên trong showAnimals*

Engine: À, console.log(animals). Không biết biến này có giá trị gì không? ("Hoisting" mà chúng ta thường biết xuất hiện ở đây)

Scope Manager: Có, biến này có giá trị mặc định là undefined.

Engine: Không sao, điều này không ảnh hưởng nhiều. Tôi sẽ tiếp tục.

Engine: Gán giá trị ['🐷', '🐔', '🐴'] cho biến animals và kết thúc việc thực thi hàm*

Kết thúc chương trình

Như vậy, khái niệm "hoisting" thực ra chỉ là một cách đơn giản hóa sự phức tạp của JavaScript, giúp các lập trình viên hình dung rằng mã nguồn của chúng ta được "viết lại" từ:

function showAnimals() {
    console.log(animals)
    var animals = ['🐷', '🐔', '🐴']
}

sang:

function showAnimals() {
    var animals
    console.log(animals)
    animals = ['🐷', '🐔', '🐴']
}

Tuy nhiên, thực tế không có quá trình nào như vậy. Compiler sẽ đọc qua mã nguồn trước khi thực thi và yêu cầu các Scope Manager khởi tạo các biến, lưu trữ chúng trong phạm vi của mình để Engine chỉ việc sử dụng mà không cần tốn thời gian tái tạo các biến.

Đây là bài viết đầu tiên của tôi, nếu có gì thiếu sót, mong các bạn thông cảm và đóng góp ý kiến để tôi có thể cải thiện trong những bài viết sau. Cảm ơn các bạn đã dành thời gian đọc.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí