ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 7.2 闭包 * 一个函数返回值是另一个函数; * 返回的函数调用了父函数内部的其他变量; * 返回的函数在外部被执行。 于是,产生了**闭包**。 ### 7.2.1.变量的作用域 变量的作用域有两种:**全局变量和局部变量。** Javascript语言的特殊之处,就在于**函数内部可以直接读取全局变量。** ~~~   var n=999;   function f1(){     alert(n);   }   f1(); // 999 ~~~ 另一方面,在函数外部自然无法读取函数内的局部变量。 ~~~   function f1(){     var n=999;   }   alert(n); // error ~~~ 出于种种原因,我们有时候需要得到函数内的局部变量。于是采用变通的手段:**在函数的内部,再定义一个函数。** ~~~   function f1(){     var n=999;     function f2(){       alert(n); // 999     }   } ~~~ 在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"作用域链"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。 既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它的内部变量了。 ~~~ function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999 ~~~ ### 7.2.2 闭包的作用 本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。 闭包的最大用处有两个:一个是可以**读取函数内部的变量**,另一个就是**让函数内部变量始终保持在内存中**,即闭包可以使得它诞生环境一直存在。 请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。 ~~~ function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7 ~~~ 上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,**闭包可以看作是函数内部作用域的一个接口**。 为什么会这样呢?原因就在于inc始终在内存中,而`inc的存在依赖于createIncrementor`,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。 闭包的另一个用处,是**封装对象的私有属性和私有方法。** ~~~ function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { name: name, getAge: getAge, setAge: setAge }; } var p1 = Person('张三'); p1.setAge(25); p1.getAge() // 25 ~~~ 上面代码中,函数Person的内部变量`_age`,通过闭包`getAge和setAge`,变成了返回对象p1的**私有变量**。 注意:外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以**内存消耗很大**。因此不能滥用闭包,否则会造成网页的性能问题。 ### 7.2.3 关于this对象 **this对象**是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用是,this等于那个对象。 但是,**匿名函数**的执行环境具有**全局性**,因此其this对象通常指向window。(通过call()或apply()改变执行环境情况除外) ~~~ var name = "The Window";   var object = {     name : "My Object",     getNameFunc : function(){       return function(){         return this.name;       };     }   };   alert(object.getNameFunc()()); //“The Window” ~~~ 为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象? 每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此不可能直接访问外部函数中的这两个变量。 ~~~ var name = "The Window";   var object = {     name : "My Object",     getNameFunc : function(){       var that = this; ←看这       return function(){         return that.name;       };     }   };   alert(object.getNameFunc()()); //"My Object" ~~~ 把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。 ### 7.2.4 模仿块级作用域 匿名函数可以用来模仿块级作用域,避免重复声明同一变量; 用作私有作用域的匿名函数语法如下: ~~~ (function(){ //块级作用域(私有作用域); })(); ~~~ 将函数声明包含在一对括号内,会将函数声明转换成函数表达式,函数表达式后面紧跟的`()`会立即调用这个函数。 ~~~ function outputNumbers(count) { for (var i = 0; i < count; i++) { alert(i); //0,1,2,3,4 } alert(i); //5 } ~~~ 改写后 ~~~ function outputNumbers(count) { (function () { for (var i = 0; i < count; i++) { alert(i); //0,1,2,3,4 } })(); alert(i); //error } ~~~ 在匿名函数中定义的任何变量,都会在执行结束时被销毁,因此,变量i只能在循环中使用。 在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,能够访问包含作用域中的所有变量。