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()函数的变量对象会被销毁。
闭包的作使用?
事实上,通过用闭包,我们能做很多事情。比方模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。这些需要感兴趣的人自己去实践和探究,此处不逐个列举。
闭包的缺点?
因为闭包会携带包含它的函数的作使用域,因而会比其余函数占使用更多的内存。过度用闭包可可以会导致内存占使用过多,所以要慎重用闭包。