Hướng dẫn hoisting javascript la gì

- "Hoisting" là hành động mặc định của JavaScript, nó sẽ di chuyển những câu lệnh khai báo tên biến lên vị trí đầu tiên trong phạm vi (lên đầu tập tin hiện tại hoặc lên đầu hàm hiện tại)

- Chúng ta có một đoạn mã như sau:

Xem ví dụ

- JavaScript sẽ tự động chuyển các câu lệnh khai báo tên biến lên vị trí đầu tiên trong phạm vi.

- Do đó, đoạn mã phía trên sẽ có ý nghĩa tương tự như đoạn mã sau:

Xem ví dụ

- Lưu ý: "Hoisting" chỉ di chuyển phần khai báo tên biến lên vị trí đầu tiên trong phạm vi, còn phần gán giá trị thì không được di chuyển lên.

- Hai đoạn mã bên dưới có ý nghĩa khác nhau:

Đoạn mã 1Đoạn mã 2

Xem ví dụ

Xem ví dụ

- Đoãn mã 2 sẽ có ý nghĩa tương tự như đoạn mã sau:

Xem ví dụ

2) Công dụng của Hoisting

- Như chúng ta đã biết, nếu sử dụng một biến trong khi nó chưa được khai báo thì câu lệnh sử dụng biến đó sẽ bị lỗi.

- Hoisting giúp ta tránh tình trạng bị lỗi khi sử dụng những biến chưa được khai báo.

- Ví dụ:

Xem ví dụ

Xem ví dụ

- Ngoài ra, nếu đang ở trong một hàm mà ta gán giá trị cho biến chưa được khai báo thì biến đó sẽ trở thành biến toàn cục.

- Hoisting giúp ta tránh tình trạng tạo ra những biến toàn cục không mong muốn.

- Ví dụ:

Xem ví dụ

Xem ví dụ

Vậy tóm lại: Để đảm bảo an toàn, tránh tình trạng bị lỗi do sử dụng những biến chưa được khai báo hoặc vô tình tạo ra biến toàn cục không mong muốn, ta có thể khai báo lại một lần nữa các tên biến ở cuối phạm vi (cuối tập tin hoặc cuối hàm)

Hoisting là khái niệm chỉ việc mọi khai báo biến (với từ khóa var) sẽ được chuyển lên trên cùng của hàm.

Để kiểm tra, tôi thử ví dụ sau trong Chrome Developer Tools:

(function(){
	console.log(foo);
})();

>> ReferenceError: foo is not defined

Oops! Tất nhiên là nó sẽ ra lỗi ?!@#$% Hãy thử một ví dụ khác:

(function(){
	console.log(foo);
	var foo = "hello";
})();
>> undefined

‘undefined’ thay vì lỗi? Nguyên nhân là do việc khai báo biến foo đã được chuyển lên trên cùng. Đoạn code trên thực tế được chuyển thành như sau khi chạy:

(function(){
	var foo;
	console.log(foo);
	foo = "hello";
})();
>> undefined

Tất nhiên chỉ có khai báo biến được chuyển lên đầu hàm, còn các biểu thức gán sẽ vẫn nằm tại vị trí cũ. Đối với các trường hợp biến được khai báo trong các khối lệnh như if, for, while, switch,… cũng không ngoại lệ. Đó là kết quả (hoặc cũng có thể là nguyên nhân) của việc:

javascript không có block-level scope.

(function(){
	console.log(foo);
    if(false){
		var foo = "hello";
	}
})();
>> undefined
Function Declaration và Function Expression

Trước tiên ta cần phân biệt hai khái niệm này:

// Function Declaration:
function foo(){
    // code
}
// Function Expression:
var foo = function(){
	// code
};

Cả hai phương pháp tạo hàm này đều bị ảnh hưởng bởi “hoisting”, tuy nhiên có một điểm khác nhau quan trọng giữa chúng.
– Function Declaration: toàn bộ hàm sẽ được di chuyển lên đầu. Nghĩa là bạn có thể sử dụng được hàm trước khi nó được định nghĩa.
– Function Expression: chỉ có phần khai báo được di chuyển lên đầu.

Và …

Hướng dẫn hoisting javascript la gì

Để trả lời, bạn có thể xem và dự đoán đoạn code sau sẽ in gì ra màn hình:

(function(){
	var foo = 1;
	(function(){
		console.log(foo);
		var foo = 2;
	})();
	console.log(foo);
})();

Nếu chưa biết về hoisting, bạn có thể đưa ra kết quả sai. Câu trả lời chính xác là ‘undefined’. Nguyên nhân là do bạn khai báo lại (với từ khóa var) biến foo bên trong hàm con. Nếu bạn không khai báo lại, hàm con sẽ sử dụng biến foo của ngữ cảnh bên ngoài.

Một ví dụ khác với hàm:

(function(){
    console.log(foo()); // 3

    var foo = function() {
        return 1;
    };
    function foo() {
        return 2;
    };
    function foo() {
        return 3;
    };
})();
>> 3

Kết quả xuất ra có thể nằm ngoài dự đoán của bạn do hàm được định nghĩa cuối cùng sẽ ghi đè lên các hàm cùng tên được định nghĩa trước nó.

Hãy tưởng tượng bạn có một đoạn javascript rất dài, bạn khai báo biến tại những chỗ mà bạn bắt đầu dùng nó, bạn quên rằng tên biến này đã được dùng hoặc nhầm lẫn giữa các scope với nhau. Kết quả là nó nhảy ra một lỗi hoặc tệ hơn là nó … âm thầm chờ đợi và xuất hiện khi đưa cho khách hàng. Vậy một best practice mà bạn nên tập từ bây giờ là:

Trong bài này chúng ta sẽ tìm hiểu hoisting trong Javascript, qua đó sẽ giúp bạn hiểu khái niệm hosting là gì, và cơ chế hoạt động của hosting trong ngôn ngữ javascript.

Hướng dẫn hoisting javascript la gì

Hướng dẫn hoisting javascript la gì

Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Hoisting là vấn đề liên quan đến cách khai báo biến trong Javascript. Theo quy tắc chung của hầu hết các ngôn ngữ lập trình, để sử dụng một biến thì bạn phải khai báo nó trước. Nhưng javascript thì lại khác, có một số trường hợp bạn có thể sử dụng biến rồi mới khai báo sau. Lý do là gì thì chúng ta cùng tìm hiểu ngay nhé.

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.

Bài viết này được đăng tại [free tuts .net]

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.

// 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("
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

// 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("
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.

// 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("
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.

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

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

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.

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

Kết quả:

"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.

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:

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.

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.

// 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("
Domain là: " + domain);
0

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

// 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("
Domain là: " + domain);
1

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:

// 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("
Domain là: " + domain);
2

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

// 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("
Domain là: " + domain);
3 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 đó.