參考鏈接
基本含義
var showName = function() {
console.log('My name is', this.name);
};
var zs = {
name: 'Zhang San',
describe: showName
},
ls = {
name: 'Li Si',
describe: showName
};
zs.describe(); // My name is Zhang San
ls.describe(); // My name is Li Si
showName(); // My name is
name = 'window'; // 等價(jià)于 window.name = 'window'; 和this.name = 'window'; 因?yàn)榇藭r(shí)window===this
showName(); // My name is window
以上示例中實(shí)際都是執(zhí)行的showName方法迷守,但是由于環(huán)境不同杭隙,輸出的結(jié)果也不同,根本原因是不同情況下的this是不一樣的。
zs.describe(); // this === zs
ls.describe(); // this === ls
showName(); // this === window
綁定機(jī)制導(dǎo)致的this不同指代
<input type="button" name="按鈕1" onclick="showName()" value="按鈕1">
<input id="btn1" type="button" name="按鈕2" value="按鈕2">
<input id="btn2" type="button" name="按鈕3" value="按鈕3">
<script>
window.name = 'window';
var showName = function() {
console.log('My name is', this.name);
};
var btn1 = document.getElementById('btn1');
btn1.onclick = showName;
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showName, false);
</script>
點(diǎn)擊三個(gè)按鈕埋哟,控制臺(tái)輸出結(jié)果分別是什么呢?
第一個(gè)為:My name is window 第二個(gè)為:My name is 按鈕2 ,第三個(gè)為My name is 按鈕3
這是為什么呢赤赊?這個(gè)和綁定事件的機(jī)制有關(guān)系闯狱。第一種形式是HTML事件,onclick="showName()"表示在點(diǎn)擊時(shí)執(zhí)行showName方法抛计,此時(shí)執(zhí)行環(huán)境為全局環(huán)境哄孤,this為window,所以輸出window吹截。
第二種和第三種分別為DOM0級(jí)事件和DOM2級(jí)事件瘦陈,其實(shí)質(zhì)是給點(diǎn)擊事件指定了一個(gè)回調(diào)函數(shù),其為showName波俄。在點(diǎn)擊事件的回調(diào)函數(shù)中晨逝,this是指當(dāng)前這個(gè)dom元素,因此輸出的值為這兩個(gè)按鈕的name屬性懦铺。
使用場(chǎng)合
- this的使用是很廣泛的咏花,其作用也非常強(qiáng)大。我們可以將this的使用歸為一下幾類阀趴。
1.構(gòu)造函數(shù)
function Person(name, gender) { this.name = name;
this.gender = gender; }
Person.prototype.showSelf = function() {
return this; }
zs = new Person('zs', 'male'); // {name: "zs", gender: "male"}
zs.showSelf(); // {name: "zs", gender: "male"}
zs === zs.showSelf(); // true
2.對(duì)象的方法
var box = {
name: 'box',
getName: function() {
return this.name;
}
};
var bag = {
name: 'bag'
};
bag.getName = box.getName;
bag.getName(); // "bag"
雖然bag.getName實(shí)際是對(duì)box.getName的一個(gè)引用昏翰, 由于運(yùn)行時(shí)使用的是bag.getName(), 此時(shí)是在bag對(duì)象下運(yùn)行的刘急, this也就指的是bag了棚菊。
再看一點(diǎn)奇怪的:
// 注意 box.getName 沒有括號(hào)
(false || box.getName)(); // window
(false ? alert : box.getName)(); // window
上面這兩種情況下, 輸出的都不再是box對(duì)象的name屬性叔汁, 而是window( 之前設(shè)置了window.name = 'window')统求。
表示此時(shí)方法內(nèi)部的this指向的是瀏覽器頂層對(duì)象window。
可以這么理解:
box對(duì)象指向了一個(gè)地址M1据块, box.getName作為box的一個(gè)方法码邻, 但本身也是對(duì)象, 它自己也有一個(gè)地址M2另假, 只有通過box.getName() 調(diào)用時(shí)像屋, 是從M1中調(diào)用M2, 所以this指向的是box边篮。 上面兩種情況都是直接拿到M2來調(diào)用己莺, 此時(shí)和M1已經(jīng)沒有任何關(guān)系了, this的指向當(dāng)前代碼塊所在的對(duì)象戈轿。
ES6箭頭函數(shù)
ES6中新增的箭頭函數(shù)里面所使用的this和之前介紹的情況都不一樣了凌受, 在箭頭函數(shù)中this不隨其運(yùn)行環(huán)境的改變而改變, 而是在聲明箭頭函數(shù)時(shí)思杯, 就已經(jīng)固定下來了胜蛉。 箭頭函數(shù)中this的指向就是聲明箭頭函數(shù)是所在的對(duì)象。
先看一個(gè)常規(guī)的例子:
function foo() {
setTimeout(function() {
console.log('name:', this.name);
}, 100);
}
foo(); // name: window
foo.call({ name: 'an obj' }); // name: window
定義一個(gè)函數(shù)foo內(nèi)部使用定時(shí)器調(diào)用一個(gè)匿名函數(shù), 此時(shí)函數(shù)有多層了誊册, this的指向應(yīng)該是全局對(duì)象window奈梳, 輸出結(jié)果證明了這一點(diǎn)。 使用foo.call結(jié)果也相同的原因是解虱, call替換的是foo函數(shù)內(nèi)的this指向攘须, 而輸出的this是在定時(shí)器的回調(diào)中的, 故結(jié)果依然是window殴泰。
我們?cè)倏匆幌录^函數(shù)中這一點(diǎn)的表現(xiàn):
function arrow_foo() {
setTimeout(() => {
console.log('name:', this.name);
}, 100);
}
arrow_foo(); // name: window
arrow_foo.call({ name: 'an obj' }); // name: an obj
我們發(fā)現(xiàn)結(jié)果于宙, 居然和上面不一樣了。 為什么呢悍汛? 我們將其轉(zhuǎn)化成ES5的結(jié)果來看一下捞魁, 上面代碼轉(zhuǎn)化后的結(jié)果是這樣的:
function arrow_foo() {
var $__1 = this;
setTimeout(function() {
console.log('name:', $__1.name);
}, 100);
}
arrow_foo();
arrow_foo.call({ name: 'an obj' });
看一下轉(zhuǎn)換后的結(jié)果, 原因就一目了然了离咐, 箭頭函數(shù)中this直接固定成了其定義時(shí)所在的對(duì)象谱俭, 此處為foo。 實(shí)際在箭頭函數(shù)中的所有this都是一個(gè)對(duì)象宵蛀, 這個(gè)對(duì)象就是其定義時(shí)所在對(duì)象的this昆著, 上面轉(zhuǎn)換后的結(jié)果中在foo中首先使用一個(gè)變量記錄下this, 而在箭頭函數(shù)中的this被替換成了之前存儲(chǔ)this的那個(gè)變量术陶。
因此直接運(yùn)行時(shí)凑懂, this是指全局對(duì)象, 而使用call時(shí)梧宫, 將foo內(nèi)的this替換成了指定的對(duì)象 { name: 'an obj' }接谨,從而輸出的上面的結(jié)果。
使用注意點(diǎn)
1.避免多層this
由于this的指向是不確定的塘匣, 所以切勿在函數(shù)中包含多層的this脓豪。
var box = {
name: 'box',
size: {
width: 300,
height: 300
},
show: function() {
console.log('name', this.name);
(function() {
console.log('size', this.size);
})();
}
};
box.show();
// name box
// size undefined
我們本意是想在show方法內(nèi)部輸出name, 并輸出size忌卤, 但是結(jié)果卻并不是想要的這樣扫夜, 這是因?yàn)樵诹⒓磮?zhí)行的函數(shù)內(nèi)部, this的執(zhí)行不再是box對(duì)象而變成了頂層對(duì)象window埠巨, 因此第二行輸出為undefined历谍。
解決方法為, 在外層用一個(gè)變量記錄下this辣垒, 在要使用的地方使用那個(gè)變量。
改寫上列子
var box = {
name: 'box',
size: {
width: 300,
height: 300
},
show: function() {
console.log('name', this.name);
var that = this;
(function() {
console.log('size', that.size);
})();
}
};
box.show();
// name box
// size Object {width: 300, height: 300}
還用一種做法是JavaScript提供的嚴(yán)格模式use strict印蔬, 如果函數(shù)內(nèi)部的this直接指向了頂層對(duì)象會(huì)直接報(bào)錯(cuò)勋桶。
var box = {
name: 'box',
size: {
width: 300,
height: 300
},
show: function() {
'use strict'
console.log('name', this.name);
(function() {
console.log('size', this.size);
})();
}
};
box.show();
// Uncaught TypeError: Cannot read property 'size' of undefined(…)
2.避免在回調(diào)函數(shù)中使用this
通常回調(diào)函數(shù)中的this都有其特定的, 如果在回調(diào)函數(shù)中使用this例驹, 應(yīng)該需要了解其含義捐韩, 否則可能出現(xiàn)意料之外的結(jié)果。
事件處理函數(shù)作為一種特殊的回調(diào)函數(shù)鹃锈, 其this是指當(dāng)前的DOM對(duì)象荤胁, 最開始的例子已經(jīng)說明了這個(gè)問題。
回調(diào)函數(shù)本身是一個(gè)函數(shù)屎债, 其作為另一個(gè)函數(shù)的參數(shù)傳遞進(jìn)去仅政, 然后在那個(gè)函數(shù)內(nèi)部執(zhí)行, 這本身已經(jīng)構(gòu)成了多層this盆驹, 此時(shí)this的指向是不確定的圆丹, 需要慎用。
3.綁定this的方法
- function.prototype.call()
使用函數(shù)的call方法躯喇,可以指定函數(shù)內(nèi)部this的指向辫封,使其在指定的作用域中運(yùn)行。
var obj = {};
var f = function() {
return this;
};
f() === this; // true this === window
f.call(obj) === obj; // true
上面代碼中廉丽, 在全局環(huán)境運(yùn)行函數(shù)f時(shí)倦微, this指向全局環(huán)境; call方法可以改變this的指向正压, 指定this指向?qū)ο髈bj璃诀, 然后在對(duì)象obj的作用域中運(yùn)行函數(shù)f。
call方法的第一個(gè)參數(shù)為一個(gè)對(duì)象蔑匣, 其表示要為函數(shù)所指定的運(yùn)行上下文環(huán)境的對(duì)象( 當(dāng)指定為undefined或null是默認(rèn)傳入window)劣欢, 之后的參數(shù)依次作為原函數(shù)的參數(shù)。
- function.prototype.apply()
使用函數(shù)的apply方法同樣可以指定函數(shù)運(yùn)行的環(huán)境裁良, 作用和call相同凿将, 使用方法也類似, 都是第一個(gè)參數(shù)傳入要指定的上下文對(duì)象价脾。 不同點(diǎn)在于牧抵, apply方法最多接收兩個(gè)參數(shù), 第二個(gè)參數(shù)為一個(gè)數(shù)組( 無論原函數(shù)需要的參數(shù)是何種類型侨把, 此數(shù)組中的每個(gè)元素將依次傳遞給原函數(shù))犀变, 表示傳遞給原函數(shù)的參數(shù), 而call可以接收多個(gè)參數(shù)秋柄, 從第二個(gè)參數(shù)開始获枝, 之后的所有參數(shù)都傳遞給原函數(shù)。
由于apply第二個(gè)參數(shù)接收的是數(shù)組骇笔, 其有很多巧用省店。 由于此文重點(diǎn)是描述this關(guān)鍵字嚣崭, 就不再贅述了。
- function.prototype.bind()
ES5中有bind這樣一個(gè)方法懦傍, 也可以指定函數(shù)的運(yùn)行環(huán)境雹舀, 但是和call、 apply有所不同粗俱, bind方法可接收一個(gè)參數(shù)说榆, 用于指定函數(shù)運(yùn)行的上下文環(huán)境, 返回一個(gè)函數(shù)作為綁定指定上下文環(huán)境后的新函數(shù)寸认。
這樣bind和call签财、 apply的區(qū)別就出來了: 前者是根據(jù)指定的上下文環(huán)境返回一個(gè)新函數(shù), 而后兩者是使用指定的上下文壞境運(yùn)行原函數(shù)废麻。
其實(shí)bind和jQuery.proxy() 類似荠卷, 雖然沒有后者處理多種情況, 但作為JavaScript原生方法烛愧, 更輕量油宜、 高效。
用本文最開始的例子來演示此方法的使用怜姿, 某對(duì)象下有某方法慎冤, 我們要將此對(duì)象這個(gè)方法作為作為一個(gè)事件處理函數(shù), 但不希望方法內(nèi)部的this被改變:
< input id = "btn1" type = "button" name = "按鈕1" value = "張三的名字是" >
< input id = "btn2" type = "button" name = "按鈕2" value = "張三的名字是" >
< script >
window.name = 'window';
var showName = function() {
console.log(this.name);
};
var zs = { name: '張三' };
var btn1 = document.getElementById('btn1');
btn1.addEventListener('click', showName, false);
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showName.bind(zs), false);
< /script>
這樣點(diǎn)擊第二個(gè)按鈕沧卢,將可以正確輸出張三的名字蚁堤。
bind
第一個(gè)參數(shù)為一個(gè)對(duì)象,為undefined或null是默認(rèn)傳入window
除此之外但狭,bind還可以接收額外參數(shù)披诗,用于在生成新函數(shù)時(shí),從原函數(shù)的第一個(gè)參數(shù)開始替換一部分參數(shù)(和jQuery.proxy()
類似)立磁。比如原函數(shù)要接收兩個(gè)參數(shù)呈队,使用bind產(chǎn)生新函數(shù)時(shí),除了第一個(gè)參數(shù)的外唱歧,可以再傳入一個(gè)參數(shù)宪摧,此參數(shù)將替換原函數(shù)的第一個(gè)參數(shù),這樣生成的新函數(shù)就只用接收一個(gè)參數(shù)了颅崩,詳情見jQuery 工具方法簡(jiǎn)析中jQuery.proxy( function, context [, additionalArguments ] )几于。