Phạm vi và Đóng cửa trong JavaScript – Được giải thích bằng các ví dụ


Bạn có thể đã bắt gặp hoặc viết mã tương tự như thế này khi viết JavaScript:

function sayWord(word) {
	return () => console.log(word);
}

const sayHello = sayWord("hello");

sayHello(); // "hello"

Mã này thú vị vì một vài lý do. Đầu tiên, chúng ta có thể truy cập word trong hàm được trả về từ sayWord. Thứ hai, chúng tôi có quyền truy cập vào wordgiá trị của khi chúng ta gọi sayHello – mặc dù chúng tôi gọi sayHello nơi chúng tôi không có quyền truy cập vào word.

Trong bài viết này, chúng ta sẽ tìm hiểu về phạm vi và bao đóng cho phép hành vi này.

Giới thiệu Phạm vi trong JavaScript

Phạm vi là phần đầu tiên sẽ giúp chúng ta hiểu ví dụ trước. Phạm vi của một biến là một phần của chương trình mà nó có sẵn để sử dụng.

Biến JavaScript có phạm vi từ vựng, nghĩa là chúng ta có thể xác định phạm vi của biến từ nơi nó được khai báo trong mã nguồn. (Điều này không hoàn toàn đúng: var các biến không có phạm vi từ vựng, nhưng chúng ta sẽ thảo luận về điều đó ngay sau đây.)

Lấy ví dụ sau:

if (true) {
	const foo = "foo";
	console.log(foo); // "foo"
}

Các if câu lệnh giới thiệu phạm vi khối bằng cách sử dụng câu lệnh khối. chúng tôi nói rằng foo là phạm vi khối đến if bản tường trình. Điều này có nghĩa là nó chỉ có thể được truy cập từ bên trong khối đó.

Nếu chúng ta cố gắng truy cập foo bên ngoài khối, chúng tôi nhận được một ReferenceError bởi vì nó nằm ngoài phạm vi:

if (true) {
	const foo = "foo";
	console.log(foo); // "foo"
}

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

Chặn các câu lệnh ở các dạng khác, chẳng hạn như forwhile vòng lặp, cũng sẽ tạo ra một phạm vi cho các biến phạm vi khối. Ví dụ, foo nằm trong phạm vi một thân hàm bên dưới:

function sayFoo() {
	const foo = "foo";
	console.log(foo);
}

sayFoo(); // "foo"

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

Phạm vi lồng nhau và chức năng

JavaScript cho phép các khối lồng nhau và do đó phạm vi lồng nhau. Các phạm vi lồng nhau tạo ra một cây phạm vi hoặc chuỗi phạm vi.

Hãy xem xét mã bên dưới, mã này lồng vào nhiều câu lệnh khối:

if (true) {
	const foo = "foo";
	console.log(foo); // "foo"

	if (true) {
		const bar = "bar";
		console.log(foo); // "foo"

		if (true) {
			console.log(foo, bar); // "foo bar"
		}
	}
}

JavaScript cũng cho phép chúng ta lồng các hàm:

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

	baz();
}

foo("bar"); // "bar"

Như mong đợi, chúng ta có thể truy cập các biến từ phạm vi trực tiếp của chúng (phạm vi mà chúng được khai báo). Chúng ta cũng có thể truy cập các biến từ phạm vi bên trong của chúng (phạm vi lồng trong phạm vi trực tiếp của chúng). Nghĩa là, chúng ta có thể truy cập các biến từ phạm vi mà chúng được khai báo trong và từ mọi phạm vi bên trong.

Đọc thêm  Cách xây dựng công cụ rút ngắn URL đơn giản chỉ bằng HTML và JavaScript

Trước khi tiếp tục, chúng ta nên làm rõ sự khác biệt trong hành vi này giữa các kiểu khai báo biến.

Phạm vi của let, const và var trong JavaScript

Chúng ta có thể tạo các biến với let, constvar các tờ khai. Vì letconst, phạm vi khối hoạt động như đã giải thích ở trên. Tuy nhiên, var cư xử khác nhau.

để cho và const

letconst tạo các biến trong phạm vi khối. Khi được khai báo trong một khối, chúng chỉ có thể truy cập được trong khối đó. Hành vi này đã được thể hiện trong các ví dụ trước đây của chúng tôi:

if (true) {
	const foo = "foo";
	console.log(foo); // "foo"
}

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

var

Các biến được tạo với var được đặt trong phạm vi chức năng gần nhất của chúng hoặc phạm vi toàn cầu (chúng ta sẽ thảo luận ngay sau đây). Chúng không nằm trong phạm vi khối:

function foo() {
	if (true) {
		var foo = "foo";
	}
	console.log(foo);
}

foo(); // "foo"

var có thể tạo ra các tình huống khó hiểu và thông tin này chỉ được đưa vào để đảm bảo tính đầy đủ. Tốt nhất là sử dụng letconst khi có thể. Phần còn lại của bài viết này sẽ chỉ liên quan đến letconst biến.

Nếu bạn quan tâm đến cách var trong ví dụ trên, bạn nên xem bài viết của tôi về cẩu.

Phạm vi toàn cầu và mô-đun trong JavaScript

Ngoài phạm vi khối, các biến có thể được đặt trong phạm vi toàn cầu và mô-đun.

Trong trình duyệt web, phạm vi toàn cầu nằm ở cấp cao nhất của tập lệnh. Nó là gốc của cây phạm vi mà chúng tôi đã mô tả trước đó và nó chứa tất cả các phạm vi khác. Do đó, việc tạo một biến trong phạm vi toàn cầu giúp nó có thể truy cập được ở mọi phạm vi:

<script>
	const foo = "foo";
</script>
<script>
	console.log(foo); // "foo"
		
	function bar() {
		if (true) {
			console.log(foo);
		}
	}

	bar(); // "foo"
</script>

Mỗi mô-đun cũng có phạm vi riêng của nó. Các biến được khai báo ở cấp độ mô-đun chỉ khả dụng trong mô-đun đó – chúng không phải là toàn cầu:

<script type="module">
	const foo = "foo";
</script>
<script>
	console.log(foo); // Uncaught ReferenceError: foo is not defined
</script>

Bao đóng trong JavaScript

Bây giờ chúng ta đã hiểu phạm vi, hãy quay lại ví dụ mà chúng ta đã thấy trong phần giới thiệu:

function sayWord(word) {
	return () => console.log(word);
}

const sayHello = sayWord("hello");

sayHello(); // "hello"

Nhớ lại rằng có hai điểm thú vị về ví dụ này:

  1. Hàm trả về từ sayWord có thể truy cập word tham số
  2. Hàm trả về duy trì giá trị của word khi nào sayHello được gọi bên ngoài phạm vi của word

Điểm đầu tiên có thể được giải thích bằng phạm vi từ vựng: hàm được trả về có thể truy cập word bởi vì nó tồn tại trong phạm vi bên ngoài của nó.

Đọc thêm  Cách sử dụng các phương thức apply(?), call(?) và bind(➰) trong JavaScript

Điểm thứ hai là do các bao đóng: Một bao đóng là một hàm được kết hợp với các tham chiếu đến các biến được xác định bên ngoài nó. Các bao đóng duy trì các tham chiếu biến, cho phép các hàm truy cập các biến bên ngoài phạm vi của chúng. Chúng “đính kèm” hàm và các biến trong môi trường của nó.

Ví dụ về Bao đóng trong JavaScript

Bạn có thể đã gặp và sử dụng bao đóng thường xuyên mà không hề hay biết. Hãy khám phá một số cách khác để sử dụng bao đóng.

gọi lại

Thông thường, một cuộc gọi lại sẽ tham chiếu đến một biến được khai báo bên ngoài chính nó. Ví dụ:

function getCarsByMake(make) {
	return cars.filter(x => x.make === make);
}

make có sẵn trong cuộc gọi lại vì phạm vi từ vựng và giá trị của make được duy trì khi chức năng ẩn danh được gọi bởi filter vì đóng cửa.

trạng thái lưu trữ

Chúng ta có thể sử dụng các bao đóng để trả về các đối tượng từ các hàm lưu trữ trạng thái. Hãy xem xét những điều sau đây makePerson hàm trả về một đối tượng có thể lưu trữ và thay đổi một name:

function makePerson(name) {
	let _name = name;

	return {
		setName: (newName) => (_name = newName),
		getName: () => _name,
	};
}

const me = makePerson("Zach");
console.log(me.getName()); // "Zach"

me.setName("Zach Snoek");
console.log(me.getName()); // "Zach Snoek"

Ví dụ này minh họa cách đóng không chỉ đóng băng các giá trị của biến từ phạm vi bên ngoài của hàm trong quá trình tạo. Thay vào đó, chúng duy trì các tham chiếu trong suốt thời gian đóng.

phương pháp riêng tư

Nếu bạn đã quen thuộc với lập trình hướng đối tượng, bạn có thể nhận thấy rằng ví dụ trước của chúng ta gần giống với một lớp lưu trữ trạng thái riêng tư và hiển thị các phương thức getter và setter công khai. Chúng ta có thể mở rộng song song hướng đối tượng này hơn nữa bằng cách sử dụng các bao đóng để triển khai các phương thức riêng tư:

function makePerson(name) {
	let _name = name;

	function privateSetName(newName) {
		_name = newName;
	}

	return {
		setName: (newName) => privateSetName(newName),
		getName: () => _name,
	};
}

privateSetName người tiêu dùng không thể truy cập trực tiếp và nó có thể truy cập biến trạng thái riêng tư _name thông qua một đóng cửa.

Đọc thêm  Cách xóa phần tử khỏi mảng JavaScript – Xóa một mục cụ thể trong JS

Phản ứng xử lý sự kiện

Cuối cùng, bao đóng là phổ biến trong trình xử lý sự kiện React. Sau đây Counter thành phần được sửa đổi từ tài liệu React:

function Counter({ initialCount }) {
	const [count, setCount] = React.useState(initialCount);

	return (
		<>
			<button onClick={() => setCount(initialCount)}>Reset</button>
			<button onClick={() => setCount((prevCount) => prevCount - 1)}>
				-
			</button>
			<button onClick={() => setCount((prevCount) => prevCount + 1)}>
				+
			</button>
			<button onClick={() => alert(count)}>Show count</button>
		</>
	);
}

function App() {
	return <Counter initialCount={0} />;
}

Đóng cửa làm cho nó có thể cho:

  • trình xử lý nhấp vào nút đặt lại, giảm và tăng để truy cập setCount
  • nút đặt lại để truy cập initialCount từ Counterđạo cụ của
  • và nút “Hiển thị số lượng” để hiển thị count tiểu bang.

Closure rất quan trọng trong các phần khác của React, chẳng hạn như props và hook. Thảo luận về những chủ đề này nằm ngoài phạm vi của bài viết này. Tôi khuyên bạn nên đọc bài đăng này từ Kent C. Dodds hoặc bài đăng này từ Dan Abramov để tìm hiểu thêm về vai trò của các lần đóng trong React.

Phần kết luận

Phạm vi đề cập đến một phần của chương trình mà chúng ta có thể truy cập vào một biến. JavaScript cho phép chúng ta lồng các phạm vi và các biến được khai báo ở phạm vi bên ngoài có thể truy cập được từ tất cả các phạm vi bên trong. Các biến có thể ở phạm vi toàn cầu, mô-đun hoặc khối.

Bao đóng là một hàm kèm theo các tham chiếu đến các biến trong phạm vi bên ngoài của nó. Bao đóng cho phép các hàm duy trì kết nối với các biến bên ngoài, thậm chí bên ngoài phạm vi của các biến.

Có nhiều cách sử dụng bao đóng, từ việc tạo các cấu trúc giống như lớp lưu trữ trạng thái và triển khai các phương thức riêng tư để chuyển các cuộc gọi lại tới các trình xử lý sự kiện.

Kết nối nào

Nếu bạn quan tâm đến nhiều bài viết như thế này, hãy đăng ký nhận bản tin của tôi và kết nối với tôi trên LinkedIn và Twitter!

Sự nhìn nhận

Cảm ơn Bryan Smith đã cung cấp phản hồi về bản nháp của bài đăng này.

Ảnh bìa của Karine Avetisyan trên Bapt.





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