對於 Function.call()的深刻理解

Function 做爲 JavaScript 的內置對象,擁有如下兩個方法:javascript

  • Function.call();
  • Function.apply();

這兩個方法所實現的功能都是相同的:將函數做爲對想象的方法調用。(引用自《JavaScript 權威指南》P.768 Function.call())。這二者的不一樣之處在於參數的類型不同。java

具體兩者不一樣之處不是本文的重點,讀者若想了解能夠自行搜索。node


言歸正傳,本文將經過實現相似 Function.call()的功能函數來深刻解釋其函數內部的運行機制!git

Function.call()的需求分析

在研究該函數內部的運行機制以前,咱們先來了解如下該函數具體是要實現什麼樣的功能?github

根據《JavaScript 權威指南》P.768 有關 Function.call(thisobj, args...)的描述,其詳細解釋以下:bash

call()將指定的函數 function 做爲對象 thisobj 的方法來調用,並傳入參數列表中 thisobj 以後的參數。返回的是調用 function 的返回值。在函數體內,關鍵字 this 指代 thisobj 對象,若是 thisobj 位 null,則使用全局對象。app

也就是說 call()函數將會用 thisobj 來調用指定的函數 function,並返回結果。函數

Function.prototype.myCall()方法設計

在有了上述描述以後,咱們就能夠着手設計方法了!咱們先寫一下僞代碼:測試

Function.prototype.myCall = function(obj) {
    let object = 若是有傳入對象obj傳入則保留obj,不然爲全局對象
    在object上添加一個臨時的屬性fn
    將this賦值給fn	//讓this所指向的對象(函數的調用者)成爲object的一個屬性。
    傳參給object.fn(),而且執行該方法,將返回值保留。
    刪除object上的臨時屬性fn。
    返回以前保留的保留值。
}
複製代碼

Function.prototype.myCall()的實現

有了以前的設計以後,就是依葫蘆畫瓢,將僞代碼進行實現。具體實現以下:ui

Function.prototype.myCall = function(obj) {
  let object = obj || global; // node環境中的全局對象
  object.fn = this;
  var args = [];
  for (let i = 1, len = arguments.length; i < len; i++) {
    args.push("arguments[" + i + "]");
  }
  let result = eval("object.fn(" + args + ")");
  delete object.fn;
  return result;
};
複製代碼

Function.prototype.myCall()的測試

這裏將會用列出兩個例子,一方面是爲了測試 myCall()的功能實現,另外一方面是爲了加深對於 call()函數的理解!

  1. 案例一是一個簡單的函數調用,即用 fn2()這個函數對象,調用 fn1(a)這個方法,具體代碼以下:

    function fn1(a) {
      console.log(a);
    }
    
    function fn2() {
      console.log("*");
    }
    
    fn1.myCall(fn2, "HelloWorld!"); //輸出HelloWorld!
    複製代碼

    在這段代碼 中,最終咱們是要運行 fn1.myCall(fn2, "HelloWorld!")。

    在運行這段代碼的時候,myCall()函數中的 this 指向調用者 fn1。myCall()的第一個參數接收了 arguments 的第一個參數,也就是 fn2,而且賦給了 object 變量。咱們將 this 所指向的對象添加爲 objec 的臨時屬性 fn。以後執行 object.fn()函數,實際上就是執行了臨時添加在 object 上的 this 對象,也就是這裏的 fn1。咱們將傳入的 arguments 從第二個開始傳入到 object.fn()中而且執行,將返回值保留。最後銷燬臨時屬性 fn,返回保留值。

  2. 案例二是 myCall 的連環調用,具體代碼以下:

    function fn1() {
      console.log(1);
    }
    function fn2() {
      console.log(2);
    }
    
    fn1.myCall.myCall(fn2); //輸出2
    複製代碼

    這段代碼理解起來講複雜也不復雜,關鍵是要理解是誰在調用誰的關係。

    • 首先函數從左往右進行執行,先查找fn1.myCall.myCall(fn2);中的粗體部分。

      在執行前半段的時候,其實是一個對象屬性查找的過程,最終依據原型鏈(在這裏默認讀者明白什麼是原型鏈了,若是不清楚原型鏈,請自行查找資料)查找到存在在原型鏈中的 Function.prototype.myCall 屬性,其實際就是一個方法。因此上述代碼實際上能夠與另外一句代碼相等:

      //這兩句代碼的意思是同樣的!
      fn1.myCall.myCall(fn2);
      Function.prototype.myCall.myCall(fn2);
      複製代碼
    • 這裏咱們就直接用 Function.prototype.myCall.**myCall(fn2)**進行解釋。

      第二步將執行後半段粗體部分。

      • 先將 fn2 賦給臨時變量 object。
      • this 所指向的對象就是一個函數對象:Function.prototype.myCall()。因此賦給 object.fn 臨時屬性指向的就是 Function.prototype.myCall()方法。
      • 因爲 arguments 長度爲 1,因此直接執行 object.fn()方法,也就是 object.myCall(),也就是 fn2.myCall()
      • fn2.myCall()實際上執行的是 global.fn2(),具體執行過程就再也不熬述了,故輸出了 2,沒有返回值。
      • 銷燬臨時屬性 fn。
      • 函數執行完畢。

      這裏咱們將一些幫助理解的測試代碼給你們粘貼一下:

      fn1.myCall.myCall(fn2); //輸出2
      Function.prototype.myCall.myCall(fn2); //輸出2
      fn2.myCall(); //輸出2
      複製代碼

2019/03/29

AJie

相關文章
相關標籤/搜索