闭包

作者: DendiSe7en | 来源:发表于2017-09-13 17:54 被阅读0次

       本文分为(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
    

      (结束啦。)

    相关文章

      网友评论

        本文标题:闭包

        本文链接:https://www.haomeiwen.com/subject/pgzksxtx.html