王福朋老師的 JavaScript原型和閉包系列 文章看了不下三遍了,最爲一個初學者,每次看的時候都會有一種 "大徹大悟" 的感受,而看完以後卻老是一臉懵逼。原型與閉包 能夠說是 JavaScirpt 中理解起來最難的部分了,固然,我也只是瞭解到了一些皮毛,對於 JavaScript OOP 更是缺少經驗。這裏我想總結一下 Javascript 中的 this
關鍵字,王福朋老師的在文章裏也花了大量的篇幅來說解 this
關鍵字的使用,能夠說 this
關鍵字也是值得重視的。javascript
咱們都知道,每個 "代碼段" 都會執行在某一個 上下文環境 當中,而在每個代碼執行以前,都會作一項 "準備工做",也就是生成相應的 上下文環境,因此每個 上下文環境 均可能會不同。html
上下文環境 是什麼?咱們能夠去看王福朋老師的文章(連接在文末),講解的很清楚了,這裏不贅述了。java
"代碼段" 能夠分爲三種:node
eval
代碼與之對應的 上下文環境 就有:git
(elav
就不討論了,不推薦使用)github
固然,這和 this
又有什麼關係呢?this
的值就是在爲代碼段作 "準備工做" 時賦值的,能夠說 this
就是 上下文環境 的一部分,而每個不一樣的 上下文環境 可能會有不同的 this
值。瀏覽器
每次在尋找一個問題的解決方案或總結一個問題的時候,我總會去嘗試將這個問題進行合適的分類,而從不一樣的方面去思考問題。
因此,這裏我大膽的將 this
關鍵字的使用分爲兩種狀況:閉包
this
this
(你也能夠選擇其餘的方式分類。固然,這也不重要了)app
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
在函數內部,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 } f2() // undefined
即在嚴格模式下,若是 this
沒有被執行上下文定義,那它爲 undefined
。
在生成 上下文環境 時:
- 若方法被
window
(或global
)對象調用,即執行window.foo()
,那this
將會被定義爲window
(或global
);- 若被普通對象調用,即執行
obj.foo()
,那this
將會被定義爲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
),即調用方法的對象。(注意嚴格模式的不一樣)
當函數做爲對象的方法調用時,它的 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) } var tang = new Foo() tang.getInfo() // "Zavier Tang" // 20
雖然這裏調用的是一個繼承方法,但 this
所指向的依然是 tang
對象。
參考:《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 {...}
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 foo = function () { console.log(this.name) } var obj1 = { name: 'Zavier1' } var obj2 = { name: 'Zavier2' } var g = foo.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'
箭頭函數是 ES6 語法的新特性,在箭頭函數中,this
的值與建立箭頭函數的上下文的 this
一致。
在全局代碼中,this
的值爲全局對象:
var foo = (() => this) //在瀏覽器中 foo() === window // true // 在node中 foo() === global // true
其實箭頭函數並無本身的 this
。因此,調用 this
時便和調用普通變量同樣在做用域鏈中查找,獲取到的便是建立此箭頭函數的上下文中的 this
。
當箭頭函數在建立其的上下文外部被調用時,箭頭函數即是一個閉包,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
值。
this
關鍵字的值取決於其所處的位置(上下文環境):
this
的值指向全局對象( window 或 global )。this
的取值取決於其所在函數的調用方式,也就是說 this
的值是在函數被調用的時候肯定的,在建立函數時沒法肯定。固然,箭頭函數是個例外,箭頭函數自己不存在 this
,而在箭頭函數中使用 this
獲取到的即是建立其的上下文中的 this
。同時,使用函數的繼承方法 call
、 apply
和 bind
會修改 this
的指向。但值得注意的是,使用 bind
方法會使 this
的值永久的綁定到給定的對象,沒法再經過調用 call
和 apply
方法修改 this
的值,箭頭函數調用 call
、 apply
或 bind
方法沒法修改 this
。參考: