zoukankan      html  css  js  c++  java
  • 深入理解javascript闭包(二)

    在上次的分享中javascript--函数参数与闭包--详解,对闭包的解释不够深入。本人经过一段时间的学习,对闭包的概念又有了新的理解。于是便把学习的过程整理成文章,一是为了加深自己闭包的理解,二是给读者提供学习的途径,避免走弯路。

    以下的分享会分为如下内容:

    1.let命令

    2.闭包特点的解读

    3.循环中的闭包

    1.let命令

      在讲闭包前,有必要谈谈ES6中的新概念,let命令。因为在赘述循环中的闭包时会使用到let命令。

      基本用法

      ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

    1     if (true) {
    2         var a = 1;
    3         let b = 2;
    4     }
    5     console.log(a); // 1
    6     console.log(b); // ReferenceError: b is not defined

      在javascript--函数参数与闭包--详解中,谈到在局部变量只能在函数内部声明,在其他代码(如 if 条件语句,for循环语句)用 var 声明的变量都为全局变量。

      在上面代码中,分别用 letvar 声明了两个变量。然后在代码块之外调用这两个变量,结果 let 声明的变量报错,var 声明的变量返回了正确的值。这表明,if 条件语句中使用var声明的变量为全局变量,可以在全局作用域下访问。而 let 声明的变量只在它所在的代码块有效,在全局作用域下无法访问。

      再来看看这两个例子。

    1     for (let i = 0; i < 10; i++) {}
    2     console.log(i);  //ReferenceError: i is not defined
    1     for (var i = 0; i < 10; i++) {}
    2     console.log(i);  // 10

    2.闭包特点的解读

      我们知道,闭包有三个特点

      a:在一个函数内部定义另外一个函数,并返回内部函数或立即执行内部函数。

      b:内部函数可以访问外部函数定义的局部变量 (变量采用var声明)

      c:让局部变量始终保存在内存中。也就是说,闭包可以使得它诞生的环境一直存在。

      我们来看一个例子,尝试串起这三个特点。

     1     function keith() {
     2         var a = 1;
     3         return function() {
     4             return a++;
     5         }
     6     }
     7     var result = keith();
     8     console.log(result()); //1
     9     console.log(result()); //2
    10     console.log(result()); //3

      首先,在函数keith内部返回了一个匿名函数,如果函数keith没有返回值,则默认返回值为undefined。

      然后,因为在函数keith中返回了一个匿名函数,又把调用函数keith的结果赋值给了全局变量result,所以全局变量result是一个闭包。当连续调用result时,依次返回1,2,3。返回值说明了内部函数可以访问外部函数定义的局部变量。也就是说,闭包记住了外部函数定义的局部变量的调用结果。

      最后,因为我们把一个闭包赋值给了一个全局变量result,在调用时依次输出1,2,3。说明了在函数keith外部访问的这个局部变量a一直存在全局作用域中。也就是说,局部变量 a 一直存在于内存当中,所以不会被垃圾回收机制回收。

      所以使用闭包的时候的注意点:

      由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    3.循环中的闭包

      一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号。

    1     for (var i = 0; i < 10; i++) {
    2         setTimeout(function() {
    3             console.log(i); //10
    4         }, 1000)
    5     }

      上面代码中,不会符合我们的预期,输出数字0-9。而是会输出数字10十次。

      出现错误的原因在于我们在setTimeout函数里面定义了一个匿名函数,匿名函数的作用是在控制台输出变量 i,而变量 i 是一个全局变量,在全局范围内都有效。所以每一次循环,新的 i 值都会覆盖旧值,导致最后输出的是最后一轮的i的值。

      所以,针对循环中的闭包,有以下两种解决方法。

      一是使用立即执行函数(IIFE),并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

    1     for (var i = 0; i < 10; i++) {
    2         (function(e){
    3             setTimeout(function() {
    4                 console.log(e); //1,2,3,....,10
    5             }, 1000)
    6         })(i)
    7     }

      二是让变量 i 只在代码块中有效。也就是说让其成为局部变量。变量 i let 声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量,所以最后输出的是1,2,3,4....,10。

    1     for (let i = 0; i < 10; i++) {
    2         setTimeout(function() {
    3             console.log(i); //1,2,3...,10
    4         }, 1000)
    5     }

      

    完。

    感谢大家的阅读。

    转载请注明出处:http://www.cnblogs.com/Uncle-Keith/p/5801015.html

  • 相关阅读:
    codeforces 980A Links and Pearls
    zoj 3640 Help Me Escape
    sgu 495 Kids and Prizes
    poj 3071 Football
    hdu 3853 LOOPS
    hdu 4035 Maze
    hdu 4405 Aeroplane chess
    poj 2096 Collecting Bugs
    scu 4444 Travel
    zoj 3870 Team Formation
  • 原文地址:https://www.cnblogs.com/unclekeith/p/5801015.html
Copyright © 2011-2022 走看看