闭包

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

  (结束啦。)

你可能感兴趣的:(闭包)