闭包

   本文分为(chang)几(pian)个(da)部(lun)分

  •    作用域链(Scope chain)
  •    闭包(Closure)的定义
  •    闭包(Closure)的使用

  最开始学习前端的时候,总是听到一个很新奇的词叫闭包。总觉得这个闭包听起来很高大上,当时也看了很多,还是觉得模糊不清,直到当时看到了一句话,豁然开朗。

  “JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。

  接下来就从这点出发,来看下怎么理解闭包。

作用域链(Scope chain)


  首先:
  要理解 作用域链 (Scope chain) 首先要理解 作用域 (Scope)是什么;

  •   作用域:在JavaScript中,作用域是执行代码的上下文。

  •    作用域有三种类型:
       全局作用域 :在代码中任何地方 都能访问到。
       局部作用域(函数作用域) :只在固定的代码片段中可访问到。
       eval作用域 : 与调用eval的方式相关联。

  •   关于JavaScript中的块级作用域
      JavaScript中没有块级作用域,逻辑语句 ( if(){} ) 和 ( for(){} ) 无法创建作用域。所以变量可以相互覆盖。这里可以看一个例子

    var a = 1; //定义一个全局变量a 并且初始化为1
    if(true){
        a = 3;//在if语句中 给 变量 a 赋值,因为if无法创建块级作用域 ,所以这里访问到的是全局变量a
        for(var i = 0 ; i < 3 ; i++ ){
            a = i;//在for语句中给a赋值 因为for无法创建块级作用域,所以这里访问到的也是全局变量a
        }       
    }
  • 关于JavaScript中声明变量时使用的var
    //在a函数中 我们声明了一个变量var
    var a = function(){
        s = 2;//如果s的作用域是函数作用域,那么我们在函数外打印它,结果是undefined 因为找不到
    }
    console.log(s);//在控制台打印出2

  JavaScript 会将 缺少var的变量声明的变量 声明在全局中。
  上面的例子如果我们在s前面加一个var 那么 就会打印出undefined 因为此时s在用var声明时,它的作用域变成了函数a的作用域。

  很好理解对吧。
  那么当一个函数被执行时,JavaScript会去查找与变量关联的值,这个查找的过程会遵循一个规则:

  这个规则,就是作用域链

  可以通过一个例子来理解什么是作用域链。

    var sthToAlert = 'peace peace'//首先定义了一个变量sthToAlert 并初始化

    var func_1 = function(){//定义第一层函数func_1

        var func_2 = function(){//定义第二层函数func_2
            console.log(sthToAlert);//在func_2中打印变量sthToAlert 结果是 'peace peace'
        }

    }

  代码很简单,但是能说明很多问题。
  首先,JavaScript在执行这段代码的时候,它执行到console.log(sthToAlert); 时,会发现,嗯?这个值哪里冒出来的。
  然后它会 由内而外 的去寻找sthToAlert的定义。

  顺序也就是 检查func_2 中是否有sthToAlert的定义      →
  检查func_2的父函数func_1 中是否有sthToAlert的定义  →
  检查func_1的父函数中是否有sthToAlert的定义
  ···
  检查全局作用域内中是否有sthToAlert的定义(如果没找到 就会返回undefined)

  那么 如果我们同时在全局和func_2中 同时定义了 sthToAlert 会怎么样呢?
  JavaScript会找到就近的定义(func_2中sthToAlert的定义),一旦找到了关于sthToAlert的定义,就不再找下去了。

闭包(Closure)的定义


  现在我们知道了作用域链的概念,那我们回头再来看最开始的这句话:

  “JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。

  换句话说,函数的作用域链 是根据 函数定义时的位置 决定的
  而不是在调用时确定的。这叫做 "词法作用域"

  那么这和闭包又有什么关系呢?
  可以结合一个场景来看:

    //我们定义了一个person 函数
    var person = function(){
        //在函数的内部 我们定义了这个person 的birth 
        //但是我们不想让别人一下就知道这个人的 birth的信息 
        //所以我们用了一个 var 来定义这个变量
        var birth = "June/18";
    }
     //我们很想打印这个birth  
    //但是又因为birth在定义时 是person函数作用域,所以在外面访问不到
    //只能返回undefined
    console.log(birth);

  那么这时候我们怎么解决这个问题呢?
  答案就是用闭包来解决。
  怎么做呢?修改下代码

    //我们定义了一个person 函数
    var person = function(){
        //在函数的内部 我们定义了这个person 的birth 
        //但是我们不想让别人一下就知道这个人的 birth的信息 
        //所以我们用了一个 var 来定义这个变量
        var birth = "June/18";

        //为了能在外部访问到birth的值,我们返回一个匿名函数来做这件事情。
        return function(){
            console.log(birth);
        }

    }
    //我们只需要调用person返回的匿名函数就可以达到效果
    person()();

  现在已经简单的说明了闭包的概念。

闭包(Closure)的使用


  在说到闭包的使用之前,可以先看一个例子。

        //定义了一个函数Q 这个函数接收一个string类型的参数
        var Q = function (string){
            //把传入的参数赋值给当前对象的status属性
            this.status = string;
        };

        //注意这里没有通过new关键字调用
        console.log((Q('111')).status);
        //这里返回的是:Uncaught TypeError: Cannot read property 'status' of undefined
        
        //注意这里是通过new关键字调用
        console.log((new Q('111')).status);
        //这里返回的是:111

  这个例子很有意思,在函数调用的方式不一样会直接导致结果的不一样。
  因为在JavaScript中我们直接调用一个函数时,这个 this 会绑定到 全局对象
  然后就很好理解为什么提示 'status' 属性的 undefined 因为这里的this指向全局对象this

  在通过new调用时,那么实际上JavaScript做的事情是,它会再创建一个链接到该函数的prototype成员的新对象,并且把this指向这个新对象。同时在函数调用结束,会把这个对象返回
用new关键来调用函数的这种行为称为:构造器调用模式

  说了这么多,这和闭包有什么关系呢?

  如果说,我并不想让其他调用者知道,这个函数里面的属性是怎么样定义的,而是提供一个公用的getter和setter 让其他人来读取或者修改这些私有属性。这时候闭包就派上了大用场。看一个例子:

      //创建一个myObject对象,对象内定义了一个匿名函数。
      var myObject = (function(){
            //在函数内部定义一个属性 name 因为函数作用域的原因,这个属性为私有。
            var name = 'dendi';
            //返回一个对象,对象本身包含了两个函数,一个getName() 一个setName()
            return{
                getName:function(){
                    return name;
                },
                setName:function(_name){
                     name = _name;
                }
            }
        });

      // 调用myObject来创建一个实例。
      var temObj = myObject();
      console.log(temObj.getName()); //输出dendi
      temObj.setName('dendoink');
      console.log(temObj.getName());//输出dendoink

  (结束啦。)

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

推荐阅读更多精彩内容