本文共 2025 字,看完只需 8 分鐘
概述
前面的文章講解了 JavaScript 中的執(zhí)行上下文,作用域,變量對(duì)象,this 的相關(guān)原理,但是我后來在網(wǎng)上看到一些例題的時(shí)候,依然沒能全做對(duì),說明自己有些細(xì)節(jié)還沒能掌握,本文就結(jié)合例題進(jìn)行深入實(shí)踐,討論函數(shù)在不同的調(diào)用方式 this 的指向問題。
老規(guī)矩,先給結(jié)論 1 和 結(jié)論2:
this 始終指向最后調(diào)用它的對(duì)象
“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。
特別提示:
本文的例子,最好自己在瀏覽器控制臺(tái)中去試一遍,看完過兩天就會(huì)忘的,一定要實(shí)踐。
1、隱式綁定
var name = "window";
function foo() {
var name = "inner";
console.log(this.name);
}
foo(); // ?
復(fù)制代碼輸出:
window
例 1 中,非嚴(yán)格模式,由于 foo 函數(shù)是在全局環(huán)境中被調(diào)用,this 會(huì)被默認(rèn)指向全局對(duì)象 window;
所以符合了我們的結(jié)論一:
this 始終指向最后調(diào)用它的對(duì)象
2、一般函數(shù)和箭頭函數(shù)的對(duì)象調(diào)用
// 例 2
var name = "window";
var person = {
name: "inner",
show1: function () {
console.log(this.name);
},
show2: () => {
console.log(this.name);
}
}
person.show1(); // ?
person.show2(); // ?
復(fù)制代碼輸出:
inner
window
person.show1() 輸出 inner 沒毛病,person.show2() 箭頭函數(shù)為什么會(huì)輸出 window 呢。MDN 中對(duì) this 的定義是:
箭頭函數(shù)不綁定 this, 箭頭函數(shù)不會(huì)創(chuàng)建自己的this,它只會(huì)從自己的作用域鏈的上一層繼承this。
再看本文前面給的結(jié)論:
“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。
由于 JS 中只有全局作用域和函數(shù)作用域,箭頭函數(shù)在定義時(shí)的上一層作用域是全局環(huán)境,全局環(huán)境中的 this 指向全局對(duì)象本身,即 window。
3、call
// 例 3
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() // ?
person1.show1.call(person2) // ?
person1.show2() // ?
person1.show2.call(person2) // ?
person1.show3()() // ?
person1.show3().call(person2) // ?
person1.show3.call(person2)() // ?
person1.show4()() // ?
person1.show4().call(person2) // ?
person1.show4.call(person2)() // ?
復(fù)制代碼輸出:
person1
person2
window
window
window
person2
window
person1
person1
person2
上面 10 行打印,你對(duì)了幾個(gè)呢?
首先:
person1.show1() 和 person1.show1.call(person2) 輸出結(jié)果應(yīng)該沒問題,call 的作用就是改變了調(diào)用的對(duì)象 為 person2。
其次:
person1.show2(),person1.show2.call(person2),由于調(diào)用的是箭頭函數(shù),和本文例 2 中是一樣的,箭頭函數(shù)定義時(shí) this 指向的是上一層,也就是全局對(duì)象, 并且 箭頭函數(shù)不綁定自己的 this, 所以通過 call() 或 apply() 方法調(diào)用箭頭函數(shù)時(shí),只能傳遞參數(shù),不能傳遞新的對(duì)象進(jìn)行綁定。故打印的值都是 window。
進(jìn)而:
function foo () {
return function () {
console.log(this.name)
}
}
foo()();
復(fù)制代碼博客前面的文章有講過閉包,上面這段代碼也是典型的閉包運(yùn)用,可以看作:
function foo () {
return function () {
console.log(this.name)
}
}
var bar = foo();
bar();
復(fù)制代碼所以,很明顯,被返回的內(nèi)部函數(shù)其實(shí)是在全局環(huán)境下被調(diào)用的?;氐角懊婵次覀兊慕Y(jié)論 1,this 始終指向最后調(diào)用函數(shù)的對(duì)象,這句話的關(guān)鍵詞應(yīng)該是什么?我覺得應(yīng)該是 調(diào)用,什么時(shí)候調(diào)用,誰調(diào)用。
再回過頭來看:
person1.show3()() 輸出 window,因?yàn)閮?nèi)部函數(shù)在全局環(huán)境中被調(diào)用。
person1.show3().call(person2) 輸出 person2, 因?yàn)閮?nèi)部函數(shù)被 person2 對(duì)象調(diào)用了。
person1.show3.call(person2)() 輸出 window,也是因?yàn)閮?nèi)部函數(shù)在全局環(huán)境中被調(diào)用。
最后:
重點(diǎn)理解結(jié)論 2:
“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。
show4: function () {
return () => console.log(this.name)
}
復(fù)制代碼這段代碼中,箭頭函數(shù)是在 外層函數(shù) show4 執(zhí)行后才被定義的。為什么?可以翻看我前面關(guān)于作用域鏈,執(zhí)行上下文,變量對(duì)象的文章,函數(shù)在進(jìn)入執(zhí)行階段時(shí),會(huì)先查找內(nèi)部的變量和函數(shù)聲明,將他們作為變量對(duì)象的屬性,關(guān)聯(lián)作用域鏈,并綁定 this 指向。
所以:
person1.show4()() 輸出 person1,因?yàn)橥獠亢瘮?shù)在執(zhí)行時(shí)的 this 為 person1, 此時(shí)定義了內(nèi)部函數(shù),而內(nèi)部函數(shù)為外部函數(shù)的 this。
person1.show4().call(person2) 輸出 person1,箭頭函數(shù)不會(huì)綁定 this, 所以 call 傳入 this 指向無效。
person1.show4.call(person2)() 輸出 person2,因?yàn)橥獠亢瘮?shù)在執(zhí)行時(shí)的 this 為 person2,此時(shí)定義了內(nèi)部函數(shù),而內(nèi)部函數(shù)為外部函數(shù)的 this。
4、構(gòu)造函數(shù)中的 this
// 例 4
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1() //
personA.show1.call(personB) //
personA.show2() //
personA.show2.call(personB) //
personA.show3()() //
personA.show3().call(personB) //
personA.show3.call(personB)() //
personA.show4()() //
personA.show4().call(personB) //
personA.show4.call(personB)() //
復(fù)制代碼輸出:
personA
personB
personA
personA
window
personB
window
personA
personA
personB
例 4 和 例 3 大致一樣,唯一的區(qū)別在于兩點(diǎn):
- 構(gòu)造函數(shù)中 this 指向被創(chuàng)建的實(shí)例
- 構(gòu)造函數(shù),也是函數(shù),所以存在作用域,所以里面的箭頭函數(shù),它們的 this 指向,來自于上一層,就不再是全局環(huán)境 window, 而是構(gòu)造函數(shù) 的 this。
5、setTimeout 函數(shù)
// 例 5
function foo(){
setTimeout(() =>{
console.log("id:", this.id)
setTimeout(() =>{
console.log("id:", this.id)
}, 100);
}, 100);
}
foo.call({id: 111}); //
復(fù)制代碼輸出:
111
111
注意一點(diǎn):
setTimeout 函數(shù)是在全局環(huán)境被 window 對(duì)象執(zhí)行的,但是 foo 函數(shù)在執(zhí)行時(shí),setTimtout 委托的匿名箭頭函數(shù)被定義,箭頭函數(shù)的 this 來自于上層函數(shù) foo 的調(diào)用對(duì)象, 所以打印結(jié)果才為 111;
6、setTimeout 函數(shù) 2
// 例 6
function foo1(){
setTimeout(() =>{
console.log("id:", this.id)
setTimeout(function (){
console.log("id:", this.id)
}, 100);
}, 100);
}
function foo2(){
setTimeout(function() {
console.log("id:", this.id)
setTimeout(() => {
console.log("id:", this.id)
}, 100);
}, 100);
}
foo1.call({ id: 111 }); // ?
foo2.call({ id: 222 }); // ?
復(fù)制代碼輸出:
111
undefined
undefined
undefined
例 5 中已經(jīng)提到,setTimeout函數(shù)被 window 對(duì)象調(diào)用,如果
是普通函數(shù),內(nèi)部的 this 自然指向了全局對(duì)象下的 id, 所以為 undefined,如果是箭頭函數(shù),this 指向的就是外部函數(shù)的 this。
7、嵌套箭頭函數(shù)
// 例 7
function foo() {
return () => {
return () => {
return () => {
console.log("id:", this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); //
var t2 = f().call({id: 3})(); //
var t3 = f()().call({id: 4}); //
復(fù)制代碼輸出:
1
1
1
這段代碼是為了鞏固我們的結(jié)論2:
“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。
- foo.call({}) 在執(zhí)行時(shí),內(nèi)部的第一層箭頭函數(shù)才被定義
- 箭頭函數(shù)無法綁定 this, 所以 call 函數(shù)指定 this 無效
- 箭頭函數(shù)的 this 來自于上一層作用域(非箭頭函數(shù)作用域)的 this
總結(jié)
有本書中有提到,當(dāng)理解 JavaScript 中的 this 之后,JavaScript 才算入門,我深以為然。
原因是,要徹底理解 this, 應(yīng)該是建立在已經(jīng)大致理解了 JS 中的執(zhí)行上下文,作用域、作用域鏈,閉包,變量對(duì)象,函數(shù)執(zhí)行過程的基礎(chǔ)上。
求點(diǎn)贊,求關(guān)注~
