闭包的定义
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。
在 JavaScript 中,广义闭包 就是每当创建一个函数,闭包就会被创建出来,因为他有一个父词法环境。狭义闭包 就是一个内层函数中访问到其外层函数的作用域(执行上下文已经出栈,但是其中的词法环境还在引用)
在一些浏览器中(例如Chorme浏览器),他的V8引擎对闭包有些优化,在词法环境中没有用到的变量是会被回收的。(用哪个留哪一个)
举个例子
1 var a=1;
2 function one(){//创建one的词法环境
3 var b=2;
4 function two() {//创建two的词法环境
5 console.log(a,b)
6 }
7 return two;
8 }
9 let two = one;
10 two();
整体的执行过程:
- 先执行全局代码,创建全局执行上下文,并将其压入栈中。此时的全局执行上下文指向的是
0x000002
,这个0x000002
指向的对象包括了全局词法环境和this,在这个全局词法环境创建的变量a赋值为1。 - 先编译函数
one
,创建one的词法环境,登记函数one
以及one
函数中的变量,此时b
初始化为undefined
,创建函数two
的实例twoFun
(let twoFun = newFunctionInstance('two', 'console.log(a,b)', scope)
,这里的scope
指的是one的词法环境
)。 - 然后执行函数
one
,one执行上下文入栈。将b
赋值为2,将two
赋值为twoFun
。执行完函数one
之后,one执行上下文出栈。现在⭐️2
就被清空了,但⭐️3
还被保留着,此时⭐️3
就是一个狭义闭包,因为在代码第9、10行会被用到。 - 执行two函数的之前先编译,创建two的词法环境,然后two的执行上下文入栈,执行完之后出栈。
闭包的应用
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。
定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。
例如:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
当然防抖、节流的原理也是用了闭包。又比如说,我们封装一个公共Fun(case)的方法,里面有不同种情况,每种情况返回一个函数(当然函数里都会调用这个公共方法下的变量),然后我们调用Fun的时候就返回相应的执行函数,然后就可以执行。