一文带你彻底攻克JavaScript闭包

0 阅读4分钟

闭包是JavaScript中一个强大且迷人的特性,也是无数前端面试题中必考题,很多人以为“函数里面嵌套一个函数”就能够形成闭包,但是对它的底层逻辑原理和内存影响。本文将从V8的作用域链机制出发,带你彻底了解什么是闭包以及它的优缺点。

一.作用域链:V8查找变量的“导航地图”

在JavaScript执行代码时,V8引擎会为每个函数创建一个执行上下文,每个执行上下文中都有一个变量环境,用来存放声明的变量和函数。

而且每一个执行上下文的变量环境中都存在一个 outer 指针,用来指向外部的执行上下文,当 V8 查找一个变量时,在当前执行上下文中没有找到,就会顺着 outer 所指向的那个执行上下文查找,以此类推直到找到全局为止,我们把这个查找的链条称为作用域链

let count = 1;
function main() {
  let count = 2;
  function bar() {
    let count = 3;
    function foo() {
      console.log(count);//3  当前找不到,去所指的上下文bar中找,找到count值为3。
    }
    foo();
  }
  bar();
}
main();

在上述代码中,foo函数的作用域链为:foo作用域->bar作用域->main作用域->全局作用域。

二.闭包的本质:“冻结”变量的集合

一般情况下,一个函数执行完毕后,它的执行上下文会被销毁,变量环境中存储的内容也会随之消失。

但是根据作用域的查找规则,内部函数一定可以访问外部函数中的变量,当一个外部函数中的内部函数被拿到外部函数之外来执行,哪怕外部函数执行完,被内部函数所引用的那部分变量依然需要被保留,我们把这一部分变量的集合称之为闭包

换句话说,闭包不是整个外部函数的作用域,而是被内部函数所引用的变量的“集合”

举个例子让你更加清晰的了解闭包

function foo() {
  var myName = "张老师";//这个变量会被闭包保留
  var age = 18;
  function bar() {
    console.log(myName);
  }
  return bar;//bar函数被调用到外部
}

var baz = foo();//foo函数执行完,它的执行上下文本该被销毁
baz();

当foo函数执行完毕时,一般情况下,它的执行上下文本该被销毁,变量myName本该消失,但我们把内部函数bar返回并赋值给了全局变量baz,而bar函数还引用着myName变量,所以V8引擎会把这些被引用的变量(这里指myName)保留下来,保存这些变量的集合就称之为闭包。

三.闭包的形成条件

1. 函数嵌套:外部函数中嵌套一个内部函数
2. 内部函数引用了外部函数的变量
3. 内部函数被“带出”了外部函数之外

多闭包和变量选择

V8引擎十分的“机智”,它只会保留内部函数所引用的变量,而不是整个作用域。这样既实现了闭包的功能又能有效的防止了栈的内存不够。

四.闭包的优缺点

优点:定义私有模块,防止全局变量的污染。
缺点:内存泄露。

如果在代码中不停的创建闭包,那些所需要被保留的引用变量就会越积越多,导致调用栈的内存越来越少,内存泄露。

因此我们应该避免使用过多的闭包,以及避免在闭包中使用过大的数据对象。

那么我们来实践一下,看一道经典的面试题

var arr = [];

for (var i = 1; i <= 5; i++) {
  function fn(j) {
    arr.push(function a() {
      console.log(j);
    });
  }
  fn(i);//每一次循环的i值都被保留,形成闭包
}

for (let n = 0; n < arr.length; n++) {
  arr[n](); 
}//1 2 3 4 5

a函数被拿到外部函数之外,a函数需要使用变量j,fn函数的执行上下文中形参与实参统一后j的值就为i,所以a函数所需的引用变量为i,所以fn函数中的i值被保留在闭包中。

五.总结

要点描述
作用域链每个执行上下文都有outer指针,形成从内到外的变量查找链条
闭包本质外部函数执行完毕后,被内部函数引用的那部分变量集合
优点实现私有模块、避免全局污染
缺点滥用会导致内存占用增加(调用栈内存越来越少)

闭包不是洪水猛兽,而是JS语言词法作用域机制的自然产物,当你理解了 outer 指针,执行上下文的生命周期和V8的变量保留机制,你就会发现闭包是如此的简单。

用好闭包,你的代码会更有生命力;滥用闭包,内存会向你发出警告。 所以我们要注意把握使用力度,把握好平衡。