资讯专栏INFORMATION COLUMN

JavaScript 闭包

wwq0327 / 2127人阅读

摘要:在文档中,闭包的定义是。假设是一个被创建的局部变量是内部函数一个闭包使用了父函数中声明的变量通过以上的代码块来看,我们可以看到闭包实际上指的就是一个拥有外部环境变量的函数。

闭包

对于 JavaScript 的初学者来说,闭包的概念和应用都可以算的上是难点。 MDN 的 JavaScript 文档对闭包的概念给出了准确的定义,也提供了简单直观的的实例,是一个非常好的学习材料。 这篇文章将从文档出发,对闭包的知识点进行一个简单的梳理。

闭包是什么

首先,我们需要对闭包提供一个准确的定义。 在文档中,闭包的定义是 "A closure is the combination of a function and the lexical environment within which that function was declared"。这个定义是很拗口的一句话。 词法环境(lexical environment)这个描述对于初学者来说过于学术和抽象,我们只需要记住就好。真正理解定义最好方式就是通过实际的代码。 假设:

function init() {
   var name = "Hello"; // name 是一个被 init 创建的局部变量
   function displayName() { 
       // displayName() 是内部函数,一个闭包
       alert(name); // 使用了父函数中声明的变量
   }
   displayName(); 
}
init();

通过以上的代码块来看,我们可以看到闭包实际上指的就是一个’拥有外部环境变量的函数‘。 在上面的例子中函数 displayName 调用了不属于本身的外部变量 name,不管此 displayName 函数最终是否被返回,实际上由 name 和 displayName 组成的闭包已经形成。

 function init() {
    var name = "Hello"; // name 是一个被 init 创建的局部变量
    function displayName() { 
        // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    return displayName(); // 闭包被返回
}
var fun = init();
fun();

我们再来看一下这一块的新的代码,唯一的区别在于这个代码中 函数init 返回了一个函数 displayName()。也就是返回了一个闭包。通过这个返回的闭包,我们就可以访问这个函数所相关联的词法环境或者说数据。本来应该被销毁的 name 变量保留了下来,而且只能通过调用闭包的方式来访问,这也就是私有性。

闭包的作用

实际上在上一个例子中,我们已经看到了闭包的作用,闭包可以用来模拟私有变量和方法。 它让函数和函数所操作的某些数据(环境)关联了起来。

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

在上面的例子中 我们可以看到变量 privateCounter 和 函数 changeBy 作为下面三个函数共同的词法环境形成了闭包。 在 makeCounter()执行之后, 本该消失的词法环境被保留下来,只能通过返回的三个函数进行更改和访问。这种行为模拟出了类似 JAVA 类中的私有变量和私有方法。

在循环中创建闭包:一个常见错误;

在 ECMAScript 2015 引入 let 这个关键字之前,在循环中有一个常见的闭包创建问题。

Helpful notes will appear here

E-mail:

Name:

Age:

function showHelp(help) {
  document.getElementById("help").innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {"id": "email", "help": "Your e-mail address"},
      {"id": "name", "help": "Your full name"},
      {"id": "age", "help": "Your age (you must be over 16)"}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

这段代码的结果就是,无论你选择哪一个输入框,helper 信息永远都会显示 Your age (you must be over 16)"。 原因就在于在返回的三个闭包实际上共享了 item 这一个词法环境,所以 helper 永远只显示为最后 age 的 helper。 这里就是闭包里另一个很重要的知识点,闭包只会捕获自由变量的引用,所以当 item 指向的helpText值最后变为 age 时,三个闭包的中的 item 也都变成了 age。 根据这一点我们可以将代码修改如下

function setupHelpAnonymous(){
    var helpText = [
      {"id": "email", "help": "Your e-mail address"},
      {"id": "name", "help": "Your full name"},
      {"id": "age", "help": "Your age (you must be over 16)"}
    ];

    for(var i = 0; i < helpText.length; i++){
      var item = helpText[i];
      (function() {
        var item = helpText[i];
        document.getElementById(item.id).onfocus = function() {
          showHelp(item.help);
        }
     })();
    }
  }

  setupHelpAnonymous()

在上面的代码片段中 我们使用了一个 IIFE (立即执行函数表达式) 对 item 这个引用进行了立刻求值。这样我们就能得到想要的结果。而在ES6中的 ’块级作用域‘ 也可以解决这个问题。

function showHelp(help) {
  document.getElementById("help").innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {"id": "email", "help": "Your e-mail address"},
      {"id": "name", "help": "Your full name"},
      {"id": "age", "help": "Your age (you must be over 16)"}
    ];

  for (var i = 0; i < helpText.length; i++) {
    let item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

每一次循环,都有一个新的 item 被创建,三个闭包不再共享同一个词法环境;相比匿名闭包的方式,也没有创建多余的闭包。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92988.html

相关文章

  • Javascript闭包入门(译文)

    摘要:也许最好的理解是闭包总是在进入某个函数的时候被创建,而局部变量是被加入到这个闭包中。在函数内部的函数的内部声明函数是可以的可以获得不止一个层级的闭包。 前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 译者 :文章写在2006年,可直到翻译的21小时之前作者还在完善这篇文章,在Stackoverflow的How do Java...

    Fourierr 评论0 收藏0
  • 理解Javascript闭包

    摘要:但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。可惜的是,并没有提供相关的成员和方法来访问闭包中的局部变量。 (收藏自 技术狂) 前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将...

    dayday_up 评论0 收藏0
  • JavaScript闭包,只学这篇就够了

    摘要:当在中调用匿名函数时,它们用的都是同一个闭包,而且在这个闭包中使用了和的当前值的值为因为循环已经结束,的值为。最好将闭包当作是一个函数的入口创建的,而局部变量是被添加进这个闭包的。 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义。 其实只要理解了核心概念,闭包并不是那么的难于理解。但是,网上充斥了太多学术性的文章,对于...

    CoderBear 评论0 收藏0
  • JavaScript深入之闭包

    摘要:深入系列第八篇,介绍理论上的闭包和实践上的闭包,以及从作用域链的角度解析经典的闭包题。定义对闭包的定义为闭包是指那些能够访问自由变量的函数。 JavaScript深入系列第八篇,介绍理论上的闭包和实践上的闭包,以及从作用域链的角度解析经典的闭包题。 定义 MDN 对闭包的定义为: 闭包是指那些能够访问自由变量的函数。 那什么是自由变量呢? 自由变量是指在函数中使用的,但既不是函数参数也...

    caige 评论0 收藏0
  • 还担心面试官问闭包

    摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 评论0 收藏0
  • [学习笔记] JavaScript 闭包

    摘要:但是,必须强调,闭包是一个运行期概念。通过原型链可以实现继承,而与闭包相关的就是作用域链。常理来说,一个函数执行完毕,其执行环境的作用域链会被销毁。所以此时,的作用域链虽然销毁了,但是其活动对象仍在内存中。 学习Javascript闭包(Closure)javascript的闭包JavaScript 闭包深入理解(closure)理解 Javascript 的闭包JavaScript ...

    sunsmell 评论0 收藏0

发表评论

0条评论

wwq0327

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<