JavaScript闭包

闭包是函数和声明该函数的词法环境的组合

​ 一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因为这些变量也是该表达式的一部分。闭包的特点:

  1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态;
  2. 一个闭包就是当函数返回时,一个没有释放资源的栈区。

​ 简单的说,JavaScript允许使用内部函数,即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问他们所在的外部函数汇总声明的所有变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包裹它们的外部函数之外被调用时,就会形成闭包。

​ 说到这里,下面就先说说词法作用域。

词法作用域

运行下面的代码:

var age = 28; // age 是一个全局变量
function init() {
    var name = "Guohh"; // name 被局部变量init函数创建
    function displayName() { // displayName()是一个内部函数,一个闭包
        alert (name); // displayName() 调用了父函数中的变量
        alert (age);  // displayName() 调用了全局中的变量
    }
    displayName();    
}
init();

​ 运行代码,可以发现函数displayName()内部调用了父函数中的变量和全局变量。这个例子介绍了浏览器引擎是如何解析函数嵌套中的变量。词法作用域中使用的域,是变量在代码声明的位置所决定的。嵌套函数可以访问器外部声明的变量。

什么是闭包

​ 在运行下面这一段代码:

function init() {
    var name = "Guohh";  
    function displayName() {  
        alert (name);  
    }
    return displayName;    
}
var ini = init();
ini();

​ 这段代码和词法作用域的结果一致,不同的就是init()函数将displayName()返回。在词法作用域中我们会认为name变量在init()函数执行完毕后将不能被访问,但是因为变量提升,我们将获得到undefined,但真实的结果却不是这样的。那么为什么呢?

​ JavaScript中的函数会形成闭包。闭包是有函数以及创建函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。

​ 既然知道什么是闭包,那么下面就来说一下它的几种用法。

闭包的用途

立即执行函数IIFE

​ 在我们翻读jQuery源码时,发现它开篇用的就是立即执行函数。立即执行函数常用于第三方库,好处在于隔离作用域,任何一个第三方库都会存在大量的变量和函数,为了避免变量污染(命名冲突)。

写法
  1. 在函数外边后加括号,并被括号包裹

    (function(){
      // 执行  
    }())
    
  2. 在函数外边包裹括号,并在后边添加括号

    (function(){
      // 执行  
    })()
    
  3. 函数前面加运算符,创建的是!void

    !function(){
      // 执行  
    }()
    void function(){
      // 执行  
    }()
    
参数

​ 立即执行函数式可以访问到外部变量,很多情况下并不需要传递参数。当我们看jQuery源码时,就会看到如下代码块:

(function(window) {})()

​ 其实在jQuery中也可以传,内部依然能使用,但这里传递的原因就是在函数内部对window的操作不影响全局的window。

返回值

​ 立即执行函数和其他函数一样,它也能返回值并且赋值给其他变量。

好处
  1. 立即执行函数模式被广泛使用了,它可以帮你封装大量的工作而不会在背后遗留任何全局变量。

  2. 定义的所有变量都会成为立即执行函数的局部变量,避免这些临时变量污染全局空间。

  3. 这种模式经常被使用在书签工具中,因为书签工具在任何页面运行并且保持全局命名空间干净是非常必要的。

  4. 这种模式也可以让你将独立的功能封装在自包含模块中。

  5. 可以将这些代码封装在一个立即执行函数中,并且确保页面在没有它的情况下正常运行。

  6. 可以添加更多的加强模块,移除他们,单独测试他们,允许用户去禁用它们等。

    注意:当将立即执行函数左右单独模块使用是在函数前面添加分号,这样可以有效地与前面的代码出现隔离。

模块化

​ 在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。比如Java,是支持方法声明为私有的,即它们只能被同一个类中的其他方法所调用。

​ 在Web中,我们大部分所写的JavaScript代码都是基于事件的某种行为,然后将其添加到用户触发的事件之上。我们的代码通常作为回调:为相应事件而执行的函数。而JavaScript中,却没有原生的方法来支持像面向对象中的私有方法的声明,但是我们可以通过闭包来实现模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码公共接口部分。这个方式也叫做模块化

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 */

​ 上面的代码我们定义了一个匿名函数,用于创建一个计数器。我们立即执行了这个匿名函数,并将他的值赋给了变量counter。我们可以把这个函数储存在另外一个变量makeCounter中,并用他来创建多个计数器。每个闭包(计时器)都是引用自己词法作用域内的变量 privateCounter 。每次调用其中一个计数器是,改变变量的值,只会改变这个闭包的词法环境,而不会影响另外一个闭包内中的变量。

​ 以这种方式使用闭包,提供了许多与面向对象编程相关的好处:数据的隐藏和封装

实现类和继承

function Person(){    
    var name = "default";       
       
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
    };   

    var p = new Person();
    p.setName("Tom");
    alert(p.getName());

    var Jack = function(){};
    //继承自Person
    Jack.prototype = new Person();
    //添加私有方法
    Jack.prototype.Say = function(){
        alert("Hello,my name is Jack");
    };
    var j = new Jack();
    j.setName("Jack");
    j.Say();
    alert(j.getName());

闭包中常见的问题

  1. for循环会形成闭包,解决方案是:函数工厂、匿名闭包、用let代替var进行变量的声明。
  2. 引用的外部变量可能会发生改变。
  3. this执行问题
  4. 内存泄露

注意

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能会大致内存泄露解决方法是:在退出函数之前,将不适用的比变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以如果你把父函数当做对象使用,把闭包当做他的公用方法,把内部变量当做他的私有属性,这是一定注意不要随便改变函数内部变量的值。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容