this的指向
在js中琐脏,this總是回指向一個對象,需要注意的是,具體指向哪個對象實在運行時基于函數(shù)的執(zhí)行環(huán)境動態(tài)綁定的日裙,并非函數(shù)聲明時的環(huán)境吹艇。又或者說:this 永遠(yuǎn)指向最后調(diào)用它的那個對象。
this的指向可以大致分為四種:
- 作為對象的方法使用
- 作為普通函數(shù)使用
- 構(gòu)造函數(shù)調(diào)用
- call阅签,apply方法調(diào)用
作為對象的方法調(diào)用
在 JavaScript 中, 函數(shù)就是對象掐暮。
當(dāng)函數(shù)作為對象的方法被調(diào)用時蝎抽,this指向該對象政钟。
var name = "globalName";
var obj = {
name: 'objName',
getName: function () {
console.log(this.name);
}
}
obj.getName(); // objName
此時this指向最后調(diào)用他的對象,也就是obj樟结。
作為普通函數(shù)調(diào)用
當(dāng)函數(shù)不作為對象的屬性調(diào)用時养交,就是一個普通函數(shù),這樣的情況在 JavaScript 的在瀏覽器中的非嚴(yán)格模式默認(rèn)是屬于全局對象 window 的瓢宦,在嚴(yán)格模式碎连,就是 undefined。
var name = "globalName";
var obj = {
name: 'objName',
getName: function () {
console.log(this.name);
}
}
var getName = obj.getName;
getName(); // globalName
此時雖然getName()是在對象obj中聲明的方法驮履,但是在實際執(zhí)行該方法的時候鱼辙,其調(diào)用對象已經(jīng)不是obj,而是全局對象 window玫镐,那么這時getName()就是作為一個普通函數(shù)調(diào)用倒戏。
構(gòu)造器調(diào)用
JavaScript中沒有類,但是可以通過構(gòu)造器創(chuàng)建對象恐似,如果函數(shù)調(diào)用前使用了new關(guān)鍵詞杜跷,則是調(diào)用了構(gòu)造函數(shù),返回的總是一個對象矫夷,這使得構(gòu)造器看起來像一個類葛闷。
// 構(gòu)造器
function constructor (name) {
this.name = name;
}
// new object
var obj = new constructor('objName');
obj.name; // objName
但是使用構(gòu)造器創(chuàng)建對象時,需要注意一個問題双藕,如果構(gòu)造器顯示返回一個object類型的對象淑趾,那么此次new的結(jié)果會返回該對象。
// 構(gòu)造器
function constructor (name) {
this.name = name;
return {
name: 'anotherName'
}
}
// new object
var obj = new constructor('objName');
obj.name; // anotherName
call忧陪,apply方法調(diào)用
與前面的幾種方式相比治笨,使用call或者apply可以動態(tài)地修改傳入函數(shù)的this:
var obj1 = {
name: 'obj1Name',
getName: function () {
console.log(this.name);
}
}
var obj2 = {
name: 'obj2Name'
}
obj1.getName(); // obj1Name
obj1.getName.call(obj2); // obj2Name
obj1.getName.apply(obj2); // obj2Name
由于call或者apply方法改變了getName方法中this的指向,即將對象obj1的方法“借”給了對象obj2去執(zhí)行赤嚼。
call 和 apply
Function.prototype.call 和 Function.prototype.apply 在實際開發(fā)中旷赖,特別是在函數(shù)式風(fēng)格的代碼編寫中,非常有用更卒,去看大神的代碼等孵,也會經(jīng)常看到蹂空,應(yīng)用非常廣泛俯萌。
call 和 apply 的區(qū)別
Function.prototype.call 和 Function.prototype.apply 這兩個方法的作用一模一樣果录,區(qū)別在于傳參形式不同。
apply 一共只有兩個參數(shù):
第一個參數(shù) 是函數(shù)體中this指向的對象咐熙,
第二個參數(shù) 為一個帶下標(biāo)的集合弱恒,可以是數(shù)組或類數(shù)組,
apply 會把這個集合中的元素作為參數(shù)一一傳遞給被調(diào)用的函數(shù)棋恼。
var func = function (arg1, arg2) {
console.log(arg1, arg2)
};
func.apply(obj, ['第一個參數(shù)', '第二個參數(shù)']);
語法:func.apply(obj, [arg1, arg2, ...])
call 則參數(shù)數(shù)量不固定返弹,但是跟 apply 一樣
第一個參數(shù) 是函數(shù)體中this指向的對象
第二個參數(shù)開始,每個參數(shù)都會被一一傳遞給被調(diào)用的函數(shù)爪飘。
var func = function (arg1, arg2) {
console.log(arg1, arg2)
};
func.call(obj, '第一個參數(shù)', '第二個參數(shù)');
語法:func.apply(obj, arg1, arg2, ...)
總結(jié)就是對參數(shù)是否用集合包裹起來的區(qū)別义起。但是apply的效率會比call高。
實際上上面的代碼塊執(zhí)行時是報錯的师崎,原因是obj并沒有定義默终,當(dāng)我們沒有目標(biāo)對象傳的時候,第一個參數(shù)可以傳null犁罩,函數(shù)體內(nèi)的this會指向默認(rèn)的宿主對象齐蔽,在瀏覽器中則為window
func.call(null, '第一個參數(shù)', '第二個參數(shù)'); // 等同于 window.func('第一個參數(shù)', '第二個參數(shù)')
call 和 apply的用途
改變this的指向
上面我們已經(jīng)說過call與apply可以用來改變函數(shù)體內(nèi)部this的指向?qū)ο螅窃趯嶋H開發(fā)中經(jīng)常會遇到this指向被不經(jīng)意改變的場景床估,那么這里再舉個?? 例子加深一下印象含滴。
比如有個div節(jié)點,節(jié)點的onclick事件中的this本來是指向這個div的:
<div id="id1">JS</div>
...
document.getElementById('id1').onclick = function() {
console.log(this.id); // id1
}
但如果變成這顷窒,在函數(shù)內(nèi)部增加一個函數(shù)func:
<div id="id1">JS</div>
...
document.getElementById('id1').onclick = function() {
console.log(this.id); // id1
var func = function () {
console.log(this.id); // undefined
}
func();
}
在onclick事件內(nèi)部調(diào)用func函數(shù)時蛙吏,func的this指向了window,而不是div鞋吉,這時候就可以用call鸦做,或者apply來修正this的指向。
<div id="id1">JS</div>
...
document.getElementById('id1').onclick = function() {
console.log(this.id); // id1
var func = function () {
console.log(this.id); // id1
}
func.apply(this);
}
另外谓着,大部分瀏覽器都實現(xiàn)了內(nèi)置的Function.prototype.bind泼诱,也是用來指定函數(shù)內(nèi)部的this指向,用法與call類似赊锚,只不過bind 是創(chuàng)建一個新的函數(shù)治筒,我們必須要手動去調(diào)用:
document.getElementById('id1').onclick = function() { console.log(this.id); // id1 var func = function () { console.log(this.id); // id1 } func.bind(this)(); }
借用其他對象的方法
常見的有“借用構(gòu)造函數(shù)”,這樣可以實現(xiàn)一些類似繼承的效果:
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('bClassName');
b.getName(); // bClassName
這里的B實際上就是借用了A構(gòu)造函數(shù)舷蒲,也就是構(gòu)造函數(shù)式繼承耸袜。
另外,下面這種運用場景會更密切一點牲平。
函數(shù)的參數(shù)arguments是一個類數(shù)組對象堤框,雖然有“下標(biāo)”,但不是真正的數(shù)組,自然不能使用obj.push(), obj.slice()等屬于Array的方法蜈抓。
那這種情況下启绰,我們可以使用Array.prototype對象上的方法來處理這樣的類數(shù)組對象。
例如:document.getElementsByClassName() 得到的就是一個HTMLCollection對象沟使,直接調(diào)用forEach的方式是不行的委可,這時候就可以找Array借一個forEach使用了。
再如
var a = {}
Array.prototype.push.call(a, 'first')
console.log(a.length); // 1
console.log(a[0]); // first
console.log(a); // {0: "first", length: 1}
這段代碼在絕大部分瀏覽器都能順利執(zhí)行腊嗡,在低版本的IE則需要顯示給a對象設(shè)置length屬性
類似上面的“借用”的方式還有很多着倾,這里就不贅述了。