轻松理解javascript中的闭包(Understand JavaScript Closures With Ease)

原文链接: Understand JavaScript Closures

闭包使用起来十分友好和可靠:它使程序员编程过程更具有创造性,表达更清晰,更简明。JavaScript中的闭包使用得十分常见,不管你的水平是哪个层次的,你都毫无疑问会碰到。当然,闭包看起来可能好些很复杂,不在你的能力范围之内。但是看完这篇文章之后,闭包对你来说,将会更容易理解,在平日的JavaScript编码中,也更吸引你去用它。

这篇文章讲述JavaScript中闭包的详情,相对来说比较短。在此之前,你需要对JavaScript的变量作用域(JavaScript variable scope)比较熟悉,因为要理解闭包,你首先必须理解变量作用域。


什么是闭包?

闭包就是内部函数能够访问外部函数的变量——作用域链(scope chain)。闭包有三个作用域链:它能访问自己的局部作用域(大括号内定义的变量),它能访问外部函数的变量,它还能访问全局变量。

内部函数不仅能访问外部函数的变量,还能访问外部函数的参数(parameters)。值得注意的是,尽管内部函数能够直接调用外部函数的参数(parameters),但是不能调用外部函数的 arguments 对象。


你可以在一个函数里面加一个函数来创建一个闭包。

一个简单的闭包例子:

function showName (firstName, lastName) {

  var nameIntro = "Your name is ";
  // 这个内部函数可以访问外部函数的变量以及参数
  function makeFullName () {
        
    return nameIntro + firstName + " " + lastName;
    
  }

  return makeFullName ();

}


showName ("Michael", "Jackson"); // Your name is Michael Jackson


闭包在Node.js中使用地十分广泛。它们是Node.js中“异步,非阻断架构”的主力。闭包在jQuery以及任何你看到的JavaScript代码中也都使用得十分频繁。



jQuery中经典的闭包例子:

$(function() {
  var selections = []; 
  $(".niners").click(function() { // 闭包能访问变量selections
    selections.push (this.prop("name")); // 修改外部函数域内的变量selections
  });
});


闭包的规则和副作用

  1. 闭包在外部函数return之后,依然能够访问外部函数的变量:
    闭包中最重要也是最棘手的特性就是,外部函数return之后,内部函数依然能够访问外部函数的变量。是的,你没看错。当JavaScript的函数执行的时候,它们(内部函数和外部函数)实际上在被创建的时候,使用的是同一个作用域链(scope chain)。
    function celebrityName (firstName) {
        var nameIntro = "This celebrity is ";
        // 这个内部函数可以访问外部函数的变量 nameIntro  和参数 firstName
       function lastName (theLastName) {
            return nameIntro + firstName + " " + theLastName;
        }
        return lastName;
    }
    
    var mjName = celebrityName ("Michael"); // 在这个时刻,外部函数 celebrityName 已经return了.
    
    // 闭包 (lastName) 在外部函数已经return之后进行调用
    // 然而此时,闭包仍能访问外部函数的变量 nameIntro  和参数 firstName
    mjName ("Jackson"); // This celebrity is Michael Jackson
  2. Closures store references to the outer function’s variables; they do not store the actual value. 
Closures get more interesting when the value of the outer function’s variable changes before the closure is called. And this powerful feature can be harnessed in creative ways, such as this private variables example first demonstrated by 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. Closures Gone Awry
    
Because closures have access to the updated values of the outer function’s variables, they can also lead to bugs when the outer function’s variable changes with a for loop. Thus:
    // 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

    In the preceding example, by the time the anonymous functions are called, the value of i is 3 (the length of the array and then it increments). The number 3 was added to the uniqueID to create 103 for ALL the celebritiesID. So every position in the returned array get id = 103, instead of the intended 100, 101, 102.

    The reason this happened was because, as we have discussed in the previous example, the closure (the anonymous function in this example) has access to the outer function’s variables by reference, not by value. So just as the previous example showed that we can access the updated variable with the closure, this example similarly accessed the i variable when it was changed, since the outer function runs the entire for loop and returns the last value of i, which is 103.

    To fix this side effect (bug) in closures, you can use an Immediately Invoked Function Expression (IIFE), such as the following:

    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

Check back on February 7th for a quiz on JavaScript Closures.

你可能感兴趣的:(轻松理解javascript中的闭包(Understand JavaScript Closures With Ease))