一、this 指向梳理圖

二、逐個(gè)學(xué)習(xí)
先說(shuō)明上下文(context)和作用域(scope)的區(qū)別:
- 作用域(scope): 指變量、函數(shù)、對(duì)象的可訪問(wèn)性
- 上下文(context):this 在同一作用域中的值
為什么設(shè)計(jì)出 this ?
函數(shù)可以在不同的運(yùn)行環(huán)境執(zhí)行,所以需要有一種機(jī)制,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運(yùn)行環(huán)境(context)。this 的設(shè)計(jì)目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運(yùn)行環(huán)境。
2.1 全局上下文
說(shuō)明:全局上下文、全局環(huán)境其實(shí)都是全局上下文環(huán)境 的簡(jiǎn)稱,好多文章兩種簡(jiǎn)稱都在用。
函數(shù)在瀏覽器全局上下文環(huán)境中被調(diào)用時(shí):
- 非嚴(yán)格模式:this 指向全局對(duì)象
window - 嚴(yán)格模式:this 指向
undefined

2.2 構(gòu)造函數(shù)
2.2.1 通過(guò) new 調(diào)用構(gòu)造函數(shù)
需要提前了解 new 關(guān)鍵字的底層原理。可稍后看文章底部。
構(gòu)造函數(shù)中的 this 根據(jù)構(gòu)造函數(shù)返回值的不同,指向也不同
- 構(gòu)造函數(shù)顯式 return 一個(gè)對(duì)象類型(包括:數(shù)組、函數(shù)等):this 指向返回的對(duì)象

- 構(gòu)造函數(shù)顯式 return 一個(gè)非對(duì)象類型(基礎(chǔ)類型):this 指向?qū)嵗龑?duì)象

-
未顯示指定 return, 構(gòu)造函數(shù)會(huì)默認(rèn)返回實(shí)例對(duì)象,而且 this 指向這個(gè)實(shí)例對(duì)象
demo 2-2.1-2
2.2.2 直接調(diào)用構(gòu)造函數(shù),當(dāng)做普通函數(shù)
this 指向全局對(duì)象 window。

2.3 對(duì)象方法中的 this
當(dāng)以對(duì)象方法調(diào)用函數(shù)時(shí),this 指向調(diào)用當(dāng)前函數(shù)的對(duì)象。
function say() {
console.log(this.name);
}
const person = {
name: 'lee',
say
}
person.say();

先在外部定義函數(shù),并未影響 this 的綁定,關(guān)鍵還是看誰(shuí)調(diào)用了這個(gè)函數(shù)。

2.4 getter 與 setter 中的 this
this 指向調(diào)用 getter、setter 的對(duì)象。
從下面例子中的 temp 對(duì)象能夠看出,通過(guò) temp.prop = person.prop; ,將 getter 與 setter 賦予 temp 對(duì)象,之后調(diào)用 temp.prop 時(shí),this 指向的是 temp 對(duì)象,所以不會(huì)對(duì) person 產(chǎn)生影響。
class Person {
constructor() {
this.name = 'lee';
}
get prop() {
return this.name;
}
set prop(value) {
this.name = value;
}
}
const person = new Person();
console.log("===============person 實(shí)例對(duì)象===============");
console.log('修改前 person.name:',person.prop);
person.prop = 'nacy';
console.log('修改后 person.name:',person.prop);
console.log("=============== temp 對(duì)象===============");
const temp = {
name: 'temp'
}
temp.prop = person.prop;
console.log('修改前 temp.name:',temp.prop);
temp.prop = 'andy';
console.log('修改后 temp.name:',temp.prop);
console.log('對(duì)person沒(méi)影響:',person.prop);
運(yùn)行結(jié)果:

2.5 原型鏈中的 this
this 指向調(diào)函數(shù)的對(duì)象。
從下面的例子可以看出,child 實(shí)例對(duì)象的 say 方法是通過(guò)原型鏈在 Parent 的原型上找到的,但是因?yàn)檎{(diào)用 say 的是 child 實(shí)例對(duì)象,所以此時(shí) this 指向 child,輸出就是 child。
function Parent() {
this.name = 'father';
}
Parent.prototype.say = function () {
console.log(this.name);
}
function Child() {
this.name = 'child';
}
Child.prototype = new Parent();
const child = new Child();
child.say();
運(yùn)行結(jié)果:

2.6 箭頭函數(shù)中的 this
箭頭函數(shù)中沒(méi)有 this 綁定,this 的值由外層最近的非箭頭函數(shù)的作用域決定。
2.7 回調(diào)函數(shù)中的 this
setTimeout()、forEach()、dom 中的事件等,表現(xiàn)是類似的,這里采用《深入淺出 es6 》 中的例子:
var PageHandler = {
id: "123456",
init: function() {
document.addEventListener("click", function(event) {
this.doSomething(event.type); // 錯(cuò)誤
}, false);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};
此時(shí) this 指向的是 document ,容易被忽略掉。改用箭頭函數(shù)之后,this 的值通過(guò)查找作用域鏈來(lái)確定,外層最近的函數(shù)就是 init, 此時(shí) this 就指向了 PageHandler, 將能夠正確調(diào)用 doSomething 。
2.8 bind、call、apply 改變 this 的指向
bind 和 call 、apply 的區(qū)別:
bind 返回的是一個(gè)新的函數(shù),并未立即執(zhí)行原函數(shù)。
call、apply 會(huì)立即執(zhí)行原函數(shù)。
call 和 apply 的區(qū)別:
call 的參數(shù)需要一個(gè)個(gè)寫上
apply 的參數(shù)是一個(gè)數(shù)組。
2.9 this 優(yōu)先級(jí)
顯式綁定:call、apply、bind、new。
隱式綁定:根據(jù)調(diào)用關(guān)系確定 this 指向。
new 綁定的優(yōu)先級(jí)比 bind 綁定高。
調(diào)用 bind 返回的新綁定函數(shù),如果被當(dāng)做構(gòu)造函數(shù),使用new關(guān)鍵字調(diào)用,bind 對(duì) this 的綁定將被忽略。箭頭函數(shù)的優(yōu)先級(jí)比 call、apply 高
var a = 10;
const say = () => {
// this 原始指向 window
console.log(this.a);
}
const obj = {
a: 3
}
// 試圖用 call 改變 this 的指向
say.call(obj);
// 試圖用 bind 改變 this 的指向
const bindSay = say.bind(obj);
bindSay();
使用 let 和 const 聲明的變量不會(huì)掛載到 window 全局對(duì)象上,所以為了能正常打印出 10,這里使用了 var a = 10;
一句話總結(jié)
候策的陳述是:this 的指向,是在調(diào)用函數(shù)時(shí)根據(jù)執(zhí)行上下文所動(dòng)態(tài)確定的。
- 直觀的看,這句話對(duì)于
對(duì)象方法和箭頭函數(shù)是容易理解的。 - call、apply、bind 這里要繞個(gè)彎子想,call、apply、bind 的內(nèi)部實(shí)現(xiàn)原理還是通過(guò)指定的對(duì)象來(lái)調(diào)用原函數(shù),所以也符合。
- 通過(guò) new 關(guān)鍵字調(diào)用構(gòu)造函數(shù),這里是一樣的思考方式,new 關(guān)鍵字的底層原理:
- 創(chuàng)建一個(gè)空對(duì)象,作為將要返回的對(duì)象實(shí)例
- 將這個(gè)空對(duì)象的原型,指向構(gòu)造函數(shù)的 prototype 屬性
- 將這個(gè)空對(duì)象賦值給 this
- 開(kāi)始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼,返回這個(gè)新的對(duì)象。
所以這里 this 指向的是實(shí)例對(duì)象。
參考
阮一峰 this 關(guān)鍵字
JavaScript中call,apply,bind以及this的理解
候策: 一網(wǎng)打盡 this,對(duì)執(zhí)行上下文說(shuō) Yes
