隨筆——js中的this指向,apply()與 call()

宮商角徵羽發表於2018-05-14

js中apply和Math.max()函式(原文

  1. apply()

    Function.apply() 是JS的一個OOP特性,一般用來模擬繼承和擴充套件this的用途,對於上面這段程式碼,可以這樣去理解: XXX.apply() 是一個呼叫函式的方法,其引數為:apply(Function, Args),Function為要呼叫的方法,Args是引數列表,當Function為null時,預設為上文

    Math.max.apply(null, arr)
    複製程式碼

    可認為是

    apply(Math.max, arr)
    複製程式碼

    然後,arr是一個引數列表,對於max方法,其引數是若干個數,即:

    arr = [a, b, c, d, ...]
    複製程式碼

    代入原式中:

    Math.max.apply(null, [a, b, c, d, ...])
    複製程式碼

    等同於:

    Math.max(a, b, c, d, ...)
    複製程式碼
  2. 區別:

    Math.math() 方法可以求出給定引數中最大的數,但是引數不能是陣列。

    例如:

    > Math.max('1','2','3.1','3.2')
    < 3.2
    > Math.min(1,0,-1)
    < -1
    複製程式碼

    此時可用 apply() 解決(第一個引數為null,第二個引數為陣列; apply() 如何實現的參見上文 apply() 的轉換介紹):

    > Math.max.apply(null, ['1','2','3.1','3.2'])
    < 3.2
    > Math.min.apply(null, [1,0,-1])
    < -1
    複製程式碼

JavaScript中的this(原文

  1. this

    在 JavaScript 中,處處使用者 this ,但是,很多時候 this 指向並不固定,而是隨著它的執行環境的改變而改變。總結一句:this總是指向呼叫它所在方法的物件

  2. this 在函式裡

    這種方式也稱為“全域性性的函式呼叫”,例如:

    <script type='text/javascript'>
        var name='Hello_World';
        function test(){
            this.name='Hello_JavaScript';
        }
        
        test();
        
        console.log(this.name);     //Hello_JavaScript
        console.log(window.name);   //Hello_JavaScript
        console.log(name);          //Hello_JavaScript
    <script>
    複製程式碼

    通過結果可以更加證明了全域性的name在函式內部被修改了,因為這個函式內部的this指的就是window。

    總結:對於全域性性函式呼叫,函式內部的this就是指的全域性物件window,即是:this是呼叫函式所在的物件。實際上這個test()函式是由全域性物件window來去呼叫的,那麼函式內部的this當然就指的是window

  3. this 在建構函式裡

    <script type='text/javascript'>
        var name='Hello_World';
        function test(){
            this.name='Hello_JavaScript';
        }
        
        var person = new test();
        
        console.log(person.name);     //Hello_JavaScript
        console.log(window.name);   //Hello_World
    <script>
    複製程式碼

    分析:我們通過new關鍵字建立一個物件的例項,可以發現new關鍵字改變了this的指向,將這個this指向了物件person。在建構函式內部,我們對this.name=“HelloWorld”進行重新賦值,並沒有改變全域性變數name的值。

    總結:宣告一個建構函式的例項物件時,建構函式內部的this都會指向新的例項物件,或者說,建構函式內部的this指向的是新建立的物件本身

  4. 在物件的方法中呼叫

    <script type='text/javascript'>
        var name='Hello_World';
        
        var person= {
          name:'Hello_JavaScript',
          info:function(){
            alert(this.name);       
          }
        }
        
        person.info();        //Hello_JavaScript
    <script>
    複製程式碼

    總結:當person物件呼叫info()函式時,info函式內部的this指向的就是person物件。即,當this出現在物件的方法中時,那麼該方法內部的this指向的就是這個物件本身,也就是說this指向的呼叫函式的物件

JavaScript之call和apply(原文

  1. 使用call和apply的作用

    上文提到 this 的出現的三種方式,總結為兩種:

    • 直接呼叫

      test(), 此種呼叫方式中,函式內部的this指向的window

    • 建構函式形式的呼叫

      var person = new test(); 此種方式呼叫中,函式內部的this指向的是person

    以上兩種方式,實際上可以這麼說,函式內部的this都是代表當前物件,只不過是JavaScript中函式內部的this會隨著程式而指向不同的物件

    那麼我的問題是:我們能不能手動修改this的指向呢?

    答案:可以的,使用call或者apply。這個也就是call和apply的作用 → 改變函式內部this的指向

  2. 應用場景

    //程式碼一
    <script type='text/javascript'>
        function Person(name,age){
          this.name=name;
          this.age=age;
        }
        
        function info(){
          alert(this.name +','+this.age);
        }
        
        var p1=new Person('jack','20');
    <script>
    複製程式碼

    問題:我現在想借用這個info方法來去實現對p1物件的列印,怎麼做?

    方案一:直接呼叫info函式,即:info() ;通過上面的講解,仔細想想,肯定不行,因為這樣呼叫的話,info函式內部的this其實是指向的window。

    方案二:通過物件呼叫,即 p1.info() ;其實也不行,因為p1物件壓根就沒有info這個方法,p1物件只有name和age屬性。

    解決方法如下:

    //程式碼二
    <script type='text/javascript'>
       function Person(name,age){
         this.name=name;
         this.age=age;
       }
       
       function info(){
         alert(this.name +','+this.age);
       }
       
       var p1=new Person('jack','20');
       p1.show=info;
       p1.show();
       
    <script>
    複製程式碼

    可以發現我們通過向p1物件新增了一個show屬性,而這個show屬性的值其實是一個函式的地址,是一個函式物件,然後通過p1.show()就可以實現列印了。此種方法確實可以實現功能,但是這種方法是通過為p1物件新增屬性完成的,如果仍然有類似的需求,是不是都要向p1物件新增屬性來完成需求呢,這樣就會導致p1物件的佔用空間越來越大,所以方式並不優雅

    針對上面的問題,本質上就是想通過修改info函式內部的this指標的問題來完成對當前物件的一個列印,那麼我們可以在不增加屬性的方式上來完成功能,這個就需要使用到了call和apply。

  3. call和apply的使用

    • 功能:改變this指向,使用指定的物件呼叫當前函式

    • 語法:

      call([thisObj[,arg1[, arg2[, [,.argN]]]]])
      複製程式碼
      apply(thisObj[,argArray])
      複製程式碼
    • 說明:

      1. 兩個方法的功能完全一樣,唯一區別就是引數。

        對於第一個引數來說thisObj,作用是一樣的,用作代表當前物件的物件,說白了就表示的是函式執行時,this指向誰。

        對於 第二個引數apply要求傳入的是一個引數陣列 ,也就是說將一系列引數組成一個陣列傳入,而 對於call來說,引數以雜湊的值的方式傳入 。例如,func(thisObj,arg1,arg2,arg3...)對應的apply用法就是func(thisObj,[arg1,arg2,arg3...])。本文以call方法為例

      2. 這兩個方法都是Function物件中的方法,因為我們定義的每個物件都擁有該方法。

      3. call 方法可以用來代替另一個物件呼叫一個方法。call 方法可將一個函式的物件上下文從初始的上下文改變為由 thisObj 指定的新物件,如果沒有提供 thisObj 引數,那麼 Global 物件被用作 thisObj。(?)

      4. 使用call和apply解決上面程式碼的問題

      //程式碼三
      <script type='text/javascript'>
          function Person(name,age){
            this.name=name;
            this.age=age;
          }
          
          function info(){
            alert(this.name +','+this.age);
          }
          
          var p1=new Person('jack','20');
          
          info.call(p1);      //或者 info.apply(p1)
          
      <script>
      複製程式碼

      分析:

      當在函式中呼叫call方法時,函式內部的this會自動指向call方法中的第一個引數。上面的例子中,當執行info.call(p1)時,info函式內部的this則會自動指向p1物件,所以當然就可以call這種方式來完成對p1物件的列印。

      1. call()重寫
      <script type='text/javascript'>
          function Person(){
              this.name = 'Hello_World';
              this.info=function(){
                  alert(this.name)
              }
          }
          
          function Cat(){
              this.name='貓';
          }
          var cat =new Cat();
          
          Person.call(cat);
          
          cat.info();    //Hello_World
          
      <script>
      複製程式碼

      上面程式碼最終會列印:‘Hello_World’,這個答案最關鍵是 Person.call(cat) 這一行程式碼,仔細去想想究竟發生了什麼事情,當呼叫call方法時,函式Person內部的this其實已經自動的指向了cat物件,相當於就是給cat物件執行了下面的兩行程式碼:

      this.name='Hello_World',
      this.info=function(){
          alert(this.name)
      }
      複製程式碼

      然後重寫了原來cat物件中的name屬性,把name由“貓”改成了“ Hello_World ”,而且並獲得了一個新的info方法(可以這麼理解,相當於給cat物件新增了一個info屬性),所以cat物件當然可以呼叫info方法了,所以結果就是“ Hello_World ”。apply的使用和call的功能相同,使用方式也很類似。

    關於call和apply的使用,剛好在 知乎 看到一個很生動形象的比喻,放在這裡希望有助於記憶。


    貓吃魚,狗吃肉,奧特曼打小怪獸。

    有天狗想吃魚了

    貓.吃魚.call(狗,魚)

    狗就吃到魚了

    貓成精了,想打怪獸

    奧特曼.打小怪獸.call(貓,小怪獸)

相關文章