闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
闭包的特性
闭包有三个特性:
1.函数嵌套函数
2.函数内部可以引用外部的参数和变量
3.参数和变量不会被垃圾回收机制回收
闭包的定义及其优缺点
闭包 是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量
闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
闭包是javascript语言的一大特点,主要应用闭包场合主要是为了:设计私有的方法和变量。
一般函数执行完毕后,局部活动对象就被销毁,内存中仅仅保存全局作用域。但闭包的情况不同!
围着主题展开话题
1. 闭包的定义?
来看一些关于闭包的定义:
1.闭包是指有权访问另一个函数作用域中变量的函数
2.函数对象可以通过作用域链相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性称为 ‘闭包' 。
3.内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。
大家想系统的学习js闭包的概念可以参考脚本之家网站的js电子书栏目学习吧。
来个定义总结
1.可以访问外部函数作用域中变量的函数
2.被内部函数访问的外部函数的变量可以保存在外部函数作用域内而不被回收---这是核心,后面我们遇到闭包都要想到,我们要重点关注被闭包引用的这个变量。
来创建个简单的闭包
var sayName = function(){ var name = 'jozo'; return function(){ alert(name); } }; var say = sayName(); say();
来解读后面两个语句:
•var say = sayName() :返回了一个匿名的内部函数保存在变量say中,并且引用了外部函数的变量name,由于垃圾回收机制,sayName函数执行完毕后,变量name并没有被销毁。
•say() :执行返回的内部函数,依然能访问变量name,输出 'jozo' .
2. 闭包中的作用域链
理解作用域链对理解闭包也很有帮助。
变量在作用域中的查找方式应该都很熟悉了,其实这就是顺着作用域链往上查找的。
当函数被调用时:
1.先创建一个执行环境(execution context),及相应的作用域链;
2.将arguments和其他命名参数的值添加到函数的活动对象(activation object)
作用域链:当前函数的活动对象优先级最高,外部函数的活动对象次之,外部函数的外部函数的活动对象依次递减,直至作用域链的末端--全局作用域。优先级就是变量查找的先后顺序;
先来看个普通的作用域链:
function sayName(name){ return name; } var say = sayName('jozo');
这段代码包含两个作用域:a.全局作用域;b.sayName函数的作用域,也就是只有两个变量对象,当执行到对应的执行环境时,该变量对象会成为活动对象,并被推入到执行环境作用域链的前端,也就是成为优先级最高的那个。 看图说话:
这图在JS高级程序设计书上也有,我重新绘了遍。
在创建sayName()函数时,会创建一个预先包含变量对象的作用域链,也就是图中索引为1的作用域链,并且被保存到内部的[[Scope]]属性中,当调用sayName()函数的时候,会创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起作用域链,此后,又有一个活动对象(图中索引为0)被创建,并被推入执行环境作用域链的前端。
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但是,闭包的情况又有所不同 :
再来看看看闭包的作用域链:
function sayName(name){ return function(){ return name; } } var say = sayName('jozo');
这个闭包实例比上一个例子多了一个匿名函数的作用域:
在匿名函数从sayName()函数中被返回后,它的作用域链被初始化为包含sayName()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在sayName()中定义的所有变量和参数,更为重要的是,sayName()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链依然在引用这个活动对象,换句话说,sayName()函数执行完后,其执行环境的作用域链会被销毁,但他的活动对象会留在内存中,知道匿名函数会销毁。这个也是后面要讲到的内存泄露的问题。
作用域链问题不写那么多了,写书上的东西也很累 o(╯□╰)o
3. 闭包的实例
实例1:实现累加