JavaScript笔记——闭包

前言

秋招大大小小若干场面试,几乎都问了这个问题,固然不知道闭包也不能说会js。因此,不要逃避,好好地来梳理一下。javascript

闭包是什么?

来看看MDN官网上关于闭包的定义:java

A closure is the combination of a function and the lexical environment within which that function was declared.面试

闭包是函数以及函数声明所在的词法环境的组合。这个定义有点干涩,我的认为能够结合闭包的实际场景简单地这么理解:函数调用结果返回一个子函数,同时该子函数使用了外部函数的局部变量,使得变量保存在内存中,造成了闭包。segmentfault

举个例子?

举个最简单的栗子~设计模式

function func(){
  var count = 0;
  return function(){
    console.log(count++);
  }
}

var fn = func();
fn(); // 0
fn(); // 1

func 函数执行返回一个函数,其中调用了func的局部变量count,致使func函数执行完成后,count变量仍保留在内存空间,未被销毁,造成了闭包。浏览器

闭包的做用?

从上一个例子,能够看到闭包的特色是读取函数内部局部变量,并将局部变量保存在内存,延长其生命周期。利用这个特色可使用闭包实现如下功能:闭包

  1. 解决相似循环绑定事件的问题
    在实际开发中,常常会遇到须要循环绑定事件的需求,好比上一篇博客的例子,在id为container的元素中添加5个按钮,每一个按钮的文案是相应序号,点击打印输出对应序号。
    其中第一个方法很容易错误写成:app

    var container = document.getElementById('container');
    for(var i = 1; i <= 5; i++) {
     var btn = document.createElement('button'),
         text = document.createTextNode(i);
     btn.appendChild(text);
     btn.addEventListener('click', function(){
       console.log(i);
     })
     container.appendChild(btn);
    }

    虽然给不一样的按钮分别绑定了事件函数,可是5个函数其实共享了一个变量 i。因为点击事件在 js 代码执行完成以后发生,此时的变量 i 值为6,因此每一个按钮点击打印输出都是6。
    为了解决这个问题,咱们能够修改代码,给各个点击事件函数创建独立的闭包,保持不一样状态的i。函数

    var container = document.getElementById('container');
    for(var i = 1; i <= 5; i++) {
     (function(_i) {
       var btn = document.createElement('button'),
           text = document.createTextNode(_i);
       btn.appendChild(text);
       btn.addEventListener('click', function(){
         console.log(_i);
       })
       container.appendChild(btn);
     })(i);
    }

    注:解决这个问题更好的方法是使用 ES6 的 let,声明块级的局部变量。 性能

  2. 封装私有变量

    经典的计数器例子:

    function makeCounter() {
      var value = 0;
      return {
        getValue: function() {
          return value;
        },
        increment: function() {
          value++;
        },
        decrement: function() {
          value--;
        }
      }
    }
    
    var a = makeCounter();
    var b = makeCounter();
    b.increment();
    b.increment();
    b.decrement();
    b.getValue(); // 1
    a.getValue(); // 0
    a.value; // undefined

    每次调用makeCounter函数,环境是不相同的,因此对b进行的increment/decrement操做不会影响a的value属性。同时,对value属性,只能经过getValue方法进行访问,而不能直接经过value属性进行访问。

闭包的问题?

使用闭包会将局部变量保持在内存中,因此会占用大量内存,影响性能。因此在再也不须要使用这些局部变量的时候,应该手动将这些变量设置为null, 使变量能被回收。

当闭包的做用域中保存一些DOM节点时,较容易出现循环引用,可能会形成内存泄漏。缘由是在IE9如下的浏览器中,因为BOM 和DOM中的对象是使用C++以COM 对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略,当出现循环引用时,会致使对象没法被回收。固然,一样能够经过设置变量为null解决。

举例以下:

function func() {
  var element = document.getElementById('test');
  element.onClick = function() {
      console.log(element.id);
  };
}

func 函数为 element 添加了闭包点击事件,匿名函数中又对element进行了引用,使得 element 的引用始终不为0。解决办法是使用变量保存所需内容,并在退出函数时将 element 置为 null。

function func() {
  var element = document.getElementById('test'),
      id = element.id;
  element.onClick = function() {
      console.log(id);
  };
  element = null;
}

参考文章

相关文章
相关标签/搜索