HomeLập trìnhJavaScriptHoisting trong JavaScript...

Hoisting trong JavaScript là gì?


Trong JavaScript, cẩu cho phép bạn sử dụng các hàm và biến trước khi chúng được khai báo. Trong bài đăng này, chúng ta sẽ tìm hiểu cẩu là gì và nó hoạt động như thế nào.

Vận thăng là gì?

Hãy xem đoạn mã dưới đây và đoán xem điều gì sẽ xảy ra khi nó chạy:

console.log(foo);
var foo = 'foo';

Nó có thể làm bạn ngạc nhiên khi mã này xuất ra undefined và không thất bại hoặc gây ra lỗi – mặc dù foo được chỉ định sau khi chúng tôi console.log nó!

Điều này là do trình thông dịch JavaScript phân chia phần khai báo và gán các hàm và biến: nó “nâng” các khai báo của bạn lên đầu phạm vi chứa của chúng trước khi thực thi.

Quá trình này được gọi là cẩu, và nó cho phép chúng ta sử dụng foo trước khi khai báo trong ví dụ của chúng tôi ở trên.

Chúng ta hãy tìm hiểu sâu hơn về hàm và biến nâng để hiểu điều này có nghĩa là gì và cách thức hoạt động của nó.

Nâng biến trong JavaScript

Như một lời nhắc nhở, chúng tôi tuyên bố một biến với var, letconst các câu lệnh. Ví dụ:

var foo;
let bar;

chúng tôi giao phó một biến một giá trị sử dụng toán tử gán:

// Declaration
var foo;
let bar;

// Assignment
foo = 'foo';
bar="bar";

Trong nhiều trường hợp, chúng ta có thể kết hợp khai báo và gán thành một bước:

var foo = 'foo';
let bar="bar";
const baz = 'baz';

Tính năng nâng biến hoạt động khác nhau tùy thuộc vào cách biến được khai báo. Hãy bắt đầu bằng cách tìm hiểu hành vi đối với var biến.

cẩu biến với var

Khi trình thông dịch kéo một biến được khai báo với varnó khởi tạo giá trị của nó thành undefined. Dòng mã đầu tiên bên dưới sẽ xuất ra undefined:

console.log(foo); // undefined

var foo = 'bar';

console.log(foo); // "bar"

Như chúng ta đã định nghĩa trước đó, hoisting xuất phát từ việc khai báo và gán biến phân tách của trình thông dịch. Chúng ta có thể đạt được hành vi tương tự này theo cách thủ công bằng cách chia khai báo và gán thành hai bước:

var foo;

console.log(foo); // undefined

foo = 'foo';

console.log(foo); // "foo"

Hãy nhớ rằng lần đầu tiên console.log(foo) đầu ra undefined bởi vì foo được nâng lên và đưa ra một giá trị mặc định (không phải vì biến không bao giờ được khai báo). Sử dụng một biến không khai báo sẽ ném một ReferenceError thay thế:

console.log(foo); // Uncaught ReferenceError: foo is not defined

Sử dụng một biến không được khai báo trước khi gán nó cũng sẽ ném một ReferenceError vì không có khai báo nào được nâng lên:

console.log(foo); // Uncaught ReferenceError: foo is not defined
foo = 'foo';      // Assigning a variable that's not declared is valid

Đến bây giờ, bạn có thể đang nghĩ, “Huh, thật kỳ lạ khi JavaScript cho phép chúng ta truy cập các biến trước khi chúng được khai báo.” Hành vi này là một phần bất thường của JavaScript và có thể dẫn đến lỗi. Việc sử dụng một biến trước khi khai báo thường không được mong muốn.

Đọc thêm  Đối tượng trong JavaScript – Hướng dẫn cho người mới bắt đầu

Rất may là letconst các biến, được giới thiệu trong ECMAScript 2015, hoạt động khác đi.

cẩu biến với letconst

Các biến được khai báo với letconst được nâng lên nhưng không được khởi tạo với giá trị mặc định. Truy cập một let hoặc const biến trước khi nó được khai báo sẽ dẫn đến một ReferenceError:

console.log(foo); // Uncaught ReferenceError: Cannot access 'foo' before initialization

let foo = 'bar';  // Same behavior for variables declared with const

Lưu ý rằng trình thông dịch vẫn nâng foo: thông báo lỗi cho chúng ta biết biến được khởi tạo ở đâu đó.

Vùng chết tạm thời

Lý do chúng tôi gặp lỗi tham chiếu khi cố truy cập vào một let hoặc const biến trước khi khai báo là do vùng chết tạm thời (TDZ).

TDZ bắt đầu ở đầu phạm vi bao quanh của biến và kết thúc khi nó được khai báo. Truy cập biến trong TDZ này sẽ đưa ra một ReferenceError.

Đây là một ví dụ với một khối rõ ràng hiển thị phần đầu và phần cuối của fooTDZ của:

{
 	// Start of foo's TDZ
  	let bar="bar";
	console.log(bar); // "bar"

	console.log(foo); // ReferenceError because we're in the TDZ

	let foo = 'foo';  // End of foo's TDZ
}

TDZ cũng có mặt trong các tham số chức năng mặc định, được đánh giá từ trái sang phải. Trong ví dụ sau, bar nằm trong TDZ cho đến khi giá trị mặc định của nó được đặt:

function foobar(foo = bar, bar="bar") {
  console.log(foo);
}
foobar(); // Uncaught ReferenceError: Cannot access 'bar' before initialization

Nhưng mã này hoạt động vì chúng tôi có thể truy cập foo bên ngoài TDZ của nó:

function foobar(foo = 'foo', bar = foo) {
  console.log(bar);
}
foobar(); // "foo"

typeof trong vùng chết tạm thời

Sử dụng một let hoặc const biến như một toán hạng của typeof toán tử trong TDZ sẽ báo lỗi:

console.log(typeof foo); // Uncaught ReferenceError: Cannot access 'foo' before initialization
let foo = 'foo';

Hành vi này phù hợp với các trường hợp khác của letconst trong TDZ mà chúng ta đã thấy. Lý do mà chúng tôi nhận được một ReferenceError đây là foo được khai báo nhưng không được khởi tạo – chúng ta nên biết rằng chúng ta đang sử dụng nó trước khi khởi tạo (nguồn: Axel Rauschmayer).

Đọc thêm  JavaScript setTimeout() – Cách đặt Hẹn giờ trong JavaScript hoặc Ngủ trong N giây

Tuy nhiên, đây không phải là trường hợp khi sử dụng một var biến trước khi khai báo vì nó được khởi tạo với undefined khi nó được nâng lên:

console.log(typeof foo); // "undefined"
var foo = 'foo';

Hơn nữa, điều này thật đáng ngạc nhiên vì chúng ta có thể kiểm tra loại biến không tồn tại mà không gặp lỗi. typeof trả về một chuỗi một cách an toàn:

console.log(typeof foo); // "undefined"

Trên thực tế, việc giới thiệu letconst phá sản typeofđảm bảo luôn trả về một giá trị chuỗi cho bất kỳ toán hạng nào.

Chức năng cẩu trong JavaScript

Các khai báo hàm cũng được nâng lên. Chức năng cẩu cho phép chúng ta gọi một chức năng trước khi nó được xác định. Ví dụ: đoạn mã sau chạy thành công và xuất ra "foo":

foo(); // "foo"

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

Lưu ý rằng chỉ có chức năng tuyên bố được nâng lên, không hoạt động biểu thức. Điều này sẽ có ý nghĩa: như chúng ta vừa học, các phép gán biến không được nâng lên.

Nếu chúng ta thử gọi biến mà biểu thức hàm đã được gán, chúng ta sẽ nhận được một TypeError hoặc ReferenceErrortùy thuộc vào phạm vi của biến:

foo(); // Uncaught TypeError: foo is not a function
var foo = function () { }

bar(); // Uncaught ReferenceError: Cannot access 'bar' before initialization
let bar = function () { }

baz(); // Uncaught ReferenceError: Cannot access 'baz' before initialization
const baz = function () { }

Điều này khác với việc gọi một hàm không bao giờ được khai báo, sẽ đưa ra một hàm khác ReferenceError:

foo(); // Uncaught ReferenceError: baz is not defined

Cách sử dụng cẩu trong JavaScript

cẩu biến

Vì sự nhầm lẫn mà var hoisting có thể tạo ra, tốt nhất là tránh sử dụng các biến trước khi chúng được khai báo. Nếu bạn đang viết mã trong một dự án greenfield, bạn nên sử dụng letconst để thực thi điều này.

Đọc thêm  Tres forms de factorizar un numero en JavaScript

Nếu bạn đang làm việc trong một cơ sở mã cũ hơn hoặc phải sử dụng var vì một lý do khác, MDN khuyên bạn nên viết var khai báo càng gần đầu phạm vi của chúng càng tốt. Điều này sẽ làm cho phạm vi biến của bạn rõ ràng hơn.

Bạn cũng có thể cân nhắc sử dụng no-use-before-define Quy tắc ESLint sẽ đảm bảo bạn không sử dụng một biến trước khi khai báo.

cẩu chức năng

Tính năng nâng hàm rất hữu ích vì chúng ta có thể ẩn việc triển khai hàm ở xa hơn trong tệp và để người đọc tập trung vào những gì mã đang thực hiện. Nói cách khác, chúng ta có thể mở một tệp và xem mã thực hiện chức năng gì mà không cần hiểu trước cách mã được triển khai.

Lấy ví dụ giả tạo sau:

resetScore();
drawGameBoard();
populateGameBoard();
startGame();

function resetScore() {
	console.log("Resetting score");
}

function drawGameBoard() {
	console.log("Drawing board");
}

function populateGameBoard() {
	console.log("Populating board");
}

function startGame() {
	console.log("Starting game");
}

Chúng tôi ngay lập tức có ý tưởng về những gì mã này làm mà không cần phải đọc tất cả các khai báo hàm.

Tuy nhiên, việc sử dụng các hàm trước khi khai báo chúng là vấn đề sở thích cá nhân. Một số nhà phát triển, chẳng hạn như Wes Bos, muốn tránh điều này và đưa các chức năng vào các mô-đun có thể được nhập khi cần (nguồn: Wes Bos).

Hướng dẫn về phong cách của Airbnb đưa điều này đi xa hơn và khuyến khích các biểu thức hàm được đặt tên trên các khai báo để ngăn tham chiếu trước khi khai báo:

Các khai báo hàm được nâng lên, có nghĩa là thật dễ dàng – quá dễ dàng – để tham chiếu hàm trước khi nó được xác định trong tệp. Điều này gây hại cho khả năng đọc và bảo trì.

Nếu bạn thấy rằng định nghĩa của một hàm đủ lớn hoặc phức tạp đến mức cản trở việc hiểu phần còn lại của tệp, thì có lẽ đã đến lúc giải nén nó vào mô-đun của chính nó! (Nguồn: Hướng dẫn về Phong cách JavaScript của Airbnb)

Phần kết luận

Cảm ơn bạn đã đọc và tôi hy vọng bài đăng này đã giúp bạn tìm hiểu về cẩu trong JavaScript. Vui lòng liên hệ với tôi trên LinkedIn nếu bạn muốn kết nối hoặc có bất kỳ câu hỏi nào!



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status