认识闭包:
闭包,是指有权限访问到其他函数作用域的变量的函数
给一个例子:
function outer () {
var name = 'HelloTF';
return function () {
console.log (name);
}
}
var getName = outer();
getName();
//HelloTF
函数outer执行后返回一个内部的匿名函数,并把返回的结果赋值给全局变量result,在全局环境中在此执行result,得到了outer函数内部的变量name。
仅从闭包的定义来看并没有什么感觉,不妨从中摘出几个关键词,结合JavaScript的原理:作用域,作用链,活动对象,变量对象
作用域
在ES6之前,JavaScript没有块级作用域,经常会遇到变量提升了或者使用闭包的时候出错的问题。ES6为了解决这些问题,添加了块级作用域,此处稍后再谈。ES5中它的作用域只有两个——全局作用域和函数作用域。
全局作用域,是在全局都可以访问的,对前端开发者而言,一般指window;
函数作用域,即仅在函数{}内的代码区域,在函数内部声明的变量,在函数外是不可访问的。
这样就可以保证函数和变量的有序访问。在上述例子中,函数内部的name变量在函数体外无法直接访问,若我们必须要访问name,就得使用闭包,即返回一个匿名函数,输出name。
JavaScript保证当前执行环境下变量和函数的有序访问的原理——作用域链
不论代码是在全局还是函数的执行环境中,都会在自己的执行环境中创建一个变量对象,这个变量对象包含了当前执行环境中声明的一切变量。
比如有这么一个例子:
var outer = 'TTF';
function outerFun () {
var inner = '老汤';
function innerFun () {
var finaler = '汤瓜皮';
}
}
outerFun()
以上代码有三个执行环境——全局执行环境、函数outerFun和函数innerFun的执行环境。
在全局环境中,有一个全局执行环境的变量对象(设为Q),它包含了全局执行环境下声明的一切变量。即Q:{outer,outerFun},作用域链的第一位就是Q,且Q始终存在,直到浏览器关闭的那一时刻才销毁。
在函数的执行环境中,也存在着自己的变量对象(称之为活动对象),它包含了一切在该函数的执行环境中声明的变量。如:函数outerFun,H1:{inner,innerFun};函数innerFun,H2:{finaler}。
分析函数的执行过程:
第一步,在函数创建时,就存在了一个函数的作用域链。
第二步,在函数执行时,函数创建活动对象,在函数执行的过程中,活动对象被推入到作用域链的第一位。
(函数的执行过程参考博客:https://www.cnblogs.com/bobo-site/p/9831554.html)
作用域链在函数创建的那一刻就创建了,活动对象在函数执行的那一刻起才存在(执行完毕立刻销毁)
结合上述例子做一个草图:
当函数执行时,自身的活动对象被推入到函数的作用域链的第一位,而后是这个函数的外层函数的活动对象占据第二位,再然后这个函数的外层函数的外层函数的活动对象占据第三位,以此类推,直到最后作用域链的最后一位是全局变量对象。
修改之前的代码,将变量打印出来
function outerFun () {
var inner = '老汤';
console.log(inner)
function innerFun () {
var finaler = '汤瓜皮';
console.log(finaler)
}
}
outerFun()//老汤
最终结果不会打印“汤瓜皮”。
结合上述逻辑,函数outerFun执行时,自身活动变量推至第一位,第二位则是全局变量,因此它无法访问内部函数innerFun的变量函数innerFun只是定义而未执行。
通过这个例子可以总结:
1、函数执行时,自身的活动变量会在作用域链中占据第一位;
2、函数将按照由内至外的顺序,将外层的变量对象或者活动对象依次推入作用域链中吗。直到最后一位是全局变量对象;
3、在调用函数的变量时,JavaScript会顺着作用域链从第一位开始查找,直到在某一位找到该变量或者因为找不到而报错。
这也正是要使用闭包的原因——由于不能“从外到内”地访问作用域链上的变量对象或者活动对象,我们必须使用闭包这样的方法让一个函数可以去访问内部函数的变量并赋给外部以便外部可以访问。
以上为个人理解,如有不对,请指出!
来源:oschina
链接:https://my.oschina.net/u/4270348/blog/3458078