Closure là gì? Tìm hiểu về Closure trong JavaScript

Có lẽ bạn chưa biết rằng, có rất nhiều người “rớt phỏng vấn xin việc” chỉ vì Closure đấy! Vậy, Closure là gì? Tại sao Closure lại xuất hiện nhiều trong những buổi phỏng vấn đến như vậy? Đừng lo, Tino Group sẽ giải đáp những câu hỏi này và giúp bạn hiểu thêm về Closure trong JavaScript thông qua một số ví dụ nhé!

Tìm hiểu về Closure

Closure là gì?

Closure là một hàm được viết lồng vào trong một hàm khác và Closure hoàn toàn có thể sử dụng biến toàn cục, biến cục bộ của hàm ( hàm cha ) ngay cả khi đã đóng và sử dụng biến cục bộ của bản thân Closure .
Trong JavaScript, Closure là một thuộc tính rất mạnh. Không chỉ JavaScript, Closure còn xuất hiện trong hầu hết những loại ngôn từ lập trình khác .

Trong JavaScript, Closure có 3 phạm vi truy cập biến số khác nhau bao gồm:

  • Biến ở trong hàm Closure
  • Biến được khai báo ở hàm cha chứa Closure (outer function)
  • Biến toàn cục (global)

closure-la-gi

Hàm Closure ra sao?

Bên cạnh kim chỉ nan, tất cả chúng ta sẽ đi tìm hiểu và khám phá về ví dụ để rõ hơn về Closure nhé !
Trước tiên, ta sẽ xem xét ví dụ về Lexical scoping như sau :

function outerFuc() {
var name = 'Mai Truc Lam';
function innerFunc() {
console.log(name);
}
innerFunc();
}
outerFuc(); // Kết quả: Mai Truc Lam

Trong đó, ta có :

  • Biến toàn cục: name
  • Function functionOuter(function cha)
  • Function innerFunc, trong function innerFunc không có biến toàn cục nào nhưng trong hàm đang sử dụng một biến name của function functionOuter.

Thay đổi code một tí, tất cả chúng ta sẽ có Closure như sau :

function outerFuc() {
var name = 'Mai Truc Lam';
function innerFunc() {
console.log(name);
}
return innerFunc;
}
var refInnerFunc = outerFuc();
refInnerFunc(); // Kết quả: Mai Truc Lam

Có vẻ, bạn cũng đã thấy sự độc lạ ở đây rồi đúng không nào ? Trong đoạn code thứ 2, tất cả chúng ta sẽ thấy :

Hàm refInnerFunc đang gáng và tham chiếu đến kết quả của hàm outerFuc(); refInnerFunc trỏ đến hàm innerFunc nhưng chưa thực thi.

Sau khi hàm outerFuc() thực thi xong, biến toàn cục của hàm sẽ được giải phóng

Khi hàm innerFunc() thực thi sẽ ra kết quả là Mai Truc Lam. Nhưng kết quà này là của hàm cha outerFuc(). Nhưng điều này nghe có vẻ vô lý về mắt lý thuyết đúng không?

Nhưng trong JavaScript, khi 1 hàm nằm trong 1 hàm khác, nếu hàm cha thực thi trước sẽ tạo ra một môi trường Lexical đặt tất cả các biến đang có vào và gắn cho hàm con (trong ví dụ là: refInnerFunc) để sử dụng.

Cũng có thể giải thích như sau: khi hàm outerFuc() “chết” đi sẽ để lại “di chúc” cho hàm con refInnerFunc() là một biến name, sau khi hàm innerFunc() “chết” theo biến mới được giải phóng.

Trong ví dụ trên, chúng ta có thể thấy được hàm refInnerFunc() là Closure, vì hàm này đang ở trong 1 hàm khác và có thể sử dụng biến name của hàm cha outerFuc(). Nếu có cả biến cục bộ của bản thân refInnerFunc() và biến global, refInnerFunc() cúng có thể sử dụng được.

refInnerFunc() sẽ tham chiếu đến môi trường Lexical và hàm innerFunc().

Ứng dụng của Closure trong thực tế là gì?

Trong thực tiễn, ta sẽ thấy Closure có khá nhiều ứng dụng như :

  • Tạo thành một Function factory: một hàm tạo ra một hàm khác
  • Mô phỏng lại các phạm vi của biến trong lập trình hướng đối tượng.
  • Closure Scope Chain

Để hiểu hơn, tất cả chúng ta lại đi vào ví dụ về 3 ứng dụng trên nhé !

Function factory

Chúng ta sẽ có code như sau :

function makeExponentiation(x) {
var exponent = x;
return function(y) {
return Math.pow(y, exponent);
}
}
var sqr = makeExponentiation(2);
var sqrt = makeExponentiation(0.5);
console.log('3 squared is ' + sqr(3));
console.log('căn bậc hai của 9 là ' + sqrt(9));

Trong đó, bạn có thể thấy hàm makeExponentiation giống như một Function factory khi có thể tạo ra được những Function tùy vào tham số truyền vào. 2 Closure: sqrt sqr sở hữu body giống nhau nhưng khác biến tương đương (ENV – Variable Equivalent).

closure-la-gi

Mô phỏng lại phạm vi của biến trong lập trình hướng đối tượng

Bên trong JavaScript chưa có khái niệm class đúng nghĩa như trong C + + hay những ngôn từ lập trình khác. Trong ES6, ở đầu cuối, khái niệm class trong JavaScript cũng có. Tuy nhiên, về thực chất đây là một giải pháp giả lập / mô phỏng / hack bằng cách sử dụng Closure. Bạn hoàn toàn có thể xem code sau đây :

function Counter() {
var counter = 0;
function add(number) {
counter += number;
}
return {
increment: function() {
add(1);
},
decrement: function() {
add(-1);
},
value: function() {
return counter;
}
};
});
var counter = Counter();
console.log('giá trị ban đầu’ + counter.value());
counter.increment();
counter.increment();
console.log('sau khi tăng lên 1' + counter.value());
counter.decrement();
console.log('sau khi giảm xuống 1 ' + counter.value());

Trong đó, các hàm increment, decrement value đều là Closure có body khác nhau nhưng chia sẻ cùng một Variable Equivalent.

Chia sẻ chung Variable Equivalent chính là “ tuyệt kỹ ” mô phỏng class trong JavaScript. Khi Closure update một biến, việc này đổi này cũng sẽ được ghi nhận ở những Closure khác .

Closure Scope Chain

Tiếp theo, tất cả chúng ta sẽ lý giải tiếp về những ý trong phần mở màn đã đề cập về 3 khoanh vùng phạm vi Closure hoàn toàn có thể truy vấn gồm có :

  • Local Scope: phạm vị tự có
  • Outer Functions Scope: phạm vi bên ngoài (phạm vi của hàm cha)
  • Global Scope

Khi lập trình, một trong những sai lầm đáng tiếc thông dụng là không nhận ra trường hợp Outer Function là một hàm lồng, dẫn đến việc : khoanh vùng phạm vi của Outer Function là khoanh vùng phạm vi của Outer Function. Điều này dẫn đến một chuỗi những khoanh vùng phạm vi hàm rất hiệu suất cao .
Để chứng tỏ cho điều này, hãy cùng Tino Group xem xét một ví dụ sau đây nhé !

// đây là global scope
var e = 10;
function sum(a){
return function(b){
return function(c){
// outer functions scope
return function(d){
// local scope
return a + b + c + d + e;
}
}
}
}
console.log(sum(1)(2)(3)(4)); // log 20
// Bạn cũng có thể viết mà không cần các hàm ẩn danh:
// global scope
var e = 10;
function sum(a){
return function sum2(b){
return function sum3(c){
// outer functions scope
return function sum4(d){
// local scope
return a + b + c + d + e;
}
}
}
}
var sum2 = sum(1);
var sum3 = sum2(2);
var sum4 = sum3(3);
var result = sum4(4);
console.log(result) //log 20

Trong ví dụ trên, bạn hoàn toàn có thể thấy một loạt những hàm lồng nhau. Tất cả những hàm này đều hoàn toàn có thể truy vấn vào khoanh vùng phạm vi của những hàm bên ngoài. Với trường hợp này, tất cả chúng ta hoàn toàn có thể nói rằng : Closure có năng lực truy vấn vào tổng thể khoanh vùng phạm vi của Outer Function .

Cân nhắc về hiệu suất

Đây không phải là một tính năng của Closure trong trong thực tiễn mà là một nhắc nhở. Do phần này quá ngắn nên Tino Group xin phép gộp chung vào ví dụ thay vì tách riêng vì nhắc nhở này có tương quan mật thiết đến ví dụ ở trên .
Việc tạo quá nhiều hàm trong một hàm khác khi Closure không thiết yếu để xử lý một tác vụ đơn cử. Lý do là vì Closure sẽ hoàn toàn có thể ảnh hưởng tác động xấu đi đến hiệu suất của script cũng như cả vận tốc giải quyết và xử lý lẫn mức tiêu thụ bộ nhớ !
Vì vậy, bạn hãy xem xét thật kỹ nhé !
closure-la-giĐến đây, Tino Group hy vọng rằng bạn đã hiểu Closure là gì cũng như hiểu hơn về Closure trong JavaScript trải qua những ví dụ. Chúc bạn sẽ thành công xuất sắc trên con đường lập trình đầy gian truân và thử thách nhé !
Bài viết có tìm hiểu thêm từ nhiều nguồn : Dev. to, Medium, JavaScript Tutorial, Mozilla Developer, TutorialsTeacher, TopDev, …

Những câu hỏi thường gặp về Closure

Tìm hiểu thêm về Closure ở đâu?

Có khá nhiều tài liệu để bạn tham khảo về Closure nói riêng và JavaScript nói chung như:

  • JavaScript Tutorial
  • Mozilla Developer
  • TutorialsTeacher

Dĩ nhiên, tài liệu của Mozilla Developer sẽ có phần chuẩn chỉnh hơn những tài liệu khác nhưng cũng hơi khó hiểu hơn. Bạn sẽ cần phải có tiếng Anh chuyên ngành hoặc tối thiểu tiếng Anh để hiểu bài viết của Mozilla Developer .

Ví dụ thường gặp về Closure khi phỏng vấn xin việc là gì?

Ví dụ này được lấy từ bài viết của Giang Coffee trên Medium, bạn hoàn toàn có thể tìm hiểu thêm, thử tìm ra lỗi sai và khắc phục lỗi này nhé !
for ( var i = 0 ; i < 3 ; i + + ) { setTimeout ( function ( ) { console.log ( i ) ; }, 1000 ) }

Cách khắc phục lỗi sai trong ví dụ trên là gì?

Cách thứ nhất: sử dụng let để giải quyết lỗi.

Cách thứ hai : sử dụng thêm nhiều Closure nữa, ví dụ như :
for ( var i = 0 ; i < 3 ; i + + ) { function log ( x ) { return function ( ) { console.log ( x ) ; } } setTimeout ( log ( i ), 1000 ) } Dĩ nhiên, trong bài phỏng vấn bạn nhớ được gì bạn cứ thử nhé !

Vì sao trả lời được câu hỏi về Closure nhưng vẫn bị đánh trượt phỏng vấn?

Có vẻ, bạn đã quay lại bài viết này sau một thời hạn đi phỏng vấn, dù vấn đáp được câu hỏi về Closure do nhà tuyển dụng lấy đúng ví dụ trên mạng đúng không nào ?
Đừng buồn, hoàn toàn có thể vì môi trường tự nhiên không tương thích, bạn không tương thích hoặc nhà tuyển dụng thấy mức kiến thức và kỹ năng của bạn vẫn chưa đủ và khá nhiều nguyên do khác nè .

CÔNG TY CỔ PHẦN TẬP ĐOÀN TINO

  • Trụ sở chính: L17-11, Tầng 17, Tòa nhà Vincom Center, Số 72 Lê Thánh Tôn, Phường Bến Nghé, Quận 1, Thành phố Hồ Chí Minh
    Văn phòng đại diện: 42 Trần Phú, Phường 4, Quận 5, Thành phố Hồ Chí Minh
  • Điện thoại: 0364 333 333
    Tổng đài miễn phí: 1800 6734
  • Email: [email protected]
  • Website: www.tino.org

5/5 – ( 1 bầu chọn )