來源:JavaScript設計模式與開發實踐
Function.prototype.call 和Function.prototype.apply的作用一模一樣,區別僅在於傳入參數形式的不同。
apply 接受兩個參數,第一個是函數體內this 對象的指向,第二個參數爲一個帶下標的集合,這個集合可以爲數組,也可以爲類數組,apply 方法把這個集合中的元素作爲參數傳遞給被調用的函數:
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] );
在這段代碼中,參數 1、2、3 被放在數組中一起傳入func 函數,它們分別對應func 參數列表中的a、b、c。
call 傳入的參數數量不固定,跟apply 相同的是,第一個參數也是代表函數體內的this 指向,從第二個參數開始往後,每個參數被依次傳入函數:
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );
當調用一個函數時,JavaScript 的解釋器並不會計較形參和實參在數量、類型以及順序上的區別,JavaScript 的參數在內部就是用一個數組來表示的。從這個意義上說,apply 比call 的使用更高,我們不必關心具體有多少參數被傳入函數,只要用apply 一股腦地推過去就可以了。
call 是包裝在apply 上面的一顆語法糖,如果我們明確地知道函數接受多少個參數,而且想一目瞭然地表達形參和實參的對應關係,那麼也可以用call 來傳送參數。
當使用call 或者apply 的時候,如果我們傳入的第一個參數爲null,函數體內的this 會指向默認的宿主對象,在瀏覽器中則是window:
var func = function( a, b, c ){
alert ( this === window ); // 輸出true
};
func.apply( null, [ 1, 2, 3 ] );
//但如果是在嚴格模式下,函數體內的this 還是爲null:
var func = function( a, b, c ){
"use strict";
alert ( this === null ); // 輸出true
}
func.apply( null, [ 1, 2, 3 ] );
call和apply的用途
1. 改變this 指向
call 和apply 最常見的用途是改變函數內部的this 指向,我們來看個例子:
var obj1 = { name: 'sven' };
var obj2 = { name: 'anne'};
window.name = 'window';
var getName = function(){
alert ( this.name );
};
getName(); // 輸出: window
getName.call( obj1 ); // 輸出: sven
getName.call( obj2 ); // 輸出: anne
當執行getName.call( obj1 )這句代碼時,getName 函數體內的this 就指向obj1 對象,所以此處的
var getName = function(){
alert ( this.name );
};
//實際上相當於:
var getName = function(){
alert ( obj1.name ); // 輸出: sven
};
在實際開發中,,經常會遇到this 指向被不經意改變的場景,比如有一個div 節點,div 節點的onclick 事件中的this 本來是指向這個div 的,假如該事件函數中有一個內部函數func,在事件內部調用func 函數時,func 函數體內的this就指向了window,而不是我們預期的div。
e.g. 1
document.getElementById( 'div1' ).onclick = function(){
var func = function(){
alert ( this.id ); // 輸出:div1
}
func.call( this );
};
e.g.2
document.getElementById = (function( func ){
return function(){
return func.apply( document, arguments );
}
})( document.getElementById );
var getId = document.getElementById;
var div = getId( 'div1' );
alert ( div.id ); // 輸出: div1
2. 借用其他對象的方法
杜鵑既不會築巢,也不會孵雛,而是把自己的蛋寄託給雲雀等其他鳥類,讓它們代爲孵化和養育。同樣,在JavaScript 中也存在類似的借用現象。
借用方法的第一種場景是“借用構造函數”,通過這種技術,可以實現一些類似繼承的效果:
var A = function( name ){
this.name = name;
};
var B = function(){
A.apply( this, arguments );
};
B.prototype.getName = function(){
return this.name;
};
var b = new B( 'sven' );
console.log( b.getName() ); // 輸出: 'sven'
借用方法的第二種運用場景
函數的參數列表arguments 是一個類數組對象,雖然它也有“下標”,但它並非真正的數組,所以也不能像數組一樣,進行排序操作或者往集合裏添加一個新的元素。這種情況下,我們常常會借用Array.prototype 對象上的方法。比如想往arguments 中添加一個新的元素,通常會借用
Array.prototype.push:
(function(){
Array.prototype.push.call( arguments, 3 );
console.log ( arguments ); // 輸出[1,2,3]
})( 1, 2 );
在操作arguments 的時候,我們經常非常頻繁地找Array.prototype 對象借用方法。
想把arguments 轉成真正的數組的時候,可以借用Array.prototype.slice 方法;想截去arguments 列表中的頭一個元素時,又可以借用Array.prototype.shift 方法。