閉包、作用域、函數的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();
    ``


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM