看过很多的关于闭包的讲解,写个进阶总结吧。
闭包在《JavaScript权威指南》定义是:函数对象本身和这个函数关联作用域链的结合。
想理解上面👆的话就要知道在JavaScript作用域相关的有:
- 变量作用域
- 全局作用域
- 本地作用域
- 函数作用域
函数作用域可以决定变量的作用域范围。一个在函数中被定义的变量,在这个函数体中,以及该函数体中嵌套定义的所有函数内部都可见。另外和同名本地变量覆盖全局变量同理,嵌套函数中的同名本地变量也会覆盖其上层函数中的本地变量。
其实变量的本质就是通过对象来组织到一起的属性集合。定义全局变量就是在全局对象(window)上定义了一个属性
var a = {};
window.a ==== a //true
所以对于本地变量的话,相当于某个对象的属性,只是没有办法获取到这个对象。这个对象被称为 调用对象 或者 声明上下文 。
引出作用域链
为了可以定位到具体指向哪个变量,就需要作用域链。
作用域链:JavaScript每一个代码块都会与一个作用域链相关联,这个链是一个对象列表,对于每一个标识符,都依次从这个链的对象中查找具有相同标识符的属性。而作用域链就是由全局变量和不定数量的由函数调用产生的调用对象构成的列表
var x = 1;
var y = 2;
// 代码执行到此,为了确定x和y,查询其作用域链: [ window ],从 window.x 和 window.y 中取出了值
console.log( x + y );
function fnA(){
// 函数被调用,产生了fnA的调用对象(即为a),用于查询变量的作用域链为:[ a, window ]
var x = 2;
// a.x 定位到x,a.y不存在,继续从 window.y 中定位到y
console.log( x + y );
}
fn();
注意函数刚定义的时候还没有调用对象存在,所以当函数嵌套多的话,作用域链就很长
引出词法作用域
词法作用域(lexical scoping)是指,函数在执行时,使用的是它被定义时的作用域,而不是这个函数被调用时的作用域。
var scope = 'global scope';
function checkScope(){
var scope = 'local scope';
return function(){
console.log( scope );
}
}
var f = checkScope();
f(); // local scope
实际该函数执行时‘fn()’时作用域中的‘scope’是 ‘global scope’ 但实际执行使用的是定义函数时那个‘local scope‘,因为函数在执行时使用的作用域链是它在定义时绑定的那个作用域链(准确地说是使用当时绑定的那个作用域链加上新建的调用对象组成的新的作用域链),而不是函数调用时所处上下文的作用域链。
最后理解闭包后,闭包的强大之处其实在于:JavaScript中的函数,通过作用域链和词法作用域两者的特性,将该函数定义时的所处的作用域中的相关函数进行了捕获和保存,从而可以在完全不同的上下文中进行引用。
应用场景
保护函数内的变量安全:如迭代器、生成器。
在内存中维持变量:如果缓存数据、柯里化。