写在前边: 咱们知道,当函数执行时,会造成本身的执行期上下文,并把它挂到本身的做用域链上,当函数执行完以后,它的执行期上下文就会被释放。因此,通常状况下,在函数外部访问函数内部的变量须要特殊的方法才能解决,这个特殊的方法就是闭包。es6
在理解闭包前,我建议你先了解下js的做用域。 理解js中的做用域面试
闭包:闭包指的是在函数的外部能够访问函数内部的变量。函数没有被释放,整条做用域链上的局部变量都将获得保留。建立闭包的通常方法是在函数内部返回一个新函数。bash
通俗的理解: 闭包:顾名思义,是一个封闭的包,可是这个包露出内部的一条线,这条线就是闭包内部返回函数的做用域链,它上面挂载了这个函数以及他的全部父级函数的变量,咱们能够经过这条线访问到函数内的变量,这就是闭包。闭包
咱们知道: 当一个函数被定义时,它的scope属性会指向他的父级的scope的引用 当一个函数执行时,会造成它本身的执行期上下文(AO),并把它挂载到他的做用域(scope chain)的最顶端,它的父级的scope依次下移。 当函数执行完毕后,他本身的执行期上下文(AO)会被销毁 关于scope、AO详情见理解js中的做用域异步
这样,当咱们在函数内部返回一个函数并在其外部被一个变量接收时,它的做用域链上存的是它的父级的做用域链,只要这个函数存在则它的做用域链就会一直存在,这样它的做用域链上的变量得不到释放,即能在函数外部访问做用域内部的变量。函数
为了便于理解,咱们举个简单的例子:post
function test(){
var a = 100
function b(){
a++
console.log(a)
}
return b
}
var global = 100
var c = test()
c() // 101
c() // 102
复制代码
1.当定义并执行test函数时,它的做用域链指向它的AO以及全局的GO 性能
闭包的做用通常有两个: 1.能够在函数外部,使用用函数内部的变量 2.函数内部的变量不会被释放。ui
因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,this
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
复制代码
输出结果都是3。为何?
1.当执行完for循环后,此时的全局执行期上下文为
GO:{
data:[...],
i:3
}
复制代码
2.当执行data[0]时,它产生它本身的执行期上下文(AO),此时他的做用域链为
scope:[AO,GO]
复制代码
此时,它的AO上没有i变量,就向它的上一级的执行期上下文中找,即以上的GO,因此输出结果为3
其余两个执行结果同理。
当咱们将其修改成闭包时,即以下代码
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (j) {
return function(){
console.log(j);
}
})(i)
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
复制代码
此时,他的执行结果分别为0,1,2
当执行完for循环时,此时的GO为
GO:{
data:[...],
i:3
}
复制代码
当data[0]执行时,此时他的做用域链为
scope:[AO,匿名函数的AO,GO]
复制代码
而此时匿名函数的AO为
AO:{
i:0
}
复制代码
data[0]的AO中没有变量i,因此它沿着做用域链向上寻找,找到匿名函数的AO,即此时i为0.
执行data[1],data[2]同理
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
console.log(i);
复制代码
以上这道题是在面试中常常问到的问题,那么它输出的是什么呢?相信大多数朋友均可以知道,最后他的结果为 5,5,5,5,5,5。 只要了解了js的运行机制、以及同步异步的问题,咱们横容易知道第一个5是当即输出,以后的5在1s后同时输出。
那么咱们将它改造为闭包。
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function() {
console.log(i);
}, 1000);
})(i)
}
console.log(i);
复制代码
它的结果为5,0,1,2,3,4 咱们分析下它的做用域。
首先,先定义了5个当即执行函数,而后执行循环外部的console,此时的GO为{i:5},因此先输出5 1s后5个当即执行函数同时执行,此时定时器内部的i为其外部函数(即当即执行函数)的i,此时i分别为0,1,2,3,4,因此输出为5,0,1,2,3,4
想要那么有没有什么其它方法来改造呢?答案是有的,es6里提供了一个叫let的东西,他会造成块级做用域。
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
console.log(i);
复制代码
以上代码会报错,由于最后的i是不存在的,由于let造成了块级做用域,只在for循环内部起做用。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // The Window
复制代码
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); // My Object
复制代码
上面这两道题,考察了闭包的用法以及this的指向问题,这里就很少作解释了。
相信看过个人这篇文章 关于js中的this指向问题,以后就能够搞明白了。