【转】js中的闭包
js中为什么要使用闭包?
先介绍一下全局变量和局部变量的优缺点:
全局变量:在全局环境下声明的变量为全局变量,全局变量在任何地方都可访问,且一直保存在内存中只到应用程序退出(关闭网页或浏览器)时才被销毁。但是过多的声明全局变量容易造成全局污染,且全局变量容易被修改。
局部变量:在函数环境下声明的变量为局部变量,局部变量仅在函数内部可访问,当函数执行完毕时就会被销毁。局部变量不会造成全局污染也不容易被修改。
从上面可以看出全局变量和局部变量的优缺点刚好是相对的,闭包的出现正好结合了全局变量和局部变量的优点。闭包可使已经执行结束的函数中的局部变量仍然留在内存中,且能被重复访问使用。
闭包的定义是什么?
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。以一个函数为例:
示例
加粗的两行代码是内部函数(一个匿名函数)中的代码,这两行代码访问了外部函数中的变量propertyName。
示例
在匿名函数从createComparisonFunction()中被返回后,他仍然可以访问在createComparisonFunction()中定义的所有变量。更为重要的是,createComparisonFunction()函数在执行完毕后,其变量对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个变量对象。
什么是作用域?
要理解什么是作用域,要先理解什么是执行环境
1.什么是执行环境(执行上下文)
当代码在JavaScript中运行的时候,代码在环境中被执行是非常重要的,它会被评估为以下之一类型来运行:
全局代码:默认环境,代码第一时间在这儿运行。
函数代码:当执行流进入一个函数体的时候。
Eval代码:在eval()函数中的文本。
来看一个例子:
示例
全局环境由紫色边框表示,还有三个不同的函数环境分别由绿色边框,蓝色边框和橙色边框表示。这里只能有一个全局环境,全局环境可以被其他环境访问。可以有很多的函数环境,每个函数都会创建一个新的函数环境,在新的函数环境中,会创建一个私有作用域,在这个函数中创建的任何声明都不能被当前函数作用域之外的地方访问。
2.执行环境的详情
一个函数被调用就会创建一个新的执行环境。然而解释器的内部,每次调用执行环境会有两个阶段:
1). 创建阶段
- 当函数被调用,但是为执行内部代码之前:
- 创建一个作用域链。
- 创建变量,函数和参数。
- 确定this的值。
2). **/代码执行阶段
> - 赋值,引用函数,解释/执行代码。
这意味着每个执行环境在概念上作为一个对象并带有三个属性
executionContextObj = {
scopeChain: { /* variableObject + all parent execution context's variableObject */ },
//作用域链:{变量对象+所有父执行环境的变量对象}
variableObject: { /* function arguments / parameters, inner variable and function declarations */ },
//变量对象:{函数形参+内部的变量+函数声明(但不包含表达式)}
this: {}
}
看下面例子:
在调用foo(22)时,创建阶段像下面这样:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
创建阶段处理了定义属性的变量名,但是并不把值赋给变量,不包括形参和实参。一旦创建阶段完成,执行流进入函数并且**/代码执行阶段,在函数执行结束之后,看起来像这样:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
上述过程也可以印证了js中的变量提升,即变量和函数声明会被提升到它们函数作用域的顶端。
理解了什么是执行环境和作用域链之后,回到上文所讲的闭包实例。
为什么createComparisonFunction()函数在执行完毕后,其变量对象不会被销毁。
示例
在匿名函数从createComparisonFunction()被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的变量对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的变量对象仍然会留在内存中;只到匿名函数销毁后,createComparisonFunction()函数的变量对象才会被销毁。下图展示了调用compareNames()过程中产生的作用域链之间的关系:
示例
解除对匿名函数的引用,只需compareNames=null即可,此时createComparisonFunction()函数的变量对象会被销毁。
闭包的作用?
事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。这些需要感兴趣的人自己去实践和探索,此处不一一列举。
闭包的缺点?
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以要慎重使用闭包。