this 跛蛋、 call 和 apply
this
跟別的語(yǔ)言大相徑庭的是糕档,JavaScript的 this 總是指向一個(gè)對(duì)象莉恼,而具體指向哪個(gè)對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境動(dòng)態(tài)綁定的,而非函數(shù)被聲明時(shí)的環(huán)境速那。
this 的指向
-
作為對(duì)象的方法調(diào)用俐银。
當(dāng)函數(shù)作為對(duì)象的方法被調(diào)用時(shí), this 指向該對(duì)象端仰。
var obj = {
a: 1,
getA: function() {
console.log(this === obj); //true
return this.a; //1
}
}
console.log(obj.getA());
-
作為普通函數(shù)調(diào)用捶惜。
this 總是指向全局對(duì)象window。
window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // 輸出:globalName
-
構(gòu)造器調(diào)用荔烧。
當(dāng)用 new 運(yùn)算符調(diào)用函數(shù)時(shí)吱七,該函數(shù)總會(huì)返回一個(gè)對(duì)象汽久,通常情況下,構(gòu)造器里的
this
就指向返回的這個(gè)對(duì)象踊餐。
var MyClass = function() {
this.name = 'steven';
return { // 顯式地返回一個(gè)對(duì)象
name: 'anne'
}
};
var obj = new MyClass();
console.log("使用構(gòu)造器調(diào)用:" + obj.name); // 輸出:anne
-
Function.prototype.call 或 Function.prototype.apply 調(diào)用景醇。
可以動(dòng)態(tài)地改變傳入函數(shù)的
this
。
var obj1 = {
name: 'steven',
getName: function() {
return this.name;
}
};
var obj2 = {
name: 'anne'
}
console.log(obj1.getName.call(obj2));// 輸出:anne
丟失的 this
?一個(gè)例子:使用getId
變量來(lái)代替document.getElementById
var getId = function(id){
return document.getElementById(id);
};
console.log(getId('div1').id)
? 如果getId
直接 來(lái)引用 document.getElementById 之后
吝岭,再調(diào)用getId
三痰,此時(shí)就成了普通函數(shù)調(diào)用,函數(shù)內(nèi)部的 this
指向了window
窜管,而不是原來(lái)的 document
酒觅,會(huì)出現(xiàn)報(bào)錯(cuò),情況如下:
var getId = document.getElementById;
console.log(getId('div1').id); //Uncaught TypeError: Illegal invocation (非法調(diào)用)
? 我們可以嘗試?yán)?apply 把 document 當(dāng)作 this 傳入 getId 函數(shù),幫助“修正” this:
var getId = (function(obj) {
return function() {
return obj.apply(document, arguments);
}
})(document.getElementById);
console.log(getId('div1').id)
call和apply
call和apply 的區(qū)別
Function.prototype.call 和 Function.prototype.apply 都是非常常用的方法微峰。它們的作用一模一樣舷丹,區(qū)別僅在于傳入?yún)?shù)形式的不同。
-
apply 接受兩個(gè)參數(shù):
- 第一個(gè)參數(shù)指定了函數(shù)體內(nèi) this 對(duì)象的指向
- 第二個(gè)參數(shù)為一個(gè)帶下標(biāo)的集合(數(shù)組或類(lèi)數(shù)組)蜓肆,
apply
方法把這個(gè)集合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù)
var func = function( a, b, c ){ alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ] }; func.apply( null, [ 1, 2, 3 ] );//1參為null颜凯,函數(shù)體內(nèi)的 this 會(huì)指向默認(rèn)的宿主對(duì)象,即window
-
call 傳入的參數(shù)數(shù)量不固定
- 第一個(gè)參數(shù)指定了函數(shù)體內(nèi) this 對(duì)象的指向
- 從第二個(gè)參數(shù)開(kāi)始往后仗扬,每個(gè)參數(shù)被依次傳入函數(shù)
var func = function( a, b, c ){ alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ] }; func.call( null, 1, 2, 3 );//1參為null症概,函數(shù)體內(nèi)的 this 會(huì)指向默認(rèn)的宿主對(duì)象,即window
當(dāng)調(diào)用一個(gè)函數(shù)時(shí)早芭,JavaScript 的解釋器并不會(huì)計(jì)較形參和實(shí)參在數(shù)量彼城、類(lèi)型以及順序上的區(qū)別,JavaScript的參數(shù)在內(nèi)部就是用一個(gè)數(shù)組來(lái)表示的退个。從這個(gè)意義上說(shuō)募壕,
apply
比call
的使用率更高,我們不必關(guān)心具體有多少參數(shù)被傳入函數(shù)语盈,只要用 apply 一股腦地推過(guò)去就可以了舱馅。當(dāng)使用
call
或者apply
的時(shí)候,如果我們傳入的第一個(gè)參數(shù)為null
刀荒,函數(shù)體內(nèi)的 this 會(huì)指向默認(rèn)的宿主對(duì)象代嗤,在瀏覽器中則是window
,但如果是在嚴(yán)格模式下,函數(shù)體內(nèi)的this
還是為null
缠借。有時(shí)候我們使用
call
或者apply
的目的不在于指定this
指向干毅,而是另有用途,比如借用其他對(duì)象的方法泼返。那么我們可以傳入null
來(lái)代替某個(gè)具體的對(duì)象硝逢。
call 和 apply 的用途
改變 this 指向
var obj1 = {
name: 'steven'
};
var obj2 = {
name: 'anne'
};
window.name = 'window';
var getName = function(name) {
console.log(this.name);
};
getName(); //'window'
getName.apply(obj1); //'steven'
getName.apply(obj2); //'anne'
在執(zhí)行getName.apply(obj1)
時(shí), getName
函數(shù)體內(nèi)的 this
就指向 obj1
對(duì)象,實(shí)際相當(dāng)于
var getName = function(name) {
console.log(obj1.name);
};
一個(gè)點(diǎn)擊的實(shí)例場(chǎng)景:
document.getElementById('div1').onclick = function() {
var _that = this; //需要額外申明一個(gè)中轉(zhuǎn)變量來(lái)存儲(chǔ)對(duì)象的this
var foo = function() {
alert(_that.id);
}
return foo();
};
在使用apply
或call
后:
document.getElementById('div1').onclick = function() {
var foo = function() {
alert(this.id);
}
foo.apply(this);
return foo();
};
Function.prototype.bind
大部分現(xiàn)代瀏覽器都實(shí)現(xiàn)了內(nèi)置的 Function.prototype.bind
趴捅,用來(lái)指定函數(shù)內(nèi)部的 this 指向垫毙,如果沒(méi)有的話(huà)模擬起來(lái)不困難:
Function.prototype.bind = function(context) {
var self = this; //保存原函數(shù)
return function() {
// 這句代碼才是執(zhí)行原來(lái)的 func 函數(shù)霹疫,并且指定 context對(duì)象為 func 函數(shù)體內(nèi)的 this 拱绑,也是我們想修正的 this 對(duì)象
return self.apply(context, arguments);
}
}
var obj = {
name: 'steven'
};
var func = function() {
alert(this.name); //輸出'steven'
}.bind(obj);
func();
再稍微復(fù)雜一些,使得可以往func
函數(shù)內(nèi)預(yù)先填寫(xiě)一些參數(shù)
Function.prototype.bind = function(){
var self = this, // 保存原函數(shù)
context = [].shift.call( arguments ), // 需要綁定的 this 上下文
args = [].slice.call( arguments ); // 剩余的參數(shù)轉(zhuǎn)成數(shù)組
return function(){ // 返回一個(gè)新的函數(shù)
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
// 執(zhí)行新的函數(shù)的時(shí)候丽蝎,會(huì)把之前傳入的 context 當(dāng)作新函數(shù)體內(nèi)的 this
// 并且組合兩次分別傳入的參數(shù)猎拨,作為新函數(shù)的參數(shù)
}
};
var obj = {
name: 'sven'
};
var func = function( a, b, c, d ){
alert ( this.name ); // 輸出:sven
alert ( [ a, b, c, d ] ) // 輸出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );
借用其他對(duì)象的方法
- 場(chǎng)景一:鳩占鵲巢
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('steven');
console.log(b.getName());
- 場(chǎng)景二:往
arguments
中添加一個(gè)新的元素,通常會(huì)借用Array.prototype.push
(function(){
Array.prototype.push.call(arguments,3);
console.log(arguments);
})(1,2,5,c)
在操作 arguments
的時(shí)候屠阻,我們經(jīng)常非常頻繁地找Array.prototype
對(duì)象借用方法红省,例如:
- 想把 arguments 轉(zhuǎn)成真正的數(shù)組的時(shí)候,可以借用
Array.prototype.slice
方法国觉。 - 想截去arguments 列表中的頭一個(gè)元素時(shí)吧恃,可以借用
Array.prototype.shift
方法。
我們甚至可以把“任意”對(duì)象傳入 Array.prototype.push
麻诀,但是對(duì)象要滿(mǎn)足以下兩個(gè)條件:
- 對(duì)象本身要可以存取屬性
- 對(duì)象的 length 屬性可讀寫(xiě)