Know Everything About This
這是一篇目前篇幅不長,但是寫起來相當漫長的文章。中途,我翻譯了這篇文章,非常有必要一讀。
一、是什麼?
「在函式內部,有兩個特殊的物件:arguments和this」
你可能見過這樣一個函式:
function addUp(){
return Array.from(arguments).reduce(function (prev, curr) {
return prev + curr
})
}
並沒有定義過它,但是arguments
直接就這麼冒出來了。this
和它一樣,它們自動定義在所有函式的作用域中,即使你並沒有用它。
NZ在《JavaScript高階程式設計》裡給this
下了個定義:「this引用的是函式據以執行的環境物件」。這就像是很多人強調的,不要看函式在哪裡定義,要看函式在哪裡被呼叫。
二、為什麼?
this
機制可以解耦物件和函式:
function identify () {
return this.name.toUpperCase()
}
function speak () {
var greeting = "Hello,I`m + identify.call(this)"
console.log(greeting)
}
var me = {
name: `cyq`
}
var you = {
name: `reader`
}
identify.call(me) // CYQ
identify.call(you) // READER
speak.call(me) // Hello,I`m CYQ
speak.call(you) // Hello,I`m READER
這段程式碼可以在不同的上下文物件中重複使用identify
和speak
,不用針對每個物件編寫不同版本的函式。如果沒有this
,就需要給函式顯式地傳遞一個上下文物件。隨著你的模式越來越複雜,顯式傳遞上下文會讓程式碼越來越混亂。當涉及到原型委託的時候,你就會明白函式可以自動引用合適的上下文物件有多重要。
三、怎麼綁?
-
The keyword “this” refers to whatever is left of the dot at call-time.
-
If there`s nothing to the left of the dot, then “this” is the root scope (e.g. Window).
-
A few functions change the behavior of “this”—bind, call and apply
-
The keyword “new” binds this to the object just created
如果看過了這個四條法則,那判斷this
指向就好辦了啊,倒過來看就好了:
-
函式是否被new修飾(new繫結)?如果是的話,
this
繫結的就是新建立的物件 -
函式是否通過call、apply()或者bind()硬繫結(顯式繫結)?如果是的話,
this
繫結的就是指定的物件。 -
函式是否在某個上下文物件呼叫(隱式繫結)?如果是的話,
this
繫結的就是那個物件。 -
如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結
undefined
,否則是全域性物件。
這可能是這篇文章最核心的地方,但是鑑於這四條太常見了,我假設讀者已經知道它們了。
一、陷阱
1. 指向自身
把
this
指向函式自身,從語法角度來說是說得通的
function foo(num) {
this.count++
}
foo.count = 0
for(let i = 0;i < 10;i++) {
if(i > 5) {
foo(i)
}
}
console.log(foo.count) // 0
如果this
指向的是函式自身的話,foo.count
就該是4而不是0了。仔細看看就能明白這裡是符合第四條的預設繫結的。
2. 指向詞法作用域
不要看它是在哪定義的,也不要看它是在哪被呼叫的,看它是怎麼被呼叫的
function foo() {
let a = 2
bar()
}
function bar() {
console.log(this.a)
}
foo() // ReferenceError: a is not a defined
對照第四條,預設繫結。
每當你想要把this
和詞法作用域的查詢混合使用時,記得提醒自己,這是沒法實現的,箭頭函式例外。
3. 隱式丟失
隱式丟失通常包括這幾種情況:
-
賦值運算子 : (f = foo.bar)()
-
逗號運算子 : (1, foo.bar)()
-
回撥函式
這一點可以說是一個龐大的工程,為此我翻譯了這篇部落格。翻譯這篇部落格這是件非常累的事兒,幸運的是,看完這篇部落格之後,我明白了很多我沒想到的東西。
所有需要了解的東西都可以在這篇部落格裡找到答案。
二、問題
1. self = this是什麼?
我們知道,當程式碼進入一個函式時,作用域會把函式的引數、this 物件和 arguments物件包括在內。因此我們不可能在一個函式裡面用this取到其他函式作用域內的this物件。
var object = {
name: `My object`,
getName: function() {
return function() {
return this.name
}
}
}
console.log(object.getName()())
這段程式碼會輸出的是什麼?雖然看上去很像是`My Object`,但是結果是undefined
.原因上面提到了:最內層的匿名函式的作用域內的this
和getName的this
是兩回事。那想要輸出`My Object`的話,自然要想辦法把getName的this繫結到匿名函式下:
var me = {
name: `cyq`,
getName: function() {
var self = this
return function() {
return self.name
}
}
}
console.log(object.getName()())
可以看到,self=this
常用的場景就是幫我們在一個內層的函式裡找到外層函式的this物件。我們需要把外層的this繫結在一個變數上,從而讓內層函式完成某種類似於詞法作用域的查詢。我個人是很不喜歡這個寫法的,後來我發現我不是一個人:搞懂JavaScript的Function.prototype.bind。
如果你瞭解過箭頭函式的話,你可能知道箭頭函式可以淘汰掉這種寫法。
2. 箭頭函式
雖然少打好幾個字元就已經足夠讓人開心了,但是箭頭函式的能力還不止於此。
var me = {
name: `cyq`,
getName: function() {
return () => {
return this.name
}
}
}
console.log(object.getName()()) //cyq
所以箭頭函式一定對它裡面的this
做了重新設計咯?答案是否定的。
箭頭函式能實現類似詞法作用域的查詢是因為,它裡面根本就沒有this
物件。前面提到,this
自動定義在所有函式的作用域內,內嵌函式是不能查詢到外部的this
物件的,因為自己的作用域裡就有this
了。而箭頭函式這個特殊的函式直接去作用域裡找this
。