1、call和apply的區別
call和apply唯一的區別是傳入引數的形式不同。
apply接受兩個引數,第一個引數指定了函式體內this物件的指向,第二個引數為一個帶下標的集合,可以是陣列,也可以是類陣列,apply方法會把集合中的元素作為引數傳遞給被呼叫的函式。
var func = function (a, b, c) {
console.log ([a, b, c]); // 輸出 [1, 2, 3]
};
func.apply(null, [1, 2, 3]); // 陣列中的1,2,3分別對應引數列表中的a,b,c
複製程式碼
call跟apply相同的是,第一個引數也是代表函式體內的this指向,第二個引數開始往後,引數依次被傳入函式內,傳入的引數數量不固定。
var func = function (a, b, c) {
console.log ([a, b, c]); //輸出:[1, 2, 3]
}
func.call (null, 1, 2, 3);
複製程式碼
當呼叫函式時,Javascript直譯器並不會計較形參和實參的數量、型別及順序,在Javascript內部統一都是用陣列表示。所以call其實就是包裝在apply上的一顆語法糖。 在使用call和apply的時候,如果傳入的第一個引數是null,函式體內的this會預設指向宿主物件,在瀏覽器環境裡,就是window。
var func = function (a, b, c) {
console.log (this === window); //輸出:true
};
func.apply(null, [1, 2, 3]);
複製程式碼
如果是嚴格模式下,函式體內的this還是null
var func = function (a, b, c) {
'use strict';
console.log (this === null); //輸出:true
};
func.apply(null, [1, 2, 3]);
複製程式碼
有時候call和apply還可以借用其他物件的方法,我們可以傳入null來替代某個具體物件:
Math.min.apply(null, [5, 2, 1, 3, 4]); //輸出:1
複製程式碼
2、call和apply的用途
(1) 可以改變this指向
call和apply最常見的用法就是用來改變函式內部的this指向。
var obj1 = {
name: 'lq'
};
var obj2 = {
name: 'xiaoming'
};
window.name = 'angelababy';
var getName = function () {
console.log (this.name);
};
getName(); // 輸出:angelababy
getName.call(obj1); // 輸出:lq
getName.call(obj2); // 輸出:xiaoming
複製程式碼
(2) Function.prototype.bind
幾乎所有的高階瀏覽器都內建了Function.prototype.bind方法用來指定函式內部this指向問題。如果不支援原生的Function.prototype.bind,我們可以自己實現一個:
Function.prototype.diyBind = function (context) {
var _this = this; // 儲存原函式
return function () { // 返回一個新的函式
return _this.apply(context, arguments); // 執行新的函式時,會將之前傳入的context當作新函式體內的this
}
};
var obj = {
name: 'lq'
}
var func = function () {
console.log (this.name);
}.diyBind(obj);
func();
複製程式碼
上面是簡化版的,下面是稍微複雜點的版本:
Function.prototype.binds = function(){
var _this = this, // 儲存原來的函式
context = [].shift.call(arguments), //擷取第一個引數,即是繫結的this上下文
args = [].slice.call(arguments); //剩餘的引數轉成陣列
return function(){ //返回一個新的函式
return _this.apply(context, [].concat.call(args,[].slice.call(arguments))); // 執行新函式的時候,會將之前傳入的context最為新函式體內的this,在此例中就是obj。用concat合併兩次傳入的引數,最為新函式的引數
}
}
var obj = {
name: 'lq'
}
var func = function(a, b, c, d){
console.log(this.name); // 輸出:lq
console.log([a, b, c, d]); // 輸出:[1, 2, 3, 4]
}.binds(obj,1, 2);
func(3,4);
複製程式碼
(3)借用其他物件的方法
借用的第一種場景是“借用建構函式”:
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('xiaoming');
console.log (b.getName()); //輸出:xiaoming
複製程式碼
借用的第二種場景是“借用Array.prototype物件上的方法”,比如:
(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方法。