闭包该怎么理解呢?

421 阅读4分钟

前言

怀着一颗忐忑的心来写这篇文章,因为今天写的是闭包,关于闭包的理解可能大家都非常理解了,也怕写的不好,误导了部分的初学者,希望下面内容有任何写的不好的地方,欢迎大家积极的指出来,真正的进步就是大家共同进步。也能够让自己成长起来,这也是我写坚持写博客的原因之一。

好了,废话闲说,我们进入我们的正题:闭包

function Car () {
  let wheel = 4;
  function drive () {
    console.log(wheel + ' wheel run');
  }
  return drive;
}

let drive = new Car();
drive(); // 4 wheel run

大家肯定都写过类似的代码,这段代码使用了闭包;那么到底闭包是什么?如何分析出闭包为什么产生和闭包到底在哪里?

什么是闭包?

闭包是由函数以及创建该函数的词法环境组合而成,闭包具备的几点要素

  1. 闭包一定是个函数对象
  2. 闭包和词法作用域、作用域链、垃圾回收机制息息相关
  3. 当函数一定是在其定义的作用域外进行访问时,才产生闭包
  4. 闭包是由该函数和其上层执行的上下文共同构成

为了让大家清晰的看到,我们借助chrome的调试工具看下闭包产生的过程。 


 当js执行到 let drive = new Car();代码,在执行car()时,此时的Call Stack只有全局上下文。


 接下来执行car(); 我们可以看到,此时的drive进入了Call Stack,并且Closure(Car) 形成了。

通过调试,我们看到当函数car执行时,闭包才产生,而封闭的空间Car时闭包。

闭包是js中的一个重要的存在,它能让很多不可能实现成为可能,但是闭包虽好,但是也不能乱用,有的时候也会适得其反。

闭包导致的内存泄漏

JS分配给浏览器的可用内存数量通常比分配给桌面应用程序的少,主要是防止js的网页全部讲系统内存耗尽。我们要想让页面具备更好的性能就必须保证页面占用最少的内存,也就是说,我们应该保证执行的代码只保存有用的数据,一旦数据不再有用,我们就该进行垃圾回收,释放内存。闭包是阻止垃圾回收的,因此变量永远存在内存中。当变量不再被使用时,会造成内存泄漏,会影响页面的性能。

因此当变量对象不再使用的时候,我们应该将其释放掉。

function Car () {
  let wheel = 4;
  function drive () {
    console.log(wheel + ' wheel run');
  }
  return drive;
}

let drive = new Car();
drive(); // 4 wheel run
drive = null; // 在drive不再使用,将其指向对象释放

闭包的应用方法

  1. 模块
var module = (function (window, undefined) {
  let name = '王小端Coder';
  function getName () {
    return name;
  }
  return {
    name,
    getName
  }
})(window);

console.log(module.name); // 王小端Coder
console.log(module.getName()); // 王小端Coder

一个模块具有私有属性、私有方法和公有属性、公有方法,而闭包能很好的将模块的公有属性、方法暴露出来。return关键字将对象引用赋值给module,从而使用的闭包。

  • 延时器、计时器
for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000 * i);
}

在上面这个例子中我们实际上想依次输出 0,1,2,3,4;而不是现在的输出5个5。下面我们进行改造。

for (var i = 0;i < 5; i++) {
  (j => {
    setTimeout(() => {
      console.log(j);
    }, 1000 * j);
  })(i)
}

在setTimeout中应用了闭包,使其内部能够记住每次循环所在的词法作用域和作用域链。由于setTimeout中的回调函数会在当前任务队列的尾部进行执行,因此上面第一个例子中每次循环中的setTimeout回调函数记住的i的值是for循环作用域中的值,此时都是5,而第二个例子记住的i的数为setTimeout的父级作用域自执行函数中的j的值,依次为0,1,2,3,4。

  • 数据缓存
var fn = function () {
  var sum = 0;
  for (var i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  return sum;
}

这个函数是返回传递参数之和,如果每次传递参数一致。结果返回都是一致的,就会造成浪费。如果改造这个函数,提高函数的性能。

var fn = (function () {
  var cache = {};
  return function () {
    var str = JSON.stringify(arguments);
    if (cache[str]) {
      return cache[str];
    } else {
      var sum = 0;
      for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
      }
      return cache[str] = sum;
    }
  }
})();

上面的示例将计算后的结果缓存到局部变量cache当中,在调用这个函数时,先在缓存中查找,如果找不到,则进行计算,然后将结果放到缓存中并返回,如果找到了,直接返回查找到的值。

如有对闭包的理解不正确的地方欢迎大家指正。谢谢!