快速理解JavaScript中call和apply原理

DaGege發表於2017-12-20

在瞭解call()apply()原理之前,我們必須對this的作用和使用方法有所瞭解,如果你熟悉this 的用法,那麼請直接往下看。

call方法:

語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定義:呼叫一個物件的一個方法,以另一個物件替換當前物件。
說明:call 方法可以用來代替另一個物件呼叫一個方法。call 方法可將一個函式的物件上下文從初始的上下文改變為由 thisObj 指定的新物件。 如果沒有提供 thisObj 引數,那麼 Global 物件被用作 thisObj。

apply方法:

語法:apply([thisObj[,argArray]])
定義:應用某一物件的一個方法,用另一個物件替換當前物件。
說明:如果 argArray 不是一個有效的陣列或者不是 arguments 物件,那麼將導致一個 TypeError。 如果沒有提供 argArray 和 thisObj 任何一個引數,那麼 Global 物件將被用作 thisObj, 並且無法被傳遞任何引數。

call()apply()的作用十分相似,只是引數型別上的差別,以適應不同的使用場景。它們都是為了改變函式執行時的 context(上下文)而存在的,再說的直白一點,就是為了改變函式內部 this 的指向。

舉例說明

我們有一句很經典的諺語,說的是:龍生龍,鳳生鳳,老鼠生來會打洞,這從遺傳上解釋是,動物的某些行為有可能是由一系列基因所調控的,但是,注意,我們偏偏想讓龍來打洞呢,該如何去實現?下面將圍繞這個話題來解釋call()apply()的原理。

var dragon = {
	name : 'foo'
	// other attribute
}

var mouse = {
	name : 'tom',
	makeHole : function(where){
		console.log(this.name + ' is making a hole in the ' + where)
	}
	// other attribute
}

mouse.makeHole.call(dragon,'hill')
複製程式碼

執行上面程式碼後會在控制檯上列印出:

快速理解JavaScript中call和apply原理

可以看出,我們宣告瞭一個dragon的物件,我們並沒有賦予它打洞的功能,但是我們使用call()繼承了mouse的方法,就可以做到mouse函式所能做到的事情。

這到底是怎麼做到的呢?讓我們來看看call()的引數: 第一個是一個物件,這個物件將代替Function類裡原本的this物件,我們傳入的是this,記住,這個thismakeHole函式裡指的是未來將要例項化這個函式的物件(我知道這有些拗口),當宣告瞭dragon的時候,這個this指的就是dragon。除了第一個引數,後面所有的引數都是傳給父函式本身使用的引數。

apply()call()功能幾乎一樣,唯一的區別就是apply()第二個引數只能是陣列,這個陣列將作為引數傳給原函式的引數列表arguments

模擬實現call()函式

call()函式是什麼樣的原理呢?我們用一個例項來幫助理解。

//建立Dragon
function Dragon(name) {
  this.name = name;
}

//建立一個說話的函式
function say(content) {
  console.log(this.name + ' : ' + content)
}

//模擬原生call函式
Function.prototype.myCall = function(context) {
  context = context || window;
  
  var args = [];
  context.fn = this;

  for (var i = 1; i < arguments.length; i++) {
    args.push(arguments[i]);
  };
  
  context.fn(...args);
  delete context.fn;
};

//例項化一個名字為'foo'的龍
var foo = new Dragon('foo')

//讓foo說話
say.myCall(foo, 'I can talk!')
複製程式碼

上面的程式碼很容易理解,唯一的困難點在於理解在原型鏈上的myCall函式 我們來分析實現的步驟:

  1. 做一個多場景適配,當myCall函式沒有接收到引數時,context對應的是window物件
  2. 建立一個空陣列,用於接收形參。
  3. 繫結this,這裡的this代表的就是上下文中的say函式。
  4. for迴圈將引數新增到args陣列,迴圈從1開始是因為第0位是foo物件,並非我們需要的引數
  5. 執行函式,並將args陣列作為rest引數傳入,這裡是ES6的寫法,不熟悉的同學參見阮一峰老師的rest 引數文件
  6. 刪除函式

列印結果為:

快速理解JavaScript中call和apply原理

可以看到,這裡我們實現了讓一個叫做foo的龍說話! apply()函式實現方式同樣類似,可以修改上述例子實現,主要是在引數一部分做處理。

相關文章