Bài viết được dịch từ: javascriptissexy.com
Chúng ta thường xuyên sử dụng closures trong JavaScript, kinh nghiệm về JavaScript của bạn không quan trọng, chắc chắn bạn sẽ bắt gặp chúng hết lần này đến lần khác. Closures có thể khá phức tạp và nằm ngoài khả năng của bạn, nhưng sau khi đọc bài viết này, closures sẽ trở lên dễ hiểu hơn và bạn có thể sử dụng chúng cho các task JavaScript hàng ngày của mình.
Bạn nên hiểu rõ scope (phạm vi) của biến trong JavaScript trước khi đọc tiếp, bởi vì để hiểu closures bạn phải hiểu scope của biến trong JavaScript.
Closure là gì?
Một closure là một inner function (hàm khai báo bên trong một hàm khác), nó có thể truy cập tới các biến của outer function (hàm chứa inner function) - scope chain. Closure có 3 scope chain, nó có thể: truy cập tới các biến khai báo bên trong nó, truy cập tới các biến của outer function, và truy cập tới các biến global.
Closures không chỉ truy cập được tới các biến, mà còn có thể truy cập các tham số của outer function. Chú ý, closures không thể truy cập đối tượng arguments của outer function.
Bạn có thể tạo một closure bằng cách thêm một function bên trong function khác.
Ví dụ cơ bản về Closures trong JavaScript:
function showName (firstName, lastName) {
var nameIntro = "Your name is ";
// this inner function has access to the outer function's variables, including the parameter
function makeFullName () {
return nameIntro + firstName + " " + lastName;
}
return makeFullName ();
}
showName ("Michael", "Jackson"); // Your name is Michael Jackson
Closures được sử dụng rộng rãi trong Node.js; chúng là những con ngựa kéo (workhorses) trong kiến trúc asynchronous, non-blocking của Node.js. Closures cũng được sử dụng thường xuyên trong jQuery.
Một ví dụ về Closures trong jQuery:
$(function() {
var selections = [];
$(".niners").click(function() { // this closure has access to the selections variable
selections.push (this.prop("name")); // update the selections variable in the outer function's scope
});
});
Các quy tắc của Closures và Hiệu ứng phụ
1. Closures có thể truy cập tới các biến của outer function ngay cả sau khi outer function return:
Một trong những tính năng quan trọng và nổi bật của closures là inner function vẫn có thể truy cập tới các biến của outer function ngay cả sau khi outer function đã return. Hãy xem ví dụ này:
function celebrityName (firstName) {
var nameIntro = "This celebrity is ";
// this inner function has access to the outer function's variables, including the parameter
function lastName (theLastName) {
return nameIntro + firstName + " " + theLastName;
}
return lastName;
}
var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.
// The closure (lastName) is called here after the outer function has returned above
// Yet, the closure still has access to the outer function's variables and parameter
mjName ("Jackson"); // This celebrity is Michael Jackson
2. Closures lưu trữ các tham chiếu tới các biến của outer function, chúng không lưu trữ các giá trị thực sự.
Sẽ thú vị hơn khi giá trị các biến của outer function thay đổi "trước khi closure được gọi". Và tính năng mạnh mẽ này có thể được sử dụng theo nhiều cách sáng tạo, chẳng hạn ví dụ các biến private (private variable), được demo lần đầu tiên bởi Douglas Crockford:
function celebrityID () {
var celebrityID = 999;
// We are returning an object with some inner functions
// All the inner functions have access to the outer function's variables
return {
getID: function () {
// This inner function will return the UPDATED celebrityID variable
// It will return the current value of celebrityID, even after the changeTheID function changes it
return celebrityID;
},
setID: function (theNewID) {
// This inner function will change the outer function's variable anytime
celebrityID = theNewID;
}
}
}
var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.
mjID.getID(); // 999
mjID.setID(567); // Changes the outer function's variable
mjID.getID(); // 567: It returns the updated celebrityId variable
3. Hiệu ứng phụ của Closures
Bởi vì closures lưu trữ các tham chiếu đến các biến của outer function, nên chúng có thể dẫn tới các bug khi các biến của của outer function thay đổi với một vòng lặp for. Ví dụ:
// This example is explained in detail below (just after this code box).
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function () {
return uniqueID + i;
}
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id()); // 103
Trong ví dụ trên, khi kết thúc vòng lặp for giá trị của i là 3. Số 3 sẽ được cộng với uniqueID thành 103 cho tất cả các celebritiesID. Vì thế tất cả các phần tử trong mảng được return đều có id = 103, thay vì 100, 101, 102 như mong đợi.
Lý do điều này xảy ra bởi vì closure (anonymous function trong ví dụ trên) truy cập tới các biến của outer function bởi tham chiếu, không phải giá trị. Bạn đã thấy trong ví dụ trước đó, chúng ta có thể truy cập biến đã được cập nhật giá trị với closure, tương tự trong ví dụ này closure truy cập biến i khi giá trị của nó đã được thay đổi, khi outer function chạy xong toàn bộ vòng lặp giá trị của i lúc này là 3 và tất cả id có giá trị là uniqueID (100) + i (3).
Để fix bug này trong closures, bạn có thể sử dụng IIFE (Immediately Invoked Function Expression), như sau:
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function (j) { // the j parametric variable is the i passed in on invocation of this IIFE
return function () {
return uniqueID + j; // each iteration of the for loop passes the current value of i into this IIFE and it saves the correct value to the array
} () // BY adding () at the end of this function, we are executing it immediately and returning just the value of uniqueID + j, instead of returning a function.
} (i); // immediately invoke the function passing the i variable as a parameter
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100
var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101
Đọc thêm
https://www.youtube.com/watch?v=yiEeiMN2Khs
https://www.youtube.com/watch?v=71AtaJpJHw0
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
https://kipalog.com/posts/JavaScript-Closures
https://kipalog.com/posts/Closure-va-scope-trong-javascript
Bình luận