1. Hoisting trong javascript là gì?

Khi bạn thực thi một đoạn mã javascript thì trình biên dịch sẽ tạo ra một bối cảnh thực thi chung, ta gọi là global execution context.

Bối cảnh thực thi này sẽ có hai giai đoạn, thứ nhất là tạo và thứ hai là thực thi.

Trong giai đoạn tạo thì javascript sẽ di chuyển các khai báo biến lên đầu của đoạn mã, giúp cho chương trình không bị lỗi khi sử dụng biến trước khi khai báo. Tính năng này ta gọi là tính năng lưu trữ trong javascript, và trong tiếng Anh gọi là Hoisting.

2. Biến hoisting trong javascript

Trong Javascript, bạn có thể định nghĩa một biến sau khi sử dụng nhờ tính năng hoisting.

Javascript sẽ di chuyển toàn bộ các khai báo biến lên đầu chương trình. Vì vậy, những dòng code có sử dụng biến mà chưa khai báo sẽ không bị lỗi.

1
2
3
4
5
6
7
8
9
10
11
// Gán nhưng chưa khai báo biến
domain = 'freetuts.net';
 
// Kết quả: Domain là freetuts.net
document.write("Domain là: " + domain);
 
// Khai báo
var domain;
 
// Kết quả: Domain là freetuts.net
document.write("<br/> Domain là: " + domain);

Nếu trong lúc khởi tạo mà bạn gán giá trị cho biến thì kết quả sẽ khác

1
2
3
4
5
6
7
8
9
10
11
// Gán nhưng chưa khai báo biến
domain = 'freetuts.net';
 
// Kêt quả: Domain là freetuts.net
document.write("Domain là: " + domain);
 
// Khai báo
var domain = 'techtuts.net';
 
// kêt quả: Domain là freetuts.net
document.write("<br/> Domain là: " + domain);

Nếu khai báo biến trước khi sử dụng thì ta vẫn có kết quả giống nhau.

1
2
3
4
5
6
7
8
9
10
11
// Khai báo
var domain;
 
// Gán nhưng chưa khai báo biến
domain = 'freetuts.net';
 
// Kết quả: Domain là freetuts.net
document.write("Domain là: " + domain);
 
// Kết quả: Domain là freetuts.net
document.write("<br/> Domain là: " + domain);

Trong ví dụ thứ 3 này thì ta đã khai báo biến trước rồi mới sử dụng sau. Điều này tuân thủ theo quy tắc tạo biến rồi mới sử dụng của hầu hết các ngôn ngữ lập trình. Nó giúp chương trình trở nên sáng và logic hơn.

3. Từ khóa let và var trong hoisting

Việc sử dung từ khóa let và var để khai báo một biến sẽ có sự khác biệt trong hoisting.

Sử dụng var: Nếu biến chưa gán giá trị thì sẽ trả về undefined.

1
2
console.log(counter); // undefined
var counter = 1;

Đoạn mã trên sẽ tương đương với đoạn mã dưới đây.

1
2
3
4
var counter;
 
console.log(counter); // undefined
counter = 1;

Sử dụng let: Nếu biến chưa gán giá trị thì sẽ xuất hiện thông báo lỗi Cannot access before initialization.

1
2
console.log(counter);
let counter = 1;

Kết quả:

1
"ReferenceError: Cannot access 'counter' before initialization

Lỗi này muốn nói rằng biến counter đã được định nghĩa trong bộ nhớ heap nhưng chưa được khởi tạo.

4. Hàm hoisting trong javascript

Cũng giống như các biến, công cụ JavaScript cũng lưu trữ các khai báo hàm. Nó sẽ di chuyển các khai báo hàm lên đầu của chương trình.

1
2
3
4
5
6
7
8
9
let x = 20,
    y = 10;
 
let result = add(x,y);
console.log(result);
 
function add(a, b){
    return a + b;
}

Trong ví dụ này thì mình đã sử dụng hàm add rồi mới khai báo sau. Tuy nhiên, javascript vẫn không trả về một lỗi nào cả. Lý do là cơ chế hoisting của javascript đã di chuyển các khai báo hàm lên đầu rồi mới thực thi chương trình.

Có nghĩa là đoạn code dưới đây sẽ tương đương:

1
2
3
4
5
6
7
8
9
function add(a, b){
    return a + b;
}
 
let x = 20,
    y = 10;
 
let result = add(x,y);
console.log(result);

5. Hoisting trong biểu thức hàm

Biểu thức hàm hay còn gọi là function expressions. Nó là một hàm được khai báo bằng cách gán vào một biến.

1
2
3
var add = function(x, y) {
    return x + y;
}

Hàm add chính là một function expressions.

Quay trở lại bài toán. Câu hỏi là chuyện gì sẽ xảy ra nếu ta sử dụng một function expressions trước rồi mới khai báo sau? Hãy xem ví dụ dưới đây.

1
2
3
4
5
6
7
8
9
let x = 20,
    y = 10;
 
let result = add(x,y);
console.log(result);
 
var add = function(x, y) {
    return x + y;
}

Chạy đoạn code này thì kết quả sẽ xuất hiện lỗi:

1
"TypeError: add is not a function

Lý do rất đơn giản. Javascript sẽ xem biết add là một biến thông thường, nên khi nó đưa lên đầu thì sẽ là một biến chứ không phải là một hàm. Vì vậy, khi sử dụng sẽ xuất hiện lỗi add is not a function.

6. Hoisting trong arrow function

Cú pháp tạo arrow function sẽ như sau:

1
var function_name = () => bieuthuc;

Chính vì vậy, nếu bạn sử dụng arrow function trước rồi mới khai báo thì sẽ bị lỗi function_name không phải là một hàm.

7. Lời kết

Hoisting nghe có vẻ kì lạ và hay, nhưng riêng cá nhân mình thấy hoisting không thực sự tốt. Bởi theo sự logic của các ngôn ngữ lập trình là bạn phải sử dụng một biến trước khi khai báo chúng. Và Hoisting trong javascript đã phá vỡ đi quy tắc đó.