[Hard] 📄 Closure
1. What is Closure ?
Closure là gì?
Để hiểu closure, trước tiên cần nắm rõ phạm vi biến (variable scope) trong JavaScript và cách function truy cập các biến bên ngoài.
Variable Scope (Phạm vi biến)
Trong JavaScript, phạm vi biến được chia thành hai loại: global scope và function scope.
// global scope
let a = 1;
function parentFunction() {
// function scope
let b = 2;
function childFunction() {
let c = 3;
console.log(a, b, c); // print 1 2 3, can access global scope & function scope
}
childFunction();
}
parentFunction();
console.log(a); // print 1, can access global scope
console.log(b, c); // Phát sinh lỗi, không thể truy cập biến trong function scope
Closure example
Điều kiện kích hoạt Closure là có một hàm con được định nghĩa bên trong hàm cha và được trả về thông qua return, nhằm bảo toàn các biến môi trường trong hàm con (tức là né tránh Garbage Collection).
function parentFunction() {
let count = 0;
return function childFunction() {
count += 1;
console.log(`Số đếm hiện tại: ${count}`);
};
}
const counter = parentFunction();
counter(); // print Số đếm hiện tại: 1
counter(); // print Số đếm hiện tại: 2
// Biến count không bị thu hồi, vì childFunction vẫn tồn tại và mỗi lần gọi đều cập nhật giá trị của count
Tuy nhiên cần lưu ý, vì closure sẽ lưu giữ biến trong bộ nhớ, nên nếu có quá nhiều biến sẽ dẫn đến chiếm dụng bộ nhớ quá lớn (không được lạm dụng closure), từ đó ảnh hưởng đến hiệu suất.
2. Create a function that meets the following conditions
Tạo function thỏa mãn các điều kiện dưới đây (sử dụng khái niệm closure để xử lý)
plus(2, 5); // output 7
plus(2)(5); // output 7
First Solution : two functions
Tách thành hai function để xử lý
function plus(value, subValue) {
return value + subValue;
}
console.log(plus(2, 5));
// use closure save variable
function plus(value) {
return function (subValue) {
return value + subValue;
};
}
console.log(plus(2)(5));
Second Solution : single function
Tất nhiên cách giải đầu tiên có xác suất bị reject không nhỏ, nên cần thử gộp vào cùng một function.
function plus(value, subValue) {
// Dựa vào số lượng tham số truyền vào mỗi lần để phán đoán
if (arguments.length > 1) {
return value + subValue;
} else {
return function (item) {
return value + item;
};
}
}
console.log(plus(2, 5));
console.log(plus(2)(5));
3. Please take advantage of the closure feature to increase the number
Hãy tận dụng đặc tính của closure để tăng dần số
function plus() {
// code
}
var obj = plus();
obj.add(); // print 1
obj.add(); // print 2
First Solution : return variable
Ở đây không sử dụng Arrow Function, mà dùng dạng function thông thường.
function plus() {
let cash = 0;
let counter = {
add() {
cash += 1;
console.log(cash);
},
};
return counter;
}
var obj = plus();
obj.add();
obj.add();
Second Solution : return object
Trong cách giải trước, cũng có thể đặt trực tiếp object bên trong return
function plus() {
let cash = 0;
return {
add: function () {
cash += 1;
console.log(cash);
},
};
}
var obj = plus();
obj.add();
obj.add();
4. What will be printed in this nested function call?
Lời gọi hàm lồng nhau này sẽ in ra gì?
function a(aa) {
aa();
}
function b(bb) {
bb();
}
function c() {
console.log('hello');
}
a(b(c));
Phân tích
Kết quả thực thi:
hello
TypeError: aa is not a function
Luồng thực thi chi tiết
// Thực thi a(b(c))
// JavaScript thực thi hàm từ trong ra ngoài
// Bước 1: Thực thi hàm trong cùng b(c)
b(c)
↓
// Hàm c được truyền vào b như tham số
// Bên trong hàm b thực thi bb(), tức là c()
c() // In ra 'hello'
↓
// Hàm b không có câu lệnh return
// Nên trả về undefined
return undefined
// Bước 2: Thực thi a(undefined)
a(undefined)
↓
// undefined được truyền vào a như tham số
// Bên trong hàm a cố gắng thực thi aa()
// Tức là undefined()
undefined() // ❌ Lỗi: TypeError: aa is not a function
Tại sao lại như vậy?
1. Thứ tự thực thi hàm (từ trong ra ngoài)
// Ví dụ
console.log(add(multiply(2, 3)));
↑ ↑
| └─ 2. Thực thi multiply(2, 3) trước → 6
└────── 3. Sau đó thực thi add(6)
// Cùng khái niệm
a(b(c))
↑ ↑
| └─ 1. Thực thi b(c) trước
└─── 2. Sau đó thực thi a(kết quả của b(c))
2. Hàm không có return sẽ trả về undefined
function b(bb) {
bb(); // Đã thực thi, nhưng không có return
} // Ngầm định return undefined
// Tương đương với
function b(bb) {
bb();
return undefined; // JavaScript tự động thêm vào
}
3. Cố gắng gọi thứ không phải function sẽ báo lỗi
const notAFunction = undefined;
notAFunction(); // TypeError: notAFunction is not a function
// Các trường hợp khác cũng báo lỗi
null(); // TypeError
123(); // TypeError
'string'(); // TypeError
Cách sửa?
Cách 1: Cho hàm b trả về một function
function a(aa) {
aa();
}
function b(bb) {
bb();
return function () {
console.log('b executed');
};
}
function c() {
console.log('hello');
}
a(b(c));
// Kết quả:
// hello
// b executed
Cách 2: Truyền trực tiếp function, không thực thi trước
function a(aa) {
aa();
}
function b(bb) {
return function () {
bb();
};
}
function c() {
console.log('hello');
}
a(b(c)); // Chỉ in ra 'hello'
// Hoặc viết như thế này
a(() => b(c)); // In ra 'hello'
Cách 3: Thay đổi logic thực thi
function a(aa) {
aa();
}
function b(bb) {
bb();
}
function c() {
console.log('hello');
}
// Thực thi riêng biệt
b(c); // In ra 'hello'
a(() => console.log('a executed')); // In ra 'a executed'
Câu hỏi liên quan
Câu hỏi 1: Nếu đổi thành như thế này thì sao?
function a(aa) {
return aa();
}
function b(bb) {
return bb();
}
function c() {
console.log('hello');
return 'world';
}
console.log(a(b(c)));
Nhấn để xem đáp án
hello
TypeError: aa is not a function
Phân tích:
b(c)→ Thực thic(), in ra'hello', trả về'world'a('world')→ Thực thi'world'()... khoan, cái này vẫn báo lỗi!
Đáp án đúng:
hello
TypeError: aa is not a function
Vì b(c) trả về 'world' (chuỗi), a('world') cố gắng thực thi 'world'(), chuỗi không phải là function, nên báo lỗi.
Câu hỏi 2: Nếu tất cả đều có return thì sao?
function a(aa) {
return aa;
}
function b(bb) {
return bb;
}
function c() {
return 'hello';
}
const result = a(b(c));
console.log(result);
console.log(result());
Nhấn để xem đáp án
[Function: c]
hello
Phân tích:
b(c)→ Trả về chính hàmc(không thực thi)a(c)→ Trả về chính hàmcresultlà hàmcresult()→ Thực thic(), trả về'hello'
Điểm ghi nhớ
// Ưu tiên gọi hàm
a(b(c))
↓
// 1. Thực thi hàm trong cùng trước
b(c) // Nếu b không có return thì là undefined
↓
// 2. Sau đó thực thi hàm ngoài
a(undefined) // Cố gắng thực thi undefined() sẽ báo lỗi
// Cách giải quyết
// ✅ 1. Đảm bảo hàm trung gian có trả về function
// ✅ 2. Hoặc sử dụng arrow function để bọc lại
a(() => b(c))