Hoisting trong Javascript

MVT
Đang cập nhật

Định nghĩa

Hoisting là cơ chế của Javascript cho phép các biến hoặc hàm được được dời lên đầu phạm vi của chúng trước khi thực thi đoạn code.

Điều này có nghĩa là dù hàm hoặc biến được khai báo ở bất cứ nơi đâu, khi có hoisting, chúng đều được gọi trước, kể cả biến toàn cục hay cục bộ.

Lưu ý: Là cơ chế này chỉ di chuyển phần khai báo mà thôi còn các phần khác giữ nguyên không đụng gì đến nó hết.

1. Hành vi của Hoisting

1.1 Đối với biến

hello = "Hello World"; //Gán chuỗi Hello World cho biến hello
console.log(hello) 
var hello; //khai báo biến hello

Kết qủa : javascript Hello World

Javascript chỉ lưu trữ các biến khai báo, không lưu các khởi tạo :

var hello = "Hello World";
console.log(hello + ", My name is: " + name);
var name = "John Doe";

Thử đoán xem kết quả sẽ là gì ? Hello World, My name is: John Doe ?

Nếu bạn đoán giống như trên thì... rất tiếc, nó không phải như vậy. Bởi vì khi chạy chương trình thì JS sẽ thực hiện khai báo biến chưa sử dụng và lưu trữ nó, nó không quan tâm đến giá trị của các biến đó là gì nên nó mặc định gán giá trị cho chúng là undefined

Để hiểu hoạt động của hoisting đối với biến thì hãy cùng tìm hiểu tiếp ví dụ:

function greeting(){
  console.log(message);
  var message = "Happy new year";
}
greeting()

Kết quả : javascript undefined

Tại sao ở phần định nghĩa, hoisting sẽ chuyển phần khai báo lên trên đầu phạm vi khi thực thi mà nó lại trả về undefined. Cùng phân tích lại nhé :

function greeting(){
  var message ; //khai báo biến message
  console.log(message);
  message = "Happy new year";
}
greeting()

Như bạn đã biết, khi khai báo biến mà không gán dữ liệu thì mặc định nó có giá trị là undefined.

Như vậy, khi thực thi chương trình : - Phần khai báo sẽ được chuyển lên đầu hàm greeting() - Nhưng phần giá trị sẽ không được chuyển nên khi in ra sẽ là giá trị mặc định undefined

Để tránh những trường hợp như vậy xuất hiện về sau thì bạn nên khai báo và khởi tạo cùng một lúc như sau:

function greeting(){
  var message = "happy New Year";
  console.log(message)
}
greeting()

1.2 Đối với hàm

Đối với hàm thì chúng ta biết là trong JavaScript có hai dạng hàm đó là khai báo hàm (function declarations)biểu thức hàm (function expression).

1.2.1. Khai báo hàm

Xét ví dụ sau :

//Gọi hàm trước khi khai báo
greeting()

// Khai báo hàm
function greeting(){
  var message = "happy New Year";
  console.log(message)
}

Cách viết trên tương tự với :

// Khai báo hàm
function greeting(){
  var message = "happy New Year";
  console.log(message)
}
//Gọi hàm sau khi khai báo
greeting()

Như vậy thì chúng ta có thể hiểu đơn giản là cũng như biến, hàm khai báo cũng sẽ được gọi trước khi chúng ta đăng ký hàm đó. Đó chính là cách thức họat động của hoisting.

1.2.2. Biểu thức hàm

Đối với biểu thức hàm ngược lại.

Vì biểu thức hàm bản chất là hàm được gán cho một biến. Do đó, nó cũng giống như vừa khai báo và vừa khởi tạo biến.

Thế nên, cơ chế hoisting không áp dụng cho biểu thức hàm.

greeting(); 

var greeting = function(){
  var message = "happy New Year";
  console.log(message)
}

Kết quả trình biên dịch JavaScript sẽ ném lỗi vào mặt bạn:

Uncaught TypeError: greeting is not a function

Đối với cách khai báo và sử dụng như dưới đây cũng không được chấp nhận:

//Gọi hàm
greetings(); 
greeting();
// Biểu thức hàm
var greetings = function greeting(){
  var message = "happy New Year";
  console.log(message)
}

2. So sánh thứ tự ưu tiên trong cơ chế hoisting

2.1. Gán biến ưu tiên hơn khai báo hàm

Trong cơ chế hoisting, phép gán biến nó sẽ có độ ưu tiên cao hơn khai báo hàm:

var greeting = "Happy new year";
function greeting(){
   document.write("This is greeting function");
}
//in kiểu của message
console.log(typeof greeting)

Kết quả là :

string

Điều này chứng to việc cố gắng ghi đè biến message bằng Khai báo hàm đã không thành công.

2.2. Biểu thức hàm ưu tiên hơn gán biến

Đầu tiên, khởi tạo biến greeting.

Sau đó, tạo thêm một biếu thức hàm cũng có tên greeting

var greeting = "Happy new year";
var greeting = function(){
   document.write("This is greeting function");
}
console.log(typeof greeting)

Kết quả là :

function

2.3. Khai báo hàm ưu tiên hơn khai báo biến

Trong cơ chế hoisting, nó phép khai báo hàm có độ ưu tiên cao hơn khai báo biến, ví dụ:

var greeting ; 
function greeting(){
  document.write("Happy new Year")
}
//in ra kiểu của message
console.log(typeof greeting);

Kết quả sẽ in ra là :

function

Điều này chứng tỏ khai báo hàm ưu tiên hơn là khai báo biến.

3. Cách giải quyết khi gặp hoisting

Hoisting khiến việc quản lý code JS trở nên khó khăn, và khó có thể phát hiện hơn khi chúng ta làm những dự án lớn, độ phức tạp cao. Để giải quyết vấn đề này bạn cần : - Hiểu rõ về nó (Cách này hơi hơi khó thì phải :v) - Để hạn chế hành vi hoisting trong JavaScript bạn chỉ cần bật chế độ use strict.

Sau khi ECMA 2015 (ES6) ra đời, để tránh hoisting, lập trình viên đỡ vất vả hơn rất nhiều bằng cách sử dụng từ khóa let hoặc const thay cho var. Nhưng nó không cho phép sử dụng cho đến khi được khai báo

Bởi vì, khi sử dụng biến let hoặc const trước khi nó được khai báo ngay lập tức dẫn đến một lỗi ReferenceError.

//cách khai báo đúng với let hoặc const
let a = 1 ;
const b = 2 ; 
console.log(a)
console.log(b);
//khai báo không đúng cách
console.log(c)
let c = 3 ; //Reference error

Lời kết

Qua bài viết này hi vọng bạn sẽ nắm được khái niệm, cách thức hoạt động của hoisting trong JavaScript. Đây là một vấn đề rất quan trọng, hiểu và nắm giữ nó thì bạn mới biết thực sự JavaScript hoạt động như thế nào.

Việc nắm bắt hoisting trong JS cũng khá khó. Nhưng hãy luyện tập, THỬ - SAI để ngộ ra dần dần bạn nhé.

Hẹn gặp lại bạn trong những bài viết tiếp theo.


Bài viết có liên quan