在不久之前,将网上总结的关于闭包的博文大概看了一下,却仍然是云里雾里。事实证明还是自己直接啃书比较好,对照着红宝书,从作用域到闭包在到this,把闭包的原理和应用以及各种坑通过知识点和网上的博文串起来,才总算弄懂闭包的大概。本篇博文是按照自己的理解所写,如果有不正确的地方,麻烦批评指正。
要知道闭包,必须先了解JS中的执行环境与作用域。
在JS中,执行环境分为两类:
window对象
我们都知道,JS中有全局变量,局部变量,那JS是怎么判断哪个是全局变量,哪个是局部变量呢?若全局和局部环境中都存在一个同名变量,怎么确定要用的是哪个?这就是作用域链的用途了。
看一个红宝书上的例子
var color = 'blue'
function changeColor () {
var anotherColor = 'red';
function swapColors() {
var temp = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors()
}
changeColor();
上述例子中有三个执行环境, 当代码由上往下进行解析时,依次会进入一下执行环境:
很容易理解,在swapColors里,是能访问到其上面的两个执行环境中的变量和函数的,而它外面的执行环境,却无法访问其内部的变量与函数。
这些,都是因为有作用域链的存在,上述例子的作用域可以用下图表示
window
|--color
|
|--changeColor()
|
|--anotherColor
|
|--swapColors()
|
|--tempColor
JS在执行代码时,会进入不同的作用域,每进到一个作用域内,都会向上搜索作用域链,查询变量与函数。即刚开始会在自己的执行对象中搜索,如果搜索不到,再沿着作用域链往上搜索。和原型链中对象属性的搜索方式非常像。
如果想在局部执行环境内访问全局对象怎么办?用window.dataName
即可。
红宝书上就一句话:闭包是指有权访问另一个函数作用于中的变量的函数。
这不是跟上面的那个例子一样吗? 没错,闭包正是作用域链所引起的。根据作用域链,就能解释为什么闭包函数能够访问其他函数的作用域。
缘由:闭包只能取得包含函数中任何变量的最后一个值
function createFunction() {
var result = new Array()
for(var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result//都返回10
}
为什么都返回10?
对于里面那个匿名函数来说,访问的变量i是createFunction执行环境中的变量,当进入到自己的执行环境时,i已经结束循环,变成10了。
怎么解决?
function createFunction() {
var result = new Array()
for(var i = 0; i < 10; i++) {
result[i] = function(num) {
return function() {
return num // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
}
}(i)
}
return result//依次返回1到10
//result数组中的每个函数都有自己num变量的一个副本
}
产生缘由:闭包会引用包含函数的整个活动对象
function assignHandler() {
var el = document.getElementById("some");
element.onClick = function () {
alert(element.id)
}
}
这里面的匿名函数只要存在,就会保存对assignHandler的活动对象的引用,所以即使函数已经执行完,assignHandler占用的内存也永远不会被回收。
怎么解决?
function assignHandler() {
var el = document.getElementById("some");
var id = el.id
element.onClick = function () {
alert(id)
}
el = null;
}
产生缘由:每个函数在调用时都会自动取得this和argument这两个特殊变量。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,而不会沿着作用域链向上搜索。
var name = 'window'
var object = {
name: 'My object',
getName: function() {
return function() {
return this.name;
}
}
}
console.log(object.getName()()) // window
因为匿名函数的执行环境具有全局性,因此其this队形通常指向window,在这个例子中里面的闭包函数是在window作用域下执行的,也就是说,this指向window。
怎么解决?
var name = 'window'
var object = {
name: 'My object',
getName: function() {
var that = this;
return function() {
return that.name;
}
}
}
console.log(object.getName()()) // My object
熟悉的var that = this;
。
原理:因为闭包函数可以访问that,而此时that作为object作用域中this指向的副本,that同样也指向object。
(发现好像都是通过建立副本来解决闭包带来的各种问题)
与C语言不一样,在JS中是没有块级作用域的,即在if语句中声明的变量,会将其添加到当前的执行环境中。利用闭包,可以约束块级作用域。在ES6中,也可以利用let和const解决这一问题。
for(var i=0; i<10; i++){
console.info(i)
}
alert(i) // 变量提升,弹出10
//为了避免i的提升可以这样做
(function () {
for(var i=0; i<10; i++){
console.info(i)
}
})()
alert(i) // underfined 因为i随着闭包函数的退出,执行环境销毁,变量回收
参考资料: