全局環境的this指向全局對象,在瀏覽器中就是咱們熟知的window對象git
說到this
的種種狀況,就離不開函數的調用,通常咱們調用函數,無外乎如下四種方式:github
foo()
。obj.foo()
。new foo()
。call
、apply
、bind
等方法。除箭頭函數外的其餘函數被調用時,會在其詞法環境上綁定this
的值,咱們能夠經過一些方法來指定this
的值。瀏覽器
call
、apply
、bind
等方法來顯式指定this
的值。function foo() {
console.log(this.a)
}
foo.call({a: 1}) // 輸出: 1
foo.apply({a: 2}) // 輸出: 2
// bind方法返回一個函數,須要手動進行調用
foo.bind({a: 3})() // 輸出: 3
複製代碼
this
的值將被隱式指定爲這個對象。let obj = {
a: 4,
foo: function() {
console.log(this.a)
}
}
obj.foo() // 輸出: 4
複製代碼
new
操做符做爲構造函數調用時,this
的值將被隱式指定新構造出來的對象。上面講了幾種比較容易記憶和理解this
的狀況,咱們來根據ECMAScript規範來簡單分析一下,這裏只說重點,一些規範內具體的實現就不講了,反而容易混淆。app
其實當咱們調用函數時,內部是調用函數的一個內置[[Call]](thisArgument, argumentsList)
方法,此方法接收兩個參數,第一個參數提供this
的綁定值,第二個參數就是函數的參數列表。函數
ECMAScript規範: 嚴格模式時,函數內的
this
綁定嚴格指向傳入的thisArgument
。非嚴格模式時,若傳入的thisArgument
不爲undefined
或null
時,函數內的this
綁定指向傳入的thisArgument
;爲undefined
或null
時,函數內的this
綁定指向全局的this
。post
因此第一點中講的三種狀況都是顯式或隱式的傳入了thisArgument
來做爲this
的綁定值。咱們來用僞代碼模擬一下:ui
function foo() {
console.log(this.a)
}
/* -------顯式指定this------- */
foo.call({a: 1})
foo.apply({a: 1})
foo.bind({a: 1})()
// 內部均執行
foo[[Call]]({a: 1})
/* -------函數構造調用------- */
new foo()
// 內部執行
let obj = {}
obj.__proto__ = foo.prototype
foo[[Call]](obj)
// 最後將這個obj返回,關於構造函數的詳細內容可翻閱我以前關於原型和原型鏈的文章
/* -------做爲對象方法調用------- */
let obj = {
a: 4,
foo: function() {
console.log(this.a)
}
}
obj.foo()
// 內部執行
foo[[Call]]({
a: 1,
foo: Function foo
})
複製代碼
那麼當函數普通調用時,thisArgument
的值並無傳入,即爲undefined
,根據上面的ECMAScript規範,若非嚴格模式,函數內this
指向全局this
,在瀏覽器內就是window。this
僞代碼模擬:spa
window.a = 10
function foo() {
console.log(this.a)
}
foo() // 輸出: 10
foo.call(undefined) // 輸出: 10
// 內部均執行
foo[[Call]](undefined) // 非嚴格模式,this指向全局對象
foo.call(null) // 輸出: 10
// 內部執行
foo[[Call]](null) // 非嚴格模式,this指向全局對象
複製代碼
根據上面的ECMAScript規範,嚴格模式下,函數內的this
綁定嚴格指向傳入的thisArgument
。因此有如下表現。prototype
function foo() {
'use strict'
console.log(this)
}
foo() // 輸出:undefined
foo.call(null) // 輸出:null
複製代碼
須要注意的是,這裏所說的嚴格模式是函數被建立時是否爲嚴格模式,並不是函數被調用時是否爲嚴格模式:
window.a = 10
function foo() {
console.log(this.a)
}
function bar() {
'use strict'
foo()
}
bar() // 輸出:10
複製代碼
ES6新增的箭頭函數在被調用時不會綁定this
,因此它須要去詞法環境鏈上尋找this
。
function foo() {
return () => {
console.log(this)
}
}
const arrowFn1 = foo()
arrowFn1() // 輸出:window
// 箭頭函數沒有this綁定,往外層詞法環境尋找
// 在foo的詞法環境上找到this綁定,指向全局對象window
// 在foo的詞法環境上找到,並不是是在全局找到的
const arrowFn2 = foo.call({a: 1})
arrowFn2() // 輸出 {a: 1}
複製代碼
切記,箭頭函數中不會綁定this
,因爲JS採用詞法做用域,因此箭頭函數中的this
只取決於其定義時的環境。
window.a = 10
const foo = () => {
console.log(this.a)
}
foo.call({a: 20}) // 輸出: 10
let obj = {
a: 20,
foo: foo
}
obj.foo() // 輸出: 10
function bar() {
foo()
}
bar.call({a: 20}) // 輸出: 10
複製代碼
當函數做爲回調函數時會產生一些怪異的現象:
window.a = 10
let obj = {
a: 20,
foo: function() {
console.log(this.a)
}
}
setTimeout(obj.foo, 0) // 輸出: 10
複製代碼
我以爲這麼解釋比較好理解:obj.foo
做爲回調函數,咱們其實在傳遞函數的具體值,而並不是函數名,也就是說回調函數會記錄傳入的函數的函數體,達到觸發條件後進行執行,僞代碼以下:
setTimeout(obj.foo, 0)
//等同於,先將傳入回調函數記錄下來
let callback = obj.foo
// 達到觸發條件後執行回調
callback()
// 因此foo函數並不是做爲對象方法調用,而是做爲函數普通調用
複製代碼
要想避免這種狀況,有三種方法,第一種方法是使用bind
返回的指定好this
綁定的函數做爲回調函數傳入:
setTimeout(obj.foo.bind({a: 20}), 0) // 輸出: 20
複製代碼
第二種方法是儲存咱們想要的this值,就是常見的,具體命名視我的習慣而定。
let _this = this
let self = this
let me = this
複製代碼
第三種方法就是使用箭頭函數
window.a = 10
function foo() {
return () => {
console.log(this.a)
}
}
const arrowFn = foo.call({a: 20})
arrowFn() // 輸出:20
setTimeout(arrowFn, 0) // 輸出:20
複製代碼
this
綁定,this
的值取決於其建立時所在詞法環境鏈中最近的this
綁定this
指向全局對象this
爲undefined
this
指向該對象new
調用,this
指向構造出的新對象call
、apply
、bind
等間接調用,this
指向傳入的第一個參數
這裏注意兩點:
bind
返回一個函數,須要手動調用,call
、apply
會自動調用- 傳入的第一個參數若爲
undefined
或null
,this
指向全局對象
call
、apply
、bind
等間接調用,this
嚴格指向傳入的第一個參數有時候文字的表述是蒼白無力的,真正理解以後會發現:this
不過如此。
例子來自南波的JavaScript之例題中完全理解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 // 函數做爲對象方法調用,this指向對象
person2 // 使用call間接調用函數,this指向傳入的person2
window // 箭頭函數無this綁定,在全局環境找到this,指向window
window // 間接調用改變this指向對箭頭函數無效
window // person1.show3()返回普通函數,至關於普通函數調用,this指向window
person2 // 使用call間接調用函數,this指向傳入的person2
window // person1.show3.call(person2)仍然返回普通函數
person1 // person1.show4調用對象方法,this指向person1,返回箭頭函數,this在person1.show4調用時的詞法環境中找到,指向person1
person1 // 間接調用改變this指向對箭頭函數無效
person2 // 改變了person1.show4調用時this的指向,因此返回的箭頭函數的內this解析改變
歡迎前往閱讀系列文章,若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。
菜鳥一枚,若是有疑問或者發現錯誤,能夠在相應的 issues 進行提問或勘誤,與你們共同進步。