Tìm hiểu về JavaScript Closures

13 tháng 05, 2017 – 4381 lượt xemBà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 ( khoanh vùng phạm vi ) của biến trong JavaScript trước khi đọc tiếp, do tại để 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ó hoàn toàn có thể truy vấn tới những biến của outer function ( hàm chứa inner function ) – scope chain. Closure có 3 scope chain, nó hoàn toàn có thể : truy vấn tới những biến khai báo bên trong nó, truy vấn tới những biến của outer function, và truy vấn tới những biến global .Closure có 3 scope chainClosures không chỉ truy vấn được tới những biến, mà còn hoàn toàn có thể truy vấn những tham số của outer function. Chú ý, closures không hề truy vấn đối tượng người dùng arguments của outer function .Bạn hoàn toà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 thoáng đãng 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 liên tục 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à điển hình nổi bật của closures là inner function vẫn hoàn toàn có thể truy vấn tới những 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ẽ mê hoặc hơn khi giá trị những biến của outer function biến hóa ” trước khi closure được gọi “. Và tính năng can đảm và mạnh mẽ này hoàn toàn có thể được sử dụng theo nhiều cách phát minh sáng tạo, ví dụ điển hình ví dụ những biến private ( private variable ), được demo lần tiên phong 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 tàng trữ những tham chiếu đến những biến của outer function, nên chúng hoàn toàn có thể dẫn tới những bug khi những biến của của outer function biến hóa 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ổng thể những celebritiesID. Vì thế toàn bộ những thành phần 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=yiEeiMN2Khshttps://www.youtube.com/watch?v=71AtaJpJHw0

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

https://kipalog.com/posts/JavaScript-Closureshttps://kipalog.com/posts/Closure-va-scope-trong-javascript

5/5 - (1 vote)

Bài viết liên quan

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments