关于闭包的定义我看到过好多个版本,这里简单的列举一下:
MDN:包是函数和声明该函数的词法环境的组合。(PS:我的理解词法环境就是变量对象)
Tyler McGinnis:子函数在其父级函数的变量环境上“关闭”(译者注:原文为a child function “closing” over the variable environment of its parent function)的概念,就叫作闭包。
w3school:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可使用函数以外定义的变量。
阮一峰:他的理解是,闭包就是可以读取其余函数内部变量的函数。因为在Javascript语言中,只有函数内部的子函数才能读取局部变量,所以能够把闭包简单理解成"定义在一个函数内部的函数"。
一次性搞懂 JavaScript 闭包 —— 简书:闭包简单来讲就是一个函数访问了它的外部变量。javascript
还有《JavaScript高级编程语言》,《JavaScript权威指南》亲有没有发现每一个人感受都给闭包有一个定义,若是你是一个小白你必定和我同样的郁闷( ˇˍˇ )。
最近了解了一下JavaScript执行上下文以后才忽然发现这么多概念原来讲的其实都是一件事。若是你看了以后和我当初同样,但愿我下面的内容能够帮助你,进入正题。html
执行上下文是用来帮助Javascript引擎管理整个解析和运行代码的复杂过程。那么如今咱们了解了执行上下文的存在目的,下一个问题就是执行上下文是怎么建立的?它们由什么组成?java
概念:当且仅当Javascript引擎首次开始解析代码(对应全局执行上下文)或当一个函数被调用时,才会建立执行上下文。git
全局执行上下文:当Javascript引擎运行代码,第一个被建立的执行上下文叫作“全局执行上下文”。最初,这个全局上下文由这二位组成:一个全局对象和一个this变量。this引用的是全局对象,若是在浏览器中运行Javascript,那么这个全局对象就是window对象,若是在Node环境中运行,这个全局对象就是global对象。 在全局执行上下文的建立阶段,Javascript引擎会:github
函数执行上下文:当函数被调用,它就被建立出来了。函数执行上下文中应该建立的应该是arguments对象,因此当建立函数执行上下文时,Javascript引擎会:编程
关于变量对象的建立有什么疑问能够看看JavaScript深刻之变量对象 json
举个栗子:咱们来讲明一下:
实际操做浏览器
这里有几处重要细节须要注意。首先,传入函数的全部参数都做为局部变量存在于该函数的执行上下文中。在例子中,handle同时存在与全局执行上下文和getURL执行上下文中,由于咱们把它传入了getURL函数作为参数。其次,在函数中声明的变量存在于函数的执行上下文中。闭包
做用域链:Javascript中一切皆对象,这些对象有一个[[Scope]]属性,该属性包含了函数被建立时的做用域中对象的集合,这个集合被称为函数的做用域链(Scope Chain),它决定了哪些数据能被函数访问。当函数建立的时候,它的[[scope]]属性自动添加好全局做用域。之因此要强调建立是由于JavaScript采用词法做用域(lexical scoping),也就是静态做用域.编程语言
举个栗子:咱们来经过简单的代码说明一下做用域链:
实际操做
function a () {
console.log('In fn a')
function b () {
console.log('In fn b')
function c () {
console.log('In fn c')
}
c()
}
b()
}
a()
复制代码
//c的做用域链
[
0:{
arguments:{length:0},
this:window
},
1:{
arguments:{length:0},
this:window,
c:fn()
}
2:{
arguments:{length:0},
this:window,
b:fn()
},
3:{
this:window,
a:fn()
}
]
复制代码
细心观察你会发现每一个函数执行完以后,每一个函数的执行上下文会消失,事实上,Javascript引擎建立了一个叫“执行栈”(也叫调用栈)的东西。每当函数被调用,就建立一个新的执行上下文并把它加入到调用栈;每当一个函数运行完毕,就被从调用栈中弹出来。因此“一般状况下”函数执行完毕后函数的执行上下文就会消失,闭包就是否是“一般状况下”。
问题来了什么叫作词法做用域(也能够说静态做用域)?
咱们在举个栗子:
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
// 结果是 ???
复制代码
答案是 1 (小伙伴你答对了么?)
解释:因也很简单,由于JavaScript采用的是词法做用域(若是不明白能够看看《JavaScript深刻之词法做用域和动态做用域》),函数的做用域基于函数建立的位置。函数foo() 定义在全局做用域下,当打印value时沿着做用于链查找就找到了全局执行上下文,而不是bar函数执行上下文。因此结果是1。
而引用《JavaScript权威指南》的回答就是: JavaScript 函数的执行用到了做用域链,这个做用域链是在函数定义的时候建立的。嵌套的函数 f() 定义在这个做用域链里,其中的变量 scope 必定是局部变量,无论什么时候何地执行函数 f(),这种绑定在执行 f() 时依然有效。
闭包就是否是“一般状况下”,若是你在一个函数中嵌入了另外一个函数,而且让外部一个指针引用内部的函数,例外状况就产生了。这种函数套函数的状况下,即便父级函数的执行上下文从调用栈弹出了,子级函数仍然可以访问父级函数的做用域。
实际操做
makeAdder执行上下文从调用栈弹出后,Javascript Visualizer建立了一个Closure Scope(闭包做用域)。Closure Scope中的变量环境和makeAdder执行上下文中的变量环境相同。这是由于咱们在函数中嵌入了另外一个函数。在本例中,inner函数嵌在makeAdder中,因此inner在makeAdder变量环境的基础上建立了一个闭包。由于闭包做用域的存在,即便makeAdder已经从调用栈弹出了,inner仍然可以访问到x变量(经过做用域链)。
如今是否是感受本身明白了一点什么是闭包呢?反正闭包的定义我还下不了,可是我仍是要粗略的表达一下我本身的想法就是:闭包就是使一个函数做为另外一个函数的返回,从而达到内部函数能够读取外部函数内部的变量和让外部函数中的变量的值始终保持在内存中的做用的一个写法。(PS.不知道你们可不能够接受,不喜勿喷!!!)
function func() {
var arr = [];
for(var i = 0;i<3;i++){
arr.push(()=> {
console.log(i);
})
}
return arr
}
var result = func();
result.forEach((item)=> {
item();
})
复制代码
输出结果是:3 3 3 有没有答对呢 ?
解释:首先在func函数执行上下文中建立了 i 变量(这里涉及到变量提高的知识,不了解本身能够看一下),当执行匿名的函数要console.log(i)的时候发如今改匿名函数的执行上下文没有这个变量,则沿着做用域链向上查找,发如今func的做用域中有i,这个i的值是3(for循环最后结束后i记录为3)。
咱们使用闭包进行以下修改,亲,你再猜猜?
function func() {
var arr = [];
for(var i = 0;i<3;i++){
(function(){
arr.push(()=> {
console.log(i);
})
})()
}
return arr
}
var result = func();
result.forEach((item)=> {
item();
})
复制代码
答案是 3 3 3,解释和上面的同样。若是你想输出 0 ,1 ,2 有两种方案:闭包和使用let
//方案一
function func() {
var arr = [];
for(var i = 0;i<3;i++){
(function(i){
arr.push(()=> {
console.log(i);
})
})(i)
}
return arr
}
var result = func();
result.forEach((item)=> {
item();
})
//输出 0 1 2
//方案二
function func() {
var arr = [];
for(let i = 0;i<3;i++){
arr.push(()=> {
console.log(i);
})
}
return arr
}
var result = func();
result.forEach((item)=> {
item();
})
//输出 0 1 2
复制代码
因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露。解决方法是,在退出函数以前,将不使用的局部变量所有删除。
闭包会在父函数外部,改变父函数内部变量的值。因此,若是你把父函数看成对象(object)使用,把闭包看成它的公用方法(Public Method),把内部变量看成它的私有属性(private value),这时必定要当心,不要随便改变父函数内部变量的值。
思考题:
代码一
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
复制代码
javascript 中this的定义:就是上下文对象,即被调用函数所处的环境,也就是说,this 在函数内部指向了调用函数的对象。若是没有搞懂就去研究一下javascript的this吧
后面发现好的闭包的内容我还会加进来,若是有什么不对的地方欢迎指正。