Có thể từ trước tới nay, chúng ta thường được nghe về việc javascript sẽ di chuyển (nâng) các khai báo của biến cũng như function lên trên cùng của phạm vi hiện tại mà nó đang thực thi. Hay chúng ta còn gọi cơ chế này là "hoisting" trong javascript.
Hoisting is JavaScript's default behavior of moving declarations to the top. - Định nghĩa trên w3schools.
Nhưng thực tế nó có phải là vậy không? Hôm nay chúng ta cùng tìm hiểu thông qua bài viết này.
Những thông tin và ví dụ trong bài viết được lấy ra từ cuốn sách You don't know JS yet của tác giả Kyle Simpson đây là 1 cuốn sách rất hay mà các lập trình viên đang sử dụng Javascript chắc chắn phải đọc để hiểu sâu hơn về ngôn ngữ mà họ đang sử dụng.
Để hiểu hơn cách hoạt động phía sau của Javascript chúng ta cần biết rằng Javascript có 2 giai đoạn là biên dịch và thực thi.
Trong giai đoạn biên dịch Javascript khai báo các biến nhờ các Scope Manager để giai đoạn thực thi có thể sử dụng các biến đó. Quá trình đó có thể được mô tả thông qua đoạn hội thoại sau của 3 nhân vật Trần Văn Lơ (Compiler), Lê Thị Gơ (Scope Manager), Nguyễn Văn Gin (Engine) như sau:
var animals = ['🐕️', '🐒', '🐪'] function showAnimals() { console.log(animals) var animals = ['🐷', '🐔', '🐴']
} showAnimals()
// undefined
1. Giai đoạn biên dịch
Lơ: *Bắt đầu đọc dòng code đầu tiên*
Lơ: Uhmm, Không biết chị Gơ có biết thằng nào tên là animals không nhỉ? (var animals = ['🐕️', '🐒', '🐪'])
Gơ: Ơ, nó là ai nhỉ tôi không biết. Để tui khai báo rồi lưu nó lại để xíu nữa anh Gin ảnh dùng cho lẹ (không mấy thằng Dev nó chửi là ngôn ngữ gì mà chậm như ...)
Lơ: Ừ thế chị tạo giùm cho tui đi
Lơ: *Anh Lơ tiếp tục đọc tiếp*
Lơ: À chị có biết thằng nào tên là showAnimals không chị Gơ
Gơ: Tôi cũng không biết luôn. Để tui khai báo nó cho anh nghen
Gơ: *Khai báo showAnimals*
Gơ: À mà nó là function nên tui tạo luôn cho nó 1 cái scope mới
Lơ: *Anh Lơ nhảy vào scope mới do chị Lơ mới tạo cho showAnimals và gặp chị Nguyễn Thị Na Gơ chứ không còn là Lê Thị Gơ nữa (vì mỗi scope có 1 Scope Manager khác nhau quản lý)*
Lơ: Na Gơ có biết thằng nào tên animals không? (var animals = ['🐷', '🐔', '🐴'] thuộc phạm vi của showAnimals chứ không phải Global)
Na Gơ: Không, tui không biết nó. Để tui khai báo nó trong scope của tui
Na Gơ: *Khai báo animals trong scope của showAnimals*
Kết thúc biên dịch
2. Giai đoạn thực thi
Gin: *Đọc dòng code đầu tiên*
Gin: Biết thằng animals không nhỉ? (Rất là bố láo bố toét)
Lơ: Dạ vâng anh, tui biết nó tui có khai báo nó cho anh trong quá trình biên dịch rồi, đây của anh dây anh Gin
Gin: Ok cảm ơn chị, bây giờ tui sẽ gán giá trị ['🐕️', '🐒', '🐪'] cho nó
Gin: Chị có biết thằng nào tên showAnimals không chị Lơ?
Lơ: Có đây anh tui cũng tạo nó luôn rồi
Gin: Ok vậy để tui thực thi nó
Gin: *Thực thi showAnimals, nhảy vào trong scope của nó và gặp chị Nguyễn Thị Na Gơ*
Gin: *Không nói không rằng đọc ngay dòng code đầu tiên trong function showAnimals*
Gin: *Nghĩ thầm trong đầu. Không biết thằng nào viết code ngu thế nhỉ*
Gin: Uhmn console.log(animals) à, không biết chị Na Gơ có biết thằng này không ta? ("Hoisting" mà chúng ta thường được biết là ở đây)
Na Gơ: Dạ có nó đây anh nhưng mà nó được khởi tạo mặc định là undefined đó nha anh Gin
Gin: Không sao, thằng cu đơ nó code ngu thì nó chết thôi chị, kệ nó đi...
Gin: Đưa biến animals có giá trị là undefined từ chị Na Gơ cho console.log và kết quả thì như ta đã được thấy
Gin: *Đọc tiếp xuống dòng code tiếp theo trong showAnimals*
Gin: *Tiếp tục hỏi chị Na Gơ về biến animals, sau đó gán giá trị ['🐷', '🐔', '🐴'] cho nó và thoát khỏi showAnimals*
Kết thúc chương trình
Như vậy, chúng ta có thể thấy "Hoisting" thật ra chỉ là 1 cách ẩn dụ để đơn giản hóa sự phức tạp phía sau của Javascript, để các Dev có thể tưởng tượng rằng nó sẽ "viết lại" code của chúng ta từ:
function showAnimals() { console.log(animals) var animals = ['🐷', '🐔', '🐴']
}
sang:
function showAnimals() { var animals console.log(animals) animals = ['🐷', '🐔', '🐴']
}
nhưng thực tế không có quá trình nào giống như thế cả.
Compiler sẽ đọc qua code trước khi thực thi và yêu cầu các Scope Manager khởi tạo các biến và để sẵn ở trong scope của chính nó quản lý.
Nhờ vậy mà khi Engine trong quá trình chạy chỉ cần lấy ra dùng chứ không cần phải tốn sức hay thời gian để tạo lại các biến đó.
Đây là bài viết đầu tiên của mình nên có gì sai sót thì mong các bạn có thể thông cảm bỏ qua và mình rất mong nhận được các đóng góp ý kiến để có thể cải thiện bài viết hiện tại cũng như sau này. Mình xin cảm ơn các bạn đã bỏ thời gian đọc.