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

翻译 2016年02月24日 17:24:32

原文链接: Understand JavaScript Closures

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

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


什么是闭包?

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

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


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

一个简单的闭包例子:

function showName (firstName, lastName) {

  var nameIntro = "Your name is ";
  // <span style="font-family: Arial, Helvetica, sans-serif;">这个内部函数可以访问外部函数的变量以及参数</span>
  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闭包

闭包和原型是javascript语言的两大特点,上篇博文浅析JavaScript原型 中已经总结了原型 ,今天就总结一下闭包的相关知识。 前言 在开始闭包之前,需要先介绍一下匿名函数和Java...
  • u010773667
  • u010773667
  • 2015年02月15日 12:17
  • 1126

对JavaScript中闭包的理解

之前对于闭包这个概念的理解都是模糊的,只是单纯的知道闭包的作用: 1. 可以在函数的外部访问到函数内部的局部变量。 2. 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。  而这几天通过...
  • qq_36276528
  • qq_36276528
  • 2017年04月11日 11:41
  • 148

深入理解javascript原型和闭包系列

从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章。写了半个月,从9月17号开始写的。每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评...
  • wangfupeng1988
  • wangfupeng1988
  • 2014年09月30日 09:04
  • 4896

JavaScript 的闭包原理与详解

JavaScript 的闭包原理与详解。JavaScript的闭包是一个特色,但也是很多新手难以理解的地方,阅读过不少大作,对闭包讲解不一,个人以为,在《JavaScript高级程序设计》一书中,解释...
  • qq_29594393
  • qq_29594393
  • 2016年10月26日 18:24
  • 1187

深入理解JavaScript闭包(Closures)

介绍 本章我们将介绍在JavaScript里大家经常来讨论的话题 —— 闭包(closure)。闭包其实大家都已经谈烂了。尽管如此,这里还是要试着从理论角度来讨论下闭包,看看ECMAScript...
  • kingstart01
  • kingstart01
  • 2017年07月05日 11:54
  • 126

深入理解javascript闭包(二)

一、什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。 相信很少有人能直接看懂这句话,因为他描述的太学术。...
  • canlets
  • canlets
  • 2014年05月08日 20:25
  • 744

js闭包的理解以及闭包中this的理解

javascript 闭包、this 2016-01-25 js pl 闭包其实很好理解,但是由于经常把this和闭包绑在一起,从而加大了理解的难度,如果将他们分开考虑,那就清晰多...
  • u010585120
  • u010585120
  • 2016年07月27日 16:59
  • 4160

javascript理解之变量作用域与闭包

何为闭包闭包是指能够访问自由变量的函数 (变量在本地使用,但在闭包中定义)。换句话说,定义在闭包中的函数可以“记忆”它被创建时候的环境。函数作用域与声明提前var scope= "global sco...
  • sinat_25127047
  • sinat_25127047
  • 2016年06月12日 17:33
  • 1436

轻松理解javascript闭包

前言:闭包允许JavaScript程序员编写更好的代码。创意、表达和简洁。我们经常在JavaScript中使用闭包,不管您的JavaScript经验如何,您无疑会一次又一次地遇到它们。当然,闭包可能看...
  • wo240
  • wo240
  • 2017年08月02日 00:53
  • 101

javascript Closures(闭包)

function init() { var name = "Mozilla"; function displayName() { alert(name); } displa...
  • fantasy0126
  • fantasy0126
  • 2011年03月30日 17:23
  • 387
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:轻松理解javascript中的闭包(Understand JavaScript Closures With Ease)
举报原因:
原因补充:

(最多只允许输入30个字)