JS-重新认识闭包

65 阅读3分钟

闭包的定义

一个函数和对其周围状态(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();

截屏2022-03-01 上午9.45.02.png 整体的执行过程:

  1. 先执行全局代码,创建全局执行上下文,并将其压入栈中。此时的全局执行上下文指向的是0x000002,这个0x000002指向的对象包括了全局词法环境和this,在这个全局词法环境创建的变量a赋值为1。
  2. 先编译函数one,创建one的词法环境,登记函数one以及one函数中的变量,此时b初始化为undefined,创建函数two的实例twoFun(let twoFun = newFunctionInstance('two', 'console.log(a,b)', scope),这里的scope指的是one的词法环境)。
  3. 然后执行函数one,one执行上下文入栈。将b赋值为2,将two赋值为twoFun。执行完函数one之后,one执行上下文出栈。现在⭐️2就被清空了,但⭐️3还被保留着,此时⭐️3就是一个狭义闭包,因为在代码第9、10行会被用到。
  4. 执行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的时候就返回相应的执行函数,然后就可以执行。