JS闭包的2W1H

闭包是JS中一个重要概念,很有用处,但不好理解。这里从what、why、how三个方面来总结闭包知识。

1 WHAT (闭包是什么?)


闭包:函数(A)中的函数(B),可以访问外部函数(A)内部的所有变量。

2 WHY (为什么闭包能访问函数变量?)


2.1 作用域链

要理解闭包原理,需要先了解作用域链的细节。

function compare(value1,value2){
  if(value1<value2){
    return -1;
  }else if(value1>value2){
    return 1;
  }else{
    return 0;
  }
}
var result = compare(5,10);

上面代码,创建compare()时,会创建包含全局变量对象(含this、compare、result)的作用域链。

而第一次调用compare()时,会创建一个局部执行环境。再创建compare()活动对象(包含this、arguments、value1、value2),推入执行环境作用域链前端。故compare()活动对象处于作用域链第一位(0),而全局变量对象处于作用域链的第二位(1)。

通过作用域链,函数可以访问局部变量和全局变量。函数执行后,局部活动对象会被销毁,内存只保存全局变量对象。但闭包的情况又不一样了。

2.2闭包的作用域链

function createComparisonFunction(propertyName){ 
    return function(object1, object2){    //闭包
       var value1 = object1[propertyName];//访问外部函数的变量
       var value2 = object2[propertyName]; 
       if(value1 > value2){ 
         return 1;  
       }else if(value1 < value2){
         return -1; 
       }else{ 
         return 0; 
       }
   };
}
// 创建函数
var compareNames = createComparison("name");
// 调用函数
var result = compareNames({name:"Lillian"},{name:"Matthew"});

外部函数<code>createComparitionFunction()</code>的活动对象会添加到闭包的作用域链中,这样闭包就可以访问函数中的变量。问题是,<code>createComparitionFunction()</code>执行完毕后,闭包仍然引用着它的活动对象,所以无法销毁其活动对象。

只有闭包销毁,才能销毁函数的活动对象,释放内存:

// 解除对函数(闭包)的引用,释放内存
compareNames = null;

3 HOW (怎样使用闭包?)


闭包有很多用途,这里只列举最常用的两种。

3.1 实现私有作用域

JS没有块级作用域概念:

function outputNumbers(count){ 
   for(var i = 0; i< count; i++){
      alert(i); 
   }
 alert(i); // 不会报错
}

由于没有块级作用域,for循环结束后,<code>i</code>并不会被销毁。所以<code>alert(i)</code>不会报错。

使用自调用函数可以模仿块级作用域:

(function(){
    //这里是块级作用域
})()

其实自调用函数实现私有作用域,与闭包没有必然联系,只是自调用函数也可以用于函数内部(作为闭包):

function outputNumbers(count){ 
   (function(){
      for(var i = 0; i< count; i++){
         alert(i); 
      }
   })()
 alert(i); // 报错,i没有定义
}

For循环放在自调用函数(此处是闭包)中,这样变量<code>i</code>只能在循环中被访问,在循环外部无法访问。

3.2 访问私有变量

在函数中定义的变量(参数、局部变量、内部函数),都不能在外部访问,所以是私有变量。而闭包可以访问函数中的变量,这就提供了访问私有变量的共有方法(特权方法)。

对于对象来说,有下面几种方式访问私有变量:

(1) 构造函数模式:

function MyObject() {
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
  //特权方法
    this.publicMethod = function() {
        privateVariable++;
        return privateFunction();
    };
}
var obj1 = new MyObject();
console.log(obj1.publicMethod());

创建<code>MyObject</code>的实例<code>obj1</code> 后,只能用<code>publicMethod() </code>访问<code>privateVariable</code>和<code>privateFunction()</code>,没有其他方法可以直接访问私有变量和私有函数。

(2)原型模式:

(function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }
    //构造函数
    MyObject = function(){
    };
     /*函数声明只能创建局部函数,所以这里使用了函数表达式。
    注意:变量MyObject没有加var,所以是全局变量。在私有作用域外部也能访问。*/

    //特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    }
})();

原型模式与构造函数模式最主要的区别就是私有变量和私有函数由实例共享的。这样,变量就成了静态的、由所有实例共享的属性,即静态私有变量。

(3)模块模式:
模块模式用于为单例创建私有变量和特权方法。(单例:只有一个实例的对象。)

var singleton = function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //公有方法和属性
    return {
        publicProperty: true,
        publicMethod: function(){
            privateVariable++;
            return privateFunction();
        }
    };
}();

该模式返回一个对象字面量,包含公有属性和方法。该对象是在匿名函数内部定义的,所以它的公有方法可以访问私有变量和函数。

(4)增强的模块模式:

var singleton = function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //创建对象,这里CustomType是一种实例类型,我们不需要理会它的具体代码
    var object = new CustomType();
    //公有属性和方法
    object.publicProperty = true;
    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    }
    //返回对象
    return object;
}();

增强的模块模式,不是直接返回对象字面量,而是创建一个对象实例,增加属性和方法后返回。
这种模式,适合单例是某种类型实例的情况。上面的代码,object是CustomType的实例,匿名函数返回object对象,并赋值给singleton变量。所以,该单例singleton是CustomType的实例。

代码来源:
《JavaScript 高级程序设计》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • 最近学这块知识学得有些吃力。还有很多遗漏的地方,只能以后多看些书来弥补了。 第7章 函数表达式 函数定义的两种方式...
    丨ouo丨阅读 361评论 0 1
  • 定义函数的方式有两种:函数声明和函数表达式。 函数声明的一个重要特征就是函数声明提升,意思是在执行代码前会先读取函...
    oWSQo阅读 660评论 0 0
  • 继承 一、混入式继承 二、原型继承 利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的...
    magic_pill阅读 1,050评论 0 3
  • 看你笑 再累的我 也不会流泪 看你哭 再冷的我 也都会慌张 你 就是我 全部的人生
    孤独且灿烂阅读 126评论 0 0
  • 图片发自简书App 奇葩年年有,今年特别多。自从进了大学,有些人的为人处事一次又一次的刷新了我的三观。 1/ 朋友...
    范慢小透阅读 353评论 0 0