Tìm hiểu về Javascript Hoisting
Bài đăng này đã không được cập nhật trong 8 năm
Hoisting
là một khái niệm khá hay trong JavaScript
tuy nhiên lại ít được mọi người để ý do nó có đôi chút phức tạp cũng như mọi người ít quan tâm đến cách thức hoạt động bên trong ngôn ngữ lập trình.
Trong bài viết này mình, mình sẽ chỉ ra khái niệm cũng như cách thức hoạt động bên trong. Do kiến thức còn hạn chế nhiều nên sẽ có nhiều lỗi, mong các bạn góp ý để mình hoàn thiện kiến thức.
Một số khái niệm cần nắm
JavaScript has function-level scope
Hãy xem đoạn code bằng C sau
#include <stdio.h>
int main() {
int x = 1;
printf("%d, ", x); // 1
if (1) {
int x = 2;
printf("%d, ", x); // 2
}
printf("%d\n", x); // 1
}
Kết quả nhận được lần lượt là 1, 2, 1 bới vì họ hàng nhà C có thuộc tính block-level scope
, vì thế trong đoạn lệnh if
, một scope mới được tạo ra, biến x
được tạo ra trong scope mới mà không ảnh hưởng tới biến x
nằm ở scope bên ngoài.
Tuy nhiên, với JavaScript
:
var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2
Kết quả nhận được lần lượt là 1, 2, 2 bởi vì JavaScript
chỉ có function-level scope
(hay không có block-level scope
), trong đoạn lệnh if
sẽ không được tạo scope mới mà sẽ dùng chung với scope bên ngoài, khiến cho biến x bị thay đổi.
JavaScript Compilation
Được xử lý bởi JavaScript engine, bao gồm 3 bước :
- Tokenizing/Lexing
- Parsing
- Code-Generation
Cụ thể ở bước này mình sẽ viết trong bài riêng sắp tới.
Trong bước này, bạn có thể hiểu thông qua ví dụ :
var foo = "bar";
function bar() {
var foo = "bar";
}
- Yêu cầu
Global
scope khai báo biến có tên làfoo
. - Yêu cầu
Global
scope khai báo hàm có tên làbar
. - Yêu cầu
bar
scope khai báo biến có tên làfoo
.
JavaScript Execution
Có 2 khái niệm cần biến ở bước này, đó là :
LHS
: Left hand side.RHS
: Right hand side.
Hiểu đơn giản, gọi là LHS reference
nếu như ta có biến nằm ở bên trái ký tự gán =
và ngược lại với RHS reference
(Nếu không có LHS reference
thì ta coi đó là RHS reference
). Một cách khác phân biệt LHS
và RHS
là RHS is target, RHS is source
.
Ta có thể xem cuộc đối thoại vui giữa Engine
và Scope
khi thực hiện đoạn code var foo = "bar";
như sau :
Engine
: HeyGlobal
scope, I have an LHS reference for a variable named foo. Ever heard of it?Scope
: The global scope has because foo was registered on line 1 in the compilation phase, so the assignment occurs.
JavaScript Compilation
Ở trên bước Execution
ta có nhắc tới Compilation
, đây cũng chính là bước sinh ra khái niệm Hoisting
ta cần tìm hiểu.
Về cơ bản, Hosting
là thao tác chuyển tất cả các khai báo biến lên trên cùng của hàm. Xem qua ví dụ dưới đây để hiểu rõ :
Code được viết :
console.log(a);
var a = 123;
console.log(a);
Mặc dù console.log(a);
được viết trước khi a
được khai báo nhưng ta không hề bị ReferenceError
. Lý do chính là Hoisting
, đoạn code sẽ trở thành như sau trong quá trình Compilation
:
var a;
console.log(a);
a = 123;
console.log(a);
Nên nhớ, Hoisting
chỉ chuyển việc khai báo các biến lên trên cùng, còn các phép gán vẫn giữ nguyên vị trí. Vậy nên ta thấy đầu ra của đoạn code trên lần lượt là undefined
và 123
.
Chú ý rằng việc khai báo hàm và biến luôn luôn sử dụng Hoisting
, hơn nữa JavaScript
không có block-level scope
khiến cho khái niệm Hoisting
trở nên nguy hiểm nếu ta không hiểu rõ và khái báo bừa bãi. Đây là một ví dụ :
var foo = 10;
function bar() {
if (!foo) {
var foo = 100;
}
console.log(foo);
}
bar();
Bạn thử đoán xem kết quả hiện ra là bao nhiêu ? Là 100
! Tại sao câu lệnh trong câu điều kiện if lại được thực hiện? Rõ ràng foo = 10 => !foo == false
mà.
Như ta đã biết, các khai báo biến sẽ được đưa lên trên cùng và JavaScript
không phải block-level scope
, vì thế sẽ xảy ra hoisting
cho var foo = 100;
trong scope tạo bởi function bar()
nên xảy ra việc giá trị của foo
khi thực hiện if (!foo)
sẽ là undefined
thay vì 10, vậy nên !foo == true
và foo = 100
được thực hiện, kết quả in ra ngoài sẽ là 100
.
Đây chỉ là một ví dụ vô cùng đơn giản để cho chúng ta thấy sự nguy hiểm của Hoisting
.
Kết luận
Thực tế khi dự án lớn với việc khai báo hàm và biến vô cùng nhiều, việc xuất hiện bug là không khó xảy ra. Những bug dạng này lại càng khó để phát hiện và xử lý, vì vậy ta nên hiểu rõ các khái niệm, luồng xử lý cũng như luôn khai báo hàm/biến ở trên cùng thay vì để chúng nó tự Hoisting
.
Tài liệu tham khảo
You don't know JS
.http://www.adequatelygood.com
All rights reserved