王福朋老師的 JavaScript原型和閉包系列 文章看了不下三遍了,做爲一個初學者,每次看的時候都會有一種 "大徹大悟" 的感受,而看完以後卻老是一臉懵逼。原型與閉包 能夠說是 JavaScirpt 中理解起來最難的部分了,也是這門面向對象語言很重要的部分,固然,我也只是瞭解到了一些皮毛,對於 JavaScript OOP 更是缺少經驗。這裏我想總結一下 Javascript 中的 this
關鍵字,王福朋老師的在文章裏也花了大量的篇幅來說解 this
關鍵字的使用,能夠說 this
關鍵字也是值得重視的。javascript
做者:正偉html
原文連接:this關鍵字java
一道很常見的題目:下面代碼將會輸出的結果是什麼?node
const obj1 = {
a: 'a in obj1',
foo: () => { console.log(this.a) }
}
const obj2 = {
a: 'a in obj2',
bar: obj1.foo
}
const obj3 = {
a: 'a in obj3'
}
obj1.foo() // 輸出 ??
obj2.bar() // 輸出 ??
obj2.bar.call(obj3) // 輸出 ??
複製代碼
在弄明白 this 關鍵字以前,也許很難來回答這道題。git
那先從上下文環境提及吧~github
咱們都知道,每個 代碼段 都會執行在某一個 上下文環境 當中,而在每個代碼執行以前,都會作一項 "準備工做",也就是生成相應的 上下文環境,因此每個 上下文環境 均可能會不同。瀏覽器
上下文環境 是什麼?咱們能夠去看王福朋老師的文章(連接在文末),講解的很清楚,這裏再也不贅述。閉包
代碼段 能夠分爲三種:app
eval
代碼與之對應的 上下文環境 就有:函數
(elav
就不討論了,不推薦使用)
固然,這和 this
又有什麼關係呢?this
的值就是在爲代碼段作 "準備工做" 時賦值的,能夠說 this
就是 上下文環境 的一部分,而每個不一樣的 上下文環境 可能會有不同的 this
值。
這裏我大膽的將 this
關鍵字的使用分爲兩種狀況:
- 全局上下文的
this
- 函數上下文的
this
(你也能夠選擇其餘的方式分類。固然,這也不重要了)
this
在全局執行上下文中(在任何函數體外部),this
都指向全局對象:
// 在瀏覽器中, 全局對象是 window
console.log(this === window) // true
var a = 'Zavier Tang'
console.log(a) // 'Zavier Tang'
console.log(window.a) // 'Zavier Tang'
console.log(this.a) // 'Zavier Tang'
this.b = 18
console.log(b) // 18
console.log(window.b) // 18
console.log(this.b) // 18
// 在 node 環境中,this 指向global
console.log(this === global) // true
複製代碼
注意:在任何函數體外部,都屬於全局上下文,
this
都指向全局對象(window
/global
)。在對象的內部,也是在全局上下文,this
一樣指向全局對象(window
/global
)
window.a = 10
var obj = {
x: this.a,
_this: this
}
obj.x // 10
obj._this === this // true
複製代碼
this
在函數內部,this
的值取決與函數被調用的方式。
this
的值在函數定義的時候是肯定不了的,只有函數調用的時候才能肯定 this
的指向。實際上 this
最終指向的是那個調用它的對象。(也不必定正確)
對於全局的方法調用,this
指向 window
對象(node下爲 global
):
var foo = function () {
return this
}
// 在瀏覽器中
foo() === window // true
// 在 node 中
foo() === global //true
複製代碼
但值得注意的是,以上代碼是在 非嚴格模式 下。然而,在 嚴格模式 下,
this
的值將保持它進入執行上下文的值:
var foo = function () {
"use strict"
return this
}
foo() // undefined
複製代碼
即在嚴格模式下,若是 this
沒有被執行上下文定義,那它爲 undefined
。
在生成 上下文環境 時:
- 若方法被
window
(或global
)對象調用,即執行window.foo()
,那this
將會被定義爲window
(或global
);- 若被普通對象調用,即執行
obj.foo()
,那this
將會被定義爲obj
對象;(後面會討論)- 但若未被對象調用(上面分別是被
window
對象和普通對象obj
調用),即直接執行foo()
,在非嚴格模式下,this
的值默認指向全局對象window
(或global
),在嚴格模式下,this
將保持爲undefined
。
經過 this
調用全局變量:
var a = 'global this'
var foo = function () {
console.log(this.a)
}
foo() // 'global this'
複製代碼
var a = 'global this'
var foo = function () {
this.a = 'rename global this' // 修改全局變量 a
console.log(this.a)
}
foo() // 'rename global this'
複製代碼
因此,對於全局的方法調用,this
指向的是全局對象 window
(或global
),即調用方法的對象。(注意嚴格模式的不一樣)
函數在全局上下文中調用,
foo()
能夠看做是window.foo()
,只不過在嚴格模式下有所限制。
當函數做爲對象的方法調用時,它的 this
值是調用該函數的對象。也就是說,函數的 this
值是在函數被調用時肯定的,在定義函數時肯定不了(箭頭函數除外)。
var obj = {
name: 'Zavier Tang',
foo: function () {
console.log(this)
console.log(this.name)
}
}
obj.foo() // Object {name: 'Zavier Tang', foo: function} // 'Zavier Tang'
//foo函數不是做爲obj的方法調用
var fn = obj.foo // 這裏foo函數並無執行
fn() // Window {...} // undefined
複製代碼
this
的值同時也只受最靠近的成員引用的影響:
//接上面代碼
var o = {
name: 'Zavier Tang in object o',
fn: fn,
obj: obj
}
o.fn() // Object {name: 'Zavier Tang in object o', fn: fn, obj: obj} // 'Zavier Tang in object o'
o.obj.foo() // Object {name: 'Zavier Tang', foo: function} // 'Zavier Tang'
複製代碼
在原型鏈中,this
的值爲當前對象:
var Foo = function () {
this.name = 'Zavier Tang'
this.age = 20
}
// 在原型上定義函數
Foo.prototype.getInfo = function () {
console.log(this.name)
console.log(this.age)
console.log(this === tang)
}
var tang = new Foo()
tang.getInfo() // "Zavier Tang" // 20 // true
複製代碼
雖然這裏調用的是一個繼承方法,但 this
所指向的依然是 tang
對象。
也能夠看做是對象
tang
調用了getInfo
方法,this
指向了tang
。即this
指向了調用它的那個對象。
參考:《Object-Oriented JavaScript》(Second Edition)
若是函數做爲構造函數,那函數當中的 this
即是構造函數即將 new
出來的對象:
var Foo = function () {
this.name = 'Zavier Tang',
this.age = 20,
this.year = 1998,
console.log(this)
}
var tang = new Foo()
console.log(tang.name) // 'Zavier Tang'
console.log(tang.age) // 20
console.log(tang.year) // 1998
複製代碼
當 Foo
不做爲構造函數調用時,this
的指向即是前面討論的,指向全局變量:
// 接上面代碼
Foo() // window {...}
複製代碼
構造函數一樣能夠看做是一個普通的函數(只不過函數名稱第一個字母大寫了而已咯),可是在用
new
關鍵字調用構造函數建立對象時,它與普通函數的行爲不一樣罷了。
apply
、call
、 bind
時當一個函數在其主體中使用 this
關鍵字時,能夠經過使用函數繼承自Function.prototype
的 call
或 apply
方法將 this
值綁定到特定對象。即 this
的值就取傳入對象的值:
var obj1 = { name: 'Zavier1' }
var obj2 = { name: 'Zavier2' }
var foo = function () {
console.log(this)
console.log(this.name)
}
foo.apply(obj1) // Ojbect {name: 'Zavier1'} //'Zavier1'
foo.call(obj1) // Ojbect {name: 'Zavier1'} //'Zavier1'
foo.apply(obj2) // Ojbect {name: 'Zavier2'} //'Zavier2'
foo.call(obj2) // Ojbect {name: 'Zavier2'} //'Zavier2'
複製代碼
與 apply
、call
不一樣,使用 bind
會建立一個與 foo
具備相同函數體和做用域的函數。可是,特別要注意的是,在這個新函數中,this
將永久地被綁定到了 bind
的第一個參數,不管以後如何調用。
var f = function () {
console.log(this.name)
}
var obj1 = { name: 'Zavier1' }
var obj2 = { name: 'Zavier2' }
var g = f.bind(obj1)
g() // 'Zavier1'
var h = g.bind(ojb2) // bind只生效一次!
h() // 'Zavier1'
var o = {
name: 'Zavier Tang',
f:f,
g:g,
h:h
}
o.f() // 'Zavier Tang'
o.g() // 'Zavier1'
o.h() // 'Zavier1'
複製代碼
到這裏,「
this
最終指向的是那個調用它的對象」 這句話就不通用了,函數調用call
、apply
、bind
方法是一個特殊狀況。下面還有一種特殊狀況:箭頭函數。
箭頭函數是 ES6 語法的新特性,在箭頭函數中,this
的值與建立箭頭函數的上下文的 this
一致。
在全局代碼中,this
的值爲全局對象:
var foo = (() => this)
//在瀏覽器中
foo() === window // true
// 在node中
foo() === global // true
複製代碼
其實箭頭函數並無本身的 this
。因此,調用 this
時便和調用普通變量同樣在做用域鏈中查找,獲取到的便是建立此箭頭函數的上下文中的 this
。若建立此箭頭函數的上下文中也沒有 this
,便繼續沿着做用域鏈往外查找,直到全局做用域,這時便指向全局對象(window
/ global
)。
當箭頭函數在建立其的上下文外部被調用時,箭頭函數即是一個閉包,this
的值一樣與原上下文環境中的 this
的值一致。因爲箭頭函數自己是不存在 this
,經過 call
、 apply
或 bind
修改 this
的指向是沒法實現的。
做爲對象的方法:
var foo = (() => this)
var obj = {
foo: foo
}
// 做爲對象的方法調用
obj.foo() === window // true
// 用apply來設置this
foo.apply(obj) === window // true
// 用bind來設置this
foo = foo.bind(obj)
foo() === window // true
複製代碼
箭頭函數 foo
的 this
被設置爲建立時的上下文(在上面代碼中,也就是全局對象)的 this
值,並且沒法經過其餘調用方式設定 foo
的 this
值。
與普通函數對比,箭頭函數的 this
值是在函數建立時肯定的,並且沒法經過調用方式從新設置 this
值。普通函數中的 this
值是在調用的時候肯定的,可經過不一樣的調用方式設定 this
值。
回到開篇的問題上,輸出結果爲:
// undefined
// undefined
// undefined
複製代碼
由於箭頭函數是在對象 obj1
內部建立的,在對象內部屬於全局上下文(注意只有全局上下文和函數上下文),this
一樣是指向全局對象,即箭頭函數的 this
指向全局對象且沒法被修改。
在全局對象中,沒有定義變量 a
,因此便輸出三個了 undefined
。
const obj1 = {
a: 'a in obj1',
foo: () => { console.log(this.a) }
}
複製代碼
this
關鍵字的值取決於其所處的位置(上下文環境):
在全局環境中,this
的值指向全局對象( window
或 global
)。
在函數內部,this
的取值取決於其所在函數的調用方式,也就是說 this
的值是在函數被調用的時候肯定的,在建立函數時沒法肯定(詳解:this關鍵字)。如下四種調用方式:
全局中調用:指向全局對象
window
/global
,foo
至關於window.foo
在嚴格模式下有所不一樣;做爲對象的方法屬性:指向調用函數的對象,在調用繼承的方法時也是如此;
new 關鍵字調用:構造函數只不過是一個函數名稱第一個字母大寫的普通函數而已,在用
new
關鍵字調用時,this 指向新建立的對象;call / apply / bind:
call
/apply
/bind
能夠修改函數的this
指向,bind
綁定的this
指向將沒法被修改。
固然,箭頭函數是個例外,箭頭函數自己不存在 this
,而在箭頭函數中使用 this
獲取到的即是建立其的上下文中的 this
。
參考: