本期的主題是this全面解析,本計劃一共28期,每期重點攻克一個面試重難點,如果你還不瞭解本進階計劃,文末點選檢視全部文章。
如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。
上篇文章詳細的分析了各種this的情況,看過之後對this的概念就很清晰了,沒看過的去看看。
我們知道this繫結規則一共有5種情況:
- 1、預設繫結(嚴格/非嚴格模式)
- 2、隱式繫結
- 3、顯式繫結
- 4、new繫結
- 5、箭頭函式繫結
其實大部分情況下可以用一句話來概括,this總是指向呼叫該函式的物件。
但是對於箭頭函式並不是這樣,是根據外層(函式或者全域性)作用域(詞法作用域)來決定this。
對於箭頭函式的this總結如下:
-
箭頭函式不繫結this,箭頭函式中的this相當於普通變數。
-
箭頭函式的this尋值行為與普通變數相同,在作用域中逐級尋找。
-
箭頭函式的this無法通過bind,call,apply來直接修改(可以間接修改)。
-
改變作用域中this的指向可以改變箭頭函式的this。
-
eg.
function closure(){()=>{//code }}
,在此例中,我們通過改變封包環境closure.bind(another)()
,來改變箭頭函式this的指向。
題目1
/**
* 非嚴格模式
*/
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)()
複製程式碼
空
白
佔
位
符
正確答案如下:
person1.show1() // person1,隱式繫結,this指向呼叫者 person1
person1.show1.call(person2) // person2,顯式繫結,this指向 person2
person1.show2() // window,箭頭函式繫結,this指向外層作用域,即全域性作用域
person1.show2.call(person2) // window,箭頭函式繫結,this指向外層作用域,即全域性作用域
person1.show3()() // window,預設繫結,這是一個高階函式,呼叫者是window
// 類似於`var func = person1.show3()` 執行`func()`
person1.show3().call(person2) // person2,顯式繫結,this指向 person2
person1.show3.call(person2)() // window,預設繫結,呼叫者是window
person1.show4()() // person1,箭頭函式繫結,this指向外層作用域,即person1函式作用域
person1.show4().call(person2) // person1,箭頭函式繫結,
// this指向外層作用域,即person1函式作用域
person1.show4.call(person2)() // person2
複製程式碼
最後一個person1.show4.call(person2)()
有點複雜,我們來一層一層的剝開。
- 1、首先是
var func1 = person1.show4.call(person2)
,這是顯式繫結,呼叫者是person2
,show4
函式指向的是person2
。 - 2、然後是
func1()
,箭頭函式繫結,this指向外層作用域,即person2
函式作用域
首先要說明的是,箭頭函式繫結中,this指向外層作用域,並不一定是第一層,也不一定是第二層。
因為沒有自身的this,所以只能根據作用域鏈往上層查詢,直到找到一個繫結了this的函式作用域,並指向呼叫該普通函式的物件。
題目2
這次通過建構函式來建立一個物件,並執行相同的4個show方法。
/**
* 非嚴格模式
*/
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)()
複製程式碼
空
白
佔
位
符
正確答案如下:
personA.show1() // personA,隱式繫結,呼叫者是 personA
personA.show1.call(personB) // personB,顯式繫結,呼叫者是 personB
personA.show2() // personA,首先personA是new繫結,產生了新的建構函式作用域,
// 然後是箭頭函式繫結,this指向外層作用域,即personA函式作用域
personA.show2.call(personB) // personA,同上
personA.show3()() // window,預設繫結,呼叫者是window
personA.show3().call(personB) // personB,顯式繫結,呼叫者是personB
personA.show3.call(personB)() // window,預設繫結,呼叫者是window
personA.show4()() // personA,箭頭函式繫結,this指向外層作用域,即personA函式作用域
personA.show4().call(personB) // personA,箭頭函式繫結,call並沒有改變外層作用域,
// this指向外層作用域,即personA函式作用域
personA.show4.call(personB)() // personB,解析同題目1,最後是箭頭函式繫結,
// this指向外層作用域,即改變後的person2函式作用域
複製程式碼
題目一和題目二的區別在於題目二使用了new操作符。
使用 new 操作符呼叫建構函式,實際上會經歷一下4個步驟:
- 建立一個新物件;
- 將建構函式的作用域賦給新物件(因此this就指向了這個新物件);
- 執行建構函式中的程式碼(為這個新物件新增屬性);
- 返回新物件。
上期思考題解
依次給出console.log輸出的數值。
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3;
(function() {
console.log(this.num);
this.num = 4;
})();
console.log(this.num);
},
sub: function() {
console.log(this.num)
}
}
myObject.add();
console.log(myObject.num);
console.log(num);
var sub = myObject.sub;
sub();
複製程式碼
答案有兩種情況,分為嚴格模式和非嚴格模式。
- 嚴格模式下,報錯。
TypeError: Cannot read property 'num' of undefined
- 非嚴格模式下,輸出:1、3、3、4、4
解答過程:
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3; // 隱式繫結 修改 myObject.num = 3
(function() {
console.log(this.num); // 預設繫結 輸出 1
this.num = 4; // 預設繫結 修改 window.num = 4
})();
console.log(this.num); // 隱式繫結 輸出 3
},
sub: function() {
console.log(this.num) // 因為丟失了隱式繫結的myObject,所以使用預設繫結 輸出 4
}
}
myObject.add(); // 1 3
console.log(myObject.num); // 3
console.log(num); // 4
var sub = myObject.sub;// 丟失了隱式繫結的myObject
sub(); // 4
複製程式碼
內容來自評論區:【進階3-1期】JavaScript深入之史上最全--5種this繫結全面解析
本期思考題
分別給出console.log輸出的內容。
var obj = {
say: function () {
function _say() {
console.log(this);
}
console.log(obj);
return _say.bind(obj);
}()
}
obj.say()
複製程式碼
參考
進階系列目錄
- 【進階1期】 呼叫堆疊
- 【進階2期】 作用域閉包
- 【進階3期】 this全面解析
- 【進階4期】 深淺拷貝原理
- 【進階5期】 原型Prototype
- 【進階6期】 高階函式
- 【進階7期】 事件機制
- 【進階8期】 Event Loop原理
- 【進階9期】 Promise原理
- 【進階10期】Async/Await原理
- 【進階11期】防抖/節流原理
- 【進階12期】模組化詳解
- 【進階13期】ES6重難點
- 【進階14期】計算機網路概述
- 【進階15期】瀏覽器渲染原理
- 【進階16期】webpack配置
- 【進階17期】webpack原理
- 【進階18期】前端監控
- 【進階19期】跨域和安全
- 【進階20期】效能優化
- 【進階21期】VirtualDom原理
- 【進階22期】Diff演算法
- 【進階23期】MVVM雙向繫結
- 【進階24期】Vuex原理
- 【進階25期】Redux原理
- 【進階26期】路由原理
- 【進階27期】VueRouter原始碼解析
- 【進階28期】ReactRouter原始碼解析
交流
進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,覺得不錯點個star。
我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!