说起闭包,它是JavaScript重要的核心技术之一,在面试以及实际应用当中,我们都离不开它们。要理解闭包,首先我们要了解一下执行环境和作用域链这两个重要概念。
一、执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。执行环境主要包括全局环境和局部环境。
请看下面例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var color="blue";
function changecolor(){
var anothercolor="red";
function swaprcolors(){
var tempcolor=anothercolor;
anothercolor=color;
color=tempcolor;
}
swapcolors();
}
changecolor();
1、第一步,首先是全局上下文入栈。
2、全局上下文入栈之后,其中的可执行代码开始执行,直到遇到了changeColor(),changeColor会创建它自己的执行上下文,因此第二步就是changeColor的执行上下文入栈。
3、changeColor的上下文入栈之后,控制器开始执行其中的代码,遇到swapcolors()之后又创建了一个执行上下文。因此第三步是swapcolors的执行上下文入栈。
4、在swapcolors的可执行代码中,再没有遇到其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapcolors的上下文从栈中弹出。
5、swapcolors的执行上下文弹出之后,继续执行changeColor的代码,也没有再遇到其他执行上下文,顺利执行完毕之后弹出。这样,ECStack中就只剩下全局上下文了。
6、全局上下文在浏览器窗口关闭后出栈。
记住:
1、执行上下文是单线程的
2、执行上下文是同步执行的,只有栈顶的上下文处于执行中,其他上下文需要等待
3、全局上下文只有唯一的一个,它在浏览器关闭时出栈
4、每次某个函数被调用,就会有个新的执行上下文生成
二、作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的最前端,是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。作用域链中的下一个变量对象来自包含的环境,再下一个变量对象则来自下一个包含的环境,这样一直延续到全局执行环境
请看下面例子1
2
3
4
5
6
7
8
9function compare(value1,value2){
if(value1<value2){
return -1
}else{
return 0;
}
}
var result = compare(5,10)
在创建compare这个函数的时候,会创建一个包含全局变量对象的作用域链,这个作用域链被保存在内部的[scope]属性中,当调用compare函数时,会为函数创建一个执行环境,然后通过复制函数的【scope】属性中的对象构建起执行环境的作用域链。然后在执行的时候会有一个compre的活动对象被创建并推入执行环境的作用域链的前端。对于这个例子的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。所以,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存仅保存全局作用域,但是闭包的情况不同
三、闭包
各种专业文献上的”闭包”定义非常抽象,其实闭包就是指有权访问另一个函数作用域中的变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
我们来看个例子
1 | function f1(){ |
四、闭包的用途
1、通过返回值来获取内部函数的引用
1 | function outer() {//获取函数内部的变量 |
2、实现封装
1 | function Person(){ |
3、实现缓存
1 | function configCache(){ |
五、使用闭包时的注意点
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包保存的是包含函数的整个变量对象,所取得的外部对象变量为闭包被调用时的对象变量,一般为外部函数变量的最后一个值
(完)