美文网首页
Javascript闭包理解

Javascript闭包理解

作者: troublemak_19be | 来源:发表于2018-11-24 16:47 被阅读0次
    何为闭包?

    闭包(Closure)是一个封闭的作用域,它可以访问外部作用域的变量。

    说起来比较抽象,实际上闭包就是一个函数,函数内部可以访问外部的变量,比如下面这个例子:

    function sayHello() {
      const greet = 'hello world';
      function helloFunc() {
        console.log(greet);
      }
      helloFunc();
    }
    sayHello(); // hello world
    

    helloFunc函数内虽然没有定义greet变量,但是它的外层函数sayHello函数里定义了greet变量,所以最后成功输出hello world,这个例子里面helloFunc就是一个闭包,寻找变量的过程是按照作用域链来寻找的。

    闭包的作用

    我们知道javascript外部作用域无法访问内部作用的值。

    function func() {
      var name = 'jack';
    }
    console.log(name);  // undefined
    

    但是函数内部却可以访问外部变量。

    var name = 'jack';
    function func() {
      console.log(name);
    }
    func(); // jack
    

    所以函数内部可以修改外部状态,我们可以让函数拥有状态,比如迭代器生成器

    var add = (function() {
      var counter = 0;
      return function() {
        counter++;
        return counter;
      };
    })();
    add(); // 1
    add(); // 2
    add(); // 3
    

    这个例子我们使用了一个立即执行函数表达式(IIFE)创建了一个匿名函数也就是一个闭包,函数返回的函数会引用这个匿名函数的局部变量counter,所以我们每次调用add函数输出的值都不一样,add函数因为闭包有了状态。

    这里使用立即执行函数主要是为了直接返回最终函数,也可以返回个普通函数像下面这样:

    var genAddFunc = function() {
      var counter = 0;
      return function() {
        counter++;
        return counter;
      }
    }
    var add = genAddFunc();
    add(); // 1
    add(); // 2
    add(); // 3
    
    闭包的应用

    比如我们需要对多个li绑定点击事件如下:

    <html>
      <body>
        <ul id="itemList">
          <li>item 1</li>
          <li>item 2</li>
          <li>item 3</li>
        </ul>
      </body>
    </html>
    
    const itemList = document.getElementById('itemList');
    const items = itemList.getElementsByTagName('li');
    for (var i = 0; i < items.length; i++) {
      items[i].onclick = function() {
        console.log(`item ${i} clicked`);
      }
    }
    items[0].click(); // item 3 clicked
    items[1].click(); // item 3 clicked
    items[2].click(); // item 3 clicked
    

    我们发现虽然li的点击事件都已经创建了闭包记住了i的值,但是都是输出3而不是0,1,2这是为什么呢?
    原因是for循环中大括号包含该的部分并不是一个封闭的作用域,通过下面代码可以验证:

    for (var i = 0; i < 3; i++) {}
    console.log(i); // 3
    console.log(window.i); // 3
    

    当for循环结束后变量i并没有被回收,实际上我们是创建了一个全局变量i,3个item的点击事件函数绑定的是全局变量i的值,所以最后都输出的是item 3 clicked,通过下面代码可以验证:

    for (var i = 0; i < items.length; i++) {
      items[i].onclick = function() {
        console.log(`item ${i} clicked`);
      }
    }
    i = 99;
    items[0].click(); // item 99 clicked
    items[1].click(); // item 99 clicked
    items[2].click(); // item 99 clicked
    

    当我们修改了i的值发现3个点击事件的结果也都变成了item 99 clicked,说明它们输出的i绑定的都是最新i的值而不是当时循环时i的值

    于是我们尝试一下使用一个局部变量看看能否保存i的值,这样我们就可以输出正确的i的值:

    for (var i = 0; i < items.length; i++) {
      items[i].onclick = function() {
        var j = i;
        console.log(`item ${j} clicked`);
      }
    }
    i = 99;
    items[0].click(); // item 99 clicked
    items[1].click(); // item 99 clicked
    items[2].click(); // item 99 clicked
    

    使用局部变量j保存i还是没有成功,我们修改了i的值后j的值也发生了变化,说明js在执行过程中j还是引用i的值。

    解决方法1:函数传参

    for (var i = 0; i < items.length; i++) {
      items[i].onclick = function() {
        console.log(`item ${j} clicked`);
      } (i)
    }
    i = 99;
    items[0].click(); // item 3 clicked
    items[1].click(); // item 3 clicked
    items[2].click(); // item 3 clicked
    

    通过传递参数我们得到了正确的结果,修改了i的值也没有影响点击事件的结果。

    解决方法2: 使用闭包

    for (var i = 0; i < items.length; i++) {
      items[i].onclick = (function() {
        var j = i;
        return function() {
          console.log(`item ${j} clicked`);
        }
      })();
    }
    i = 99;
    items[0].click(); // item 3 clicked
    items[1].click(); // item 3 clicked
    items[2].click(); // item 3 clicked
    

    在闭包中我们用j来保存i的值,这样我们就能记住当时循环时i的值了,和之前的单独一个function区别是闭包可产生独立的作用域这样j就是i的值的拷贝而不是i的引用,我是这么理解的。

    解决方法2: 使用let
    使用let应该是最优雅的解决办法了,let是ES6的关键字它能够产生封闭的作用域:

    for (let i = 0; i < items.length; i++) {
      items[i].onclick = function() {
        console.log(`item ${i} clicked`);
      }
    }
    items[0].click(); // item 3 clicked
    items[1].click(); // item 3 clicked
    items[2].click(); // item 3 clicked
    

    相关文章

      网友评论

          本文标题:Javascript闭包理解

          本文链接:https://www.haomeiwen.com/subject/tyzeqqtx.html