JavaScript~~call的解析


javascript 中關於call方法的詳解。

  

     關於javascript中的call方法,網上查了一些資料總是不得詳解。總結網上的觀點,call有兩個妙用:

                      1: 繼承。(不太喜歡這種繼承方式。)

                      2: 修改函數運行時的this指針。

 

     js中關於call的解釋如下:

                    

 

                          js關於call的這份文檔容易讓人迷糊。而《javascript權威指南》對call的描述就比較容易理解了。

 

                                   

 

                           

                          注意紅色框中的部分,f.call(o)其原理就是先通過 o.m = f 將 f作爲o的某個臨時屬性m存儲,然後執行m,執行完畢後將m屬性刪除。

                          如 function f(){

                                                  var a = "name";

                                                  this.b = "test1";

                                                  this.add = function (){ return "add" }

                                       }

                               function o(){

                                                this. c = "c";  

                                       }

                               f.all(o);

                                                              圖  1

 

                             圖1中 f.call(o)其實相當於: function o(){ 

                                                                      this.c = "c" ;                                                          

                                                                      var a = "name";

                                                                      this.b = "test1";

                                                                      this.add = function (){ return "add" }

                                                          }

                             說白了,就是把f的方法在o中走一遍,但不做保存。既然不做保存,那麼如何通過call實現繼承和修改函數運行時的this指針等妙用?關鍵在於this,對,關鍵還是在於this的作用域。在之前的文章反覆說過,this的作用域不是定義它的函數的作用域,而是執行時的作用域。

                              如下例子:

                                         

                                                                            圖     2

 

                               如圖2, 在執行A.call(this)之前,輸出的this 不包含任何屬性和方法,之後則繼承了A的屬性和方法。還是按照上面的方法解釋,執行完A.call(this) (this此時代表執行時的作用域,即B)後,B構造函數如下:

                                     function B()

                                       {

                                          console.log(this);                                        

                                          this.test1 = "test1";

                                          this.test2 = "test2";

                                          this.add = function(){ return this.test1 + this.test2 ; }

                                           console.log(this); 

 

                                        }

                                   因爲調用A.call(this)的時候,this作用域已經發生改變了,代表的都是B(),哪怕執行A中的方法和屬性的時候,this也是代表着執行時的作用域B();所以如此才能實現繼承,即將A()中的屬性賦值和方法定義在B()中再走一遍。其實此時也修改了函數運行時的this指針。

 

                                    關於call的第二個妙用,修改函數運行時的this指針:

                                        這裏採用一位貼吧大神的回答,做了一個測試例子,其實應該也是js中關於each遍歷的定義。

                                         

                                                                                          圖             3

 

                                       如圖3,第一個中直接調用fn方法,其中因爲沒有定義函數作用域,輸出的this表示window對象,即全局對象。而第二個中通過fn.call(array[index]  , index , array[index] )將fn方法放到array[index]作用域中執行,輸出的this表示的是array[index]對象。

                                      換一個視角:

                                              

                                                                            圖   4

 

 

                                       如圖 4 , 通過each方法中調用call方法可以實現遍歷。我們假設array[index]是DOM元素集合,如標籤爲li的所有集合,假設fn是我們自定義實現的方法,不就是我們用的each遍歷DOM元素的方法嗎?

                                       

                                       

                                                                               圖               5

 

                                           如圖5,簡單的代碼即實現了遍歷DOM元素的each方法。


複製代碼

function fn1(){   console.log(1);}function fn2(){    console.log(2);}fn1.call(fn2);     //輸出 1
 fn1.call.call(fn2);  //輸出 2

複製代碼

   對於 fn1.call(fn2);我能夠理解,這段代碼僅僅 使得 fn1對象的this指向了fn2;但是最終不影響fn1函數的執行。因爲fn1中不包含對this的操作。不過 fn1.call.call(fn2);實在是令我費解。我一時半會沒有領會筆者的表達方式。花了很長時間去領會。最終還是看其他大神的博客才得以有所體會。究其原因還是在於對 call 函數的原理的研究。call 函數執行的時候到底幹了什麼????直接粘貼代碼(摘自CSDN:深入JS系列(一:call, apply, bind實現)):

複製代碼

Function.prototype.es3Call = function (context) {   var content = context || window;
   content.fn = this;   var args = [];   // arguments是類數組對象,遍歷之前需要保存長度,過濾出第一個傳參
   for (var i = 1, len = arguments.length ; i < len; i++) {      // 避免object之類傳入
      args.push('arguments[' + i + ']');
    }   var result = eval('content.fn('+args+')');   delete content.fn;   return result;
 }

複製代碼

 

 

 

  在本機上調試後發現,執行  fn1.call.call(fn2); 的結果與 fn1.es3Call.es3Call(fn2);的結果一致。說明其基本還原了call函數的原理。故結合原理代碼總結就是:

    1:把傳入的第一個參數作爲 call 函數內部的一個臨時對象 context;

    2:給 context 對象一個屬性 fn , 我稱呼其爲實際執行函數 context.fn ;讓 this 關鍵字(僅僅是關鍵字,而不是this對象)指向這個屬性 ,即 context.fn = this ; 注意 : 在這裏的 this 對象指向的是調用call()函數的函數對象。如 fn1.call(fn2);在執行 call 函數時,call 函數內部的this指向的是fn1;然而 fn1.call.call(fn2);在執行 call() 函數時(注意這裏必須是打了小括號“()”纔算執行函數,fn1.call訪問的是一個對象),call函數內部的 this 指向的是 fn1.call 。

    3:將傳入call函數的其他參數,放入臨時數組arr[];

    4:利用 eval (筆者採用es3的方法實現,也可以利用其他方式實現)。執行 context.fn( [args] ) ; 實際就是執行 this( [args] );結合第2點。

    5:執行完成後再把 context.fn 刪除。返回執行 this( [args] ) 的結果。

   總結上邊 5 點之後,能夠大概解釋出 fn1.call.call(fn2);的執行結果爲什麼是 輸出 2 了。

   首先 調用call 函數時,也就是 fn1.call.call(fn2) ;加粗部分;先將 fn2 作爲 臨時的 context 對象 。然後 將 fn1.call這個函數對象作爲 實際執行函數屬性 : context.fn = fn1.call;注意:fn1.call會通過原型鏈找到最終的對象。其本質爲 Function.prototype.call; 然後檢查其他參數,沒有了。直接執行 fn1.call()函數 ,即 context.fn();此時函數的本質還是 Function.prototype.call 函數對象。不過執行這個函數的環境還是在 Function.prototype.call()中,只不過是第一次調用的call()函數中。第一次調用的call()函數將this關鍵字指向了 fn2 ;故而 在  fn1.call.call(fn2) ;加粗部分的 函數中執行的 call函數執行過程中的 this指向的是 fn2;傳入的參數爲空,故而 新的 call()函數對象 的this關鍵字 被替換爲window; 而執行 this()時,就是執行 fn2();不涉及 this操作。故最終輸出2。

  這樣就能夠較好的解釋 fn1.call.call(fn2);的輸出結果了。爲了驗證這個過程。可以這段代碼查看各個最終執行函數的this對象的指向:

function func(){
    console.log(this);
}
func.call(func);     //輸出funcfunc.call.call(func); //輸出window

 

 

  至於 func 爲什麼指向 window MDN官網上有具體解釋(如下圖)。如果執行 func.call.call(func,2);還會出來結果 Number{2}。
  

   以上。就是我目前對 js 中call 函數的理解。


轉載來源:https://www.cnblogs.com/donghezi/p/9742778.html

              https://www.cnblogs.com/f-dream/p/4950918.html



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章