闭包、作用域、函数的4种调用方式

闭包

变量作用域

  • 变量作用域的概念:就是一个变量可以使用的范围
  • JS中首先有一个最外层的作用域:称之为全局作用域
  • JS中还可以通过函数创建出一个独立的作用域,其中函数可以嵌套,所以作用域也可以嵌套
var age=18;     //age是在全局作用域中声明的变量:全局变量

function f1(){
    console.log(name);      //可以访问到name变量
    var name="周董" //name是f1函数内部声明的变量,所以name变量的作用域就是在f1函数内部

    console.log(name);      //可以访问到name变量

    console.log(age);       //age是全局作用域中声明的,所以age也可以访问
}
    //多级作用域
    //-->1级作用域
    var gender="男";
    function fn(){
        //gender:可以访问
        //age:  可以访问,值为undefined
        //height: 不能访问

        //-->2级作用域
        return function(){
            //gender:   通过一级一级作用域的查找,发现gender是全局作用域中声明的变量
            //age:      可以访问,值为undefined
            //height:  可以访问,值为undefined
            console.log(gender);

            //-->3级作用域
            var height=180;
        }
        var age=5;
    }
  • 注意:变量的声明和赋值是在两个不同时期的
    function fn(){
        console.log(age);   //undeinfed
        var age=18;
        console.log(age);   //18
    }
- fn函数执行的时候,首先找到函数内部所有的变量、函数声明,把他们放在作用域中,给变量一个初始值:undefined    -->变量可以访问
- 逐条执行代码,在执行代码的过程中,如果有赋值语句,对变量进行赋值

作用域链

  • 由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?我们把这个变量的查找过程称之为变量的作用域链
  • 作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
  • 简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)
    • 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
      • 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
        • 再查找上级函数的上级函数,直到全局作用域为止
          • 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
function fn(callback){
    var age=18;
    callback()
}
    
fn(function(){
    console.log(age); //undefined
    var age = 15;
    //分析:age变量:
    //1、查找当前作用域:并没有
    //2、查找上一级作用域:全局作用域
    //-->难点:看上一级作用域,不是看函数在哪里调用,而是看函数在哪里编写
    //-->因为这种特别,我们通常会把作用域说成是:词法作用域
})

闭包概念

各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

使用闭包的注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包问题的产生原因

  • 函数执行完毕后,作用域中保留了最新的a变量的值

闭包的应用场景

  • 模块化
  • 防止变量被破坏

函数的4种调用方式

  • ES6之前,函数内部的this是由该函数的调用方式决定的
    • 函数内部的this跟大小写、书写位置无关
  • 在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据上面的四种方式来决定的
  • 1、函数调用
    var age=18;
    var p={
        age:15
        say:function(){
            console.log(this.age);
        }
    }
    var f1=p.say; //f1是函数
    f1();       //函数调用-->this:window    -->this.age=18

    function Person(name){
        this.name=name;
    }
    Person.prototype={
        constructor:Person,
        say:function(){
            console.log(this.name);
        }
    }

    //函数的第一种调用方式:函数调用    
    //  -->函数内部的this指向window
    Person("abc"); //window.name --> abc

    //注:window对象中的方法都是全局函数,window对象中的属性都是全局变量
  • 2、方法调用
    var clear=function(){
        console.log(this.length);
    }
    
    var length=50;
    var tom={ c:clear,length:100 };
    tom.c();        //这里是方法调用的方式        
    //打印this.length 是50 还是100?
    //-->相当于:this是指向window还是指向tom呢? 
    //  -->结果为:100  
    //      -->this:tom

    //结论:由于clear函数被当成tom.c()这种方法的形式来进行调用,所以函数内部的this指向调用该方法的对象:tom 
  • 3、new调用(构造函数)
    //1、
    function fn(name){
        this.name=name;
    }
    //通过new关键字来调用的,那么这种方式就是构造函数的构造函数的调用方式,那么函数内部的this就是该构造函数的实例
    var _n=new fn("小明");  //_n有个name属性,值为:小明

    //2、
    function jQuery(){
        var _init=jQuery.prototype.init;
        //_init就是一个构造函数
        return new _init();
    }
    jQuery.prototype={
        constructor:jQuery,
        length:100,
        init:function(){
            //this可以访问到实例本身的属性,也可以访问到init.prototype中的属性
            //这里的init.prototype并不是jQuery.prototype
            console.log(this.length);   // undefined
        }
    }

  • 4、上下文调用方式(call、apply、bind)

    • 上下文模式应用场景:

      • 一些需要指定this的情况,比如$.each方法回调函数内部的this
      • 判断数据类型:
        • Object.prototype.toString.call(1);
    • call、apply

     //call、apply
      function f1(){
          console.log(this);
      }
      //call方法的第一个参数决定了函数内部的this的值
      f1.call([1,3,5])
      f1.call({age:20,height:1000})
      f1.call(1)      
      f1.call("abc")
      f1.call(true);
      f1.call(null)
      f1.call(undefined);
    
      //上述代码可以用apply完全替换
    
      //总结:
      //call方法的第一个参数:
      //1、如果是一个对象类型,那么函数内部的this指向该对象
      //2、如果是undefined、null,那么函数内部的this指向window
      //3、如果是数字-->this:对应的Number构造函数的实例
      //      -->   1   --> new Number(1)
      //4、如果是字符串-->this:String构造函数的实例
      //      --> "abc"   --> new String("abc")
      //5、如果是布尔值-->this:Boolean构造函数的实例
      //      --> false   --> new Boolean(false)
    
    
    • call和apply异同:
      • call和apply都可以改变函数内部的this的值
      • 不同的地方:传参的形式不同
      function toString(a,b,c){
          console.log(a+" "+b+" "+c);
      }
      toString.call(null,1,3,5)     //"1 3 5"
      toString.apply(null,[1,3,5])  //"1 3 5"
    
    • bind
      var obj = {
          age:18,
          run : function(){
              console.log(this);  //this:obj
              
              var _that=this;
    
              setTimeout(function(){
                  //this指向window
                  console.log(this.age); //undefined是正确的
                  console.log(_that.age); //18
                  
              },50);
          }
      }
      obj.run();
    
      //bind是es5中才有的(IE9+)
      var obj5 = {
          age:18,
          run : function(){
              console.log(this);  //this:obj5
    
              setTimeout((function(){
                  console.log(this.age); //18
              }).bind(this),50);  //this:obj5
              //通过执行了bind方法,匿名函数本身并没有执行,只是改变了该函数内部的this的值,指向obj5
          }
      }
      obj5.run();
      
      //bind基本用法
      function speed(){
          console.log(this.seconds);
      }
      //执行了bind方法之后,产生了一个新函数,这个新函数里面的逻辑和原来还是一样的,唯一的不同是this指向{seconds:100}
      var speedBind = speed.bind({ seconds:100 });
      speedBind();    //100
    
      (function eat(){
          console.log(this.seconds);
      }).bind({ seconds:360 })()  //360
    
      var obj={
          name:"西瓜",
          drink:(function(){
              //this指向了:{ name:"橙汁" }
              console.log(this.name);
          }).bind({ name:"橙汁" })
      }
      obj.drink();    //"橙汁"
    
      var p10={
          height:88,
          run:function(){
              //this
              setInterval((function(){
                  console.log(this.height);   //88
              }).bind(this),100)  
          }
      }
      p10.run();
    ``
    
posted @ 2020-05-18 23:22  十一是假期啊  阅读(988)  评论(0编辑  收藏  举报