资讯专栏INFORMATION COLUMN

闭包理解

niceforbear / 3464人阅读

摘要:什么是闭包红宝书上给出的定义是闭包是指有权访问另一个函数作用域中的变量的函数,看到另外一个理解是函数和函数内部能访问到的变量或者环境的总合,就是一个闭包。闭包用于创建单例所谓单例,就是只有一个实例的对象。它使用立即执行函数和闭包来达到目的。

面试必问题目,但总觉得理解得不深入,索性写一篇文章慢慢梳理吧。

什么是闭包

红宝书上给出的定义是:闭包是指有权访问另一个函数作用域中的变量的函数,看到另外一个理解是:函数和函数内部能访问到的变量(或者环境)的总合,就是一个闭包。创建一个闭包最常见的方式就是在一个函数内部创建另一个函数。下面写一个例子:

function f1() {
  var a = 1;
  function closure() {
    console.log(++a);
  } 
  return closure;
}

上面例子中,f1 内部的匿名函数以及它能够访问到的外部函数的变量 a 合在一起,就形成了一个闭包。使用 return 将闭包返回的目的是让它可以被外部访问。下面看看它怎么使用:

var f2 = f1();   // 执行外部函数,返回闭包
f2();     // 2
f2();     // 3
f2();     // 4

第一句执行函数 f1() 后,闭包被返回并赋值给了一个全局变量 f2,以后每次调用 f2(),变量 a 的值就会加 1。通常函数执行完毕后,其作用域链和活动对象都会被销毁,为什么这里 a 并没有被销毁并且每次执行 f2() 还会被递增?原因是闭包有权访问外部函数的变量,进一步说,闭包的作用域链会引用外部函数的活动对象,所以 f2() 在执行时,其作用域链实际上是:

自身的活动对象;

f1() 的活动对象;

全局变量对象。

所以 f1() 执行完后,其执行环境的作用域链会被销毁,但活动对象仍然会留在内存中,因为闭包作用域链在引用这个活动对象(说白了就是闭包还需要使用外层函数的变量,不允许它们被销毁),直到闭包被销毁后,f1() 的活动对象才会被销毁。

上面例子中,是将返回的闭包赋值给了一个全局变量 f2var f2 = f1();f2 是不会被销毁的,每次执行完 f2(),闭包的作用域链不会被销毁,所以就会出现每次执行 f2()a 递增。

但是换一种闭包的调用方式,情况会不同:

f1()();   // 2
f1()();   // 2

因为没有把闭包赋值给一个全局变量,闭包执行完后,其执行域链与活动对象都销毁了。

闭包的作用 创建用于访问私有变量的公有方法

其实构造函数中定义的实例方法,就是闭包:

function Person(){
  var name = "Leon";
  function sayHi() {
    alert("Hi!");
  }
  this.publicMethod = function() {
    alert(name);
    return sayHi();
  }
}

构造函数 Person 中定义实例方法 publicMethod() 就是一个闭包,它可以访问外部函数的变量 name 和 函数 sayHi(),为什么要这么做呢?因为我们想在构造函数中定义一些私有变量,让外部不能直接访问,只能通过定义好的公有方法访问,从而达到保护变量,收敛外部权限的目的。

而在普通函数中,把闭包 return 出去供外部使用,其实目的也就是:让函数内部的变量始终保持在内存中,同时保护这些变量,让它们不能被直接访问。

function person(){
  var name = "Leon";
  function sayHi() {
    alert("Hi!");
  }
  function publicMethod() {
    alert(name);
    return sayHi();
  }
  return publicMethod;
}
闭包用于创建单例

所谓单例,就是只有一个实例的对象。单例模式的好处在于:

保证一个类只有一个实例,避免了一个在全局范围内使用的实例频繁创建与销毁。

比如网页中的弹窗,点击 a 按钮弹出,点击 b 按钮隐藏,如果弹窗每一次弹出都需要新建一个对象,将会造成性能的浪费,更好的办法就是只实例化一个对象,一直使用。

划分了命名空间,避免了与全局命名空间的冲突。

比如在一个单例中可以定义很多方法,通过单例.方法来使用,避免了在全局环境中定义函数,造成函数名冲突。

下面逐步介绍下单例的创建方式,后两种方式将用到闭包。

1. 对象字面量创建单例
var singleton = {
  attr1: 1,
  attr2: 2,
  method: function () {
    return this.attr1 + this.attr2;
  }
}
var s1 = singleton;
var s2 = singleton;
console.log(s1 == s2)  // true

上面用字面量形式创建了一个单例,可以看到 s1s2 是等同的。这种方式的问题在于外部可以直接访问单例的内部变量并加以修改,如果想让单例拥有私有变量,就需要使用模块模式,模块模式就是用了闭包。

2. 模块模式

JS 中的模块模式的作用是:为单例添加私有变量和公有方法。它使用立即执行函数和闭包来达到目的。

var singleton = (function(){
  // 创建私有变量
  var privateNum = 1;
  // 创建私有函数
  function privateFunc(){
    console.log(++privateNum);
  }
  // 返回一个对象包含公有方法
  return {
      publicMethod: function(){
        console.log(privateNum)
        return privateFunc()
      }
  };
})();

singleton.publicMethod();
// 1
// 2 

这里首先定义了一个立即执行函数,它返回一个对象,该对象中有一个闭包 publicMethod(), 它可以访问外部函数的私有变量。从而这个被返回的对象就成为了单例的公共接口,外部可以通过它的公有方法访问私有变量而无权直接修改。总结一下就是两点:

立即执行函数可以创建一个块级作用域, 避免在全局环境中添加变量。

闭包可以访问外层函数中的变量。

3. 构造函数+闭包

上面提到的对象字面是用来创建单例的方法之一,既然单例只能被实例化一次,不难想到,在使用构造函数新建实例时,先判断实例是否已被新建,未被新建则新建实例,否则直接返回已被新建的实例。

var Singleton = function(name){
  this.name = name;
};

// 获取实例对象
var getInstance = (function() {
  var instance = null;
  return function(name) {
      if(!instance) {
          instance = new Singleton(name);
      }
      return instance;
  }
})();

var a = getInstance("1");
console.log(a);  // {name: "1"}
var b = getInstance("2");
console.log(b);  // {name: "1"}

这里将构造函数和实例化过程进行了分离, getInstance()中存在一个闭包,它可以访问到外部变量 instance,第一次 instance = null,则通过 new Singleton(name) 新建实例,并将这个实例保存在instance 中,之后再想新建实例,因为闭包访问到的instance已经有值了,就会直接返回之前实例化的对象。

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

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

相关文章

  • 多层级理解闭包

    摘要:第二梯队理解有了第一梯队的认识,我们慢慢修正大脑中对闭包的认识。理解这句话就可以很好的与闭包这两个字关联起来理解闭包这个概念了。总结第二梯队理解闭包是一个有特定功能的函数。第四梯队理解闭包通过访问外部变量,一个闭包可以维持这些变量。 闭包 闭包的概念困惑了我很久,记得当时我面试的时候最后一面有一个问题就是问题关于闭包的问题,然而到现在已经完全不记得当时的题目是啥了,但仍然能够回忆起当时...

    nemo 评论0 收藏0
  • 理解 JavaScript 闭包

    摘要:如何在初学就理解闭包你需要接着读下去。这样定义闭包是函数和声明该函数的词法环境的组合。小结闭包在中随处可见。闭包是中的精华部分,理解它需要具备一定的作用域执行栈的知识。 这是本系列的第 4 篇文章。 作为 JS 初学者,第一次接触闭包的概念是因为写出了类似下面的代码: for (var i = 0; i < helpText.length; i++) { var item = he...

    宠来也 评论0 收藏0
  • 谈谈我所理解闭包,js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    betacat 评论0 收藏0
  • 谈谈我所理解闭包,js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    zhoutao 评论0 收藏0
  • 简单理解JavaScript中的闭包

    摘要:闭包在我理解是一种比较抽象的东西。所以我写了一篇博文来方便自己理解闭包。那么现在我们可以解释一下闭包的第一个定义在计算机科学中,闭包是引用了自由变量的函数。循环中创建闭包在我们使用的关键字之前,闭包的一个常见问题就出现在循环中创建闭包。 零. 前言 从我开始接触前端时就听说过闭包,但是一直不理解闭包究竟是什么。上网看了各种博客,大家对闭包的说法不一。闭包在我理解是一种比较抽象的东西。所...

    sihai 评论0 收藏0
  • 理解Javascript的闭包

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

    dayday_up 评论0 收藏0

发表评论

0条评论

niceforbear

|高级讲师

TA的文章

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