前言:王福朋老師的 "JavaScript原型和閉包系列" 文章看了不下三遍了,最為一個初學者,每次看的時候都會有一種 “大徹大悟” 的感覺,而看完之後卻總是一臉懵逼。 原型與閉包 可以說是 JavaScirpt 中理解起來最難的部分了,當然,我也只是瞭解到了一些皮毛,對於 JavaScript O ...
前言:王福朋老師的 JavaScript原型和閉包系列 文章看了不下三遍了,最為一個初學者,每次看的時候都會有一種 “大徹大悟” 的感覺,而看完之後卻總是一臉懵逼。原型與閉包 可以說是 JavaScirpt 中理解起來最難的部分了,當然,我也只是瞭解到了一些皮毛,對於 JavaScript OOP 更是缺乏經驗。這裡我想總結一下 Javascript 中的 this
關鍵字,王福朋老師的在文章里也花了大量的篇幅來講解 this
關鍵字的使用,可以說 this
關鍵字也是個值得註意的。
我也不知道博客園編譯出來的Markdown文檔會這麼醜,作為一個完美主義者,有點無法忍受,附上原文鏈接
我們都知道,每一個 ”代碼段“ 都會執行在某一個 上下文環境 當中,而在每一個代碼執行之前,都會做一項 “準備工作”,也就是生成相應的 上下文環境,所以每一個 上下文環境 都可能會不一樣。
上下文環境 是什麼?我們可以去看王福朋老師的文章(鏈接在文末),講解的很清楚了,這裡不贅述了。
”代碼段“ 可以分為三種:
- 全局代碼
- 函數體
eval
代碼
與之對應的 上下文環境 就有:
- 全局上下文
- 函數上下文
(elav
就不討論了,不推薦使用)
當然,這和 this
又有什麼關係呢?this
的值就是在為代碼段做 “準備工作” 時賦值的,可以說 this
就是 上下文環境 的一部分,而每一個不同的 上下文環境 可能會有不一樣的 this
值。
每次在尋找一個問題的解決方案或總結一個問題的時候,我總會去嘗試將這個問題進行合適的分類,而從不同的方面去思考問題。
所以,這裡我大膽的將 this
關鍵字的使用分為兩種情況:
全局上下文的
this
函數上下文的
this
(你也可以選擇其他的方式分類。當然,這也不重要了)
全局上下文中的 this
在全局執行上下文中(在任何函數體外部),this
都指向全局對象:
// 在瀏覽器中, 全局對象是 window
console.log(this === window) // true
let 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(a) // 18
console.log(window.a) // 18
console.log(this.a) // 18
// 在 node 環境中,this 指向global
console.log(this === global) // true
函數上下文中的 this
在函數內部,this
的值取決與函數被調用的方式。
this
的值在函數定義的時候是確定不了的,只有函數調用的時候才能確定 this
的指向。實際上 this
的最終指向的是那個調用它的對象。(也不一定正確)
1. 全局函數
對於全局的方法調用,this
指向 window
對象(node下為 global
):
let foo = function () {
return this
}
// 在瀏覽器中
foo() === window // true
// 在 node 中
foo() === global //true
但值得註意的是,以上代碼是在 非嚴格模式 下。然而,在 嚴格模式 下,this
的值將保持它進入執行上下文的值:
let 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
調用全局變數:
let a = 'global this'
let foo = function () {
console.log(this.a)
}
foo() // 'global this'
let a = 'global this'
let foo = function () {
this.a = 'rename global this' // 修改全局變數 a
console.log(this.a)
}
foo() // 'rename global this'
所以,對於全局的方法調用,this
指向的是全局對象 window
(或global
),即調用方法的對象。(註意嚴格模式的不同)
2. 作為對象的方法
當函數作為對象的方法調用時,它的 this
值是調用該函數的對象。也就是說,函數的 this
值是在函數被調用時確定的,在定義函數時確定不了(箭頭函數除外)。
let 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的方法調用
let fn = obj.foo // 這裡foo函數並沒有執行
fn() // Window {...} // undefined
this
的值同時也只受最靠近的成員引用的影響:
//接上面代碼
let 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'
3. 作為構造函數
如果函數作為構造函數,那函數當中的 this
便是構造函數即將 new
出來的對象:
let Foo = function () {
this.name = 'Zavier Tang',
this.age = 20,
this.year = 1998,
console.log(this)
}
let tang = new Foo()
console.log(tang.name) // 'Zavier Tang'
console.log(tang.age) // 20
console.log(tang.year) // 1998
當 Foo
不作為構造函數調用時,this
的指向便是前面討論的,指向全局變數:
// 接上面代碼
Foo() // window {...}
4. 函數調用 apply
、call
、 bind
時
當一個函數在其主體中使用 this
關鍵字時,可以通過使用函數繼承自Function.prototype
的 call
或 apply
方法將 this
值綁定到調用中的特定對象。即 this
的值就取傳入對象的值:
let obj1 = {
name: 'Zavier1'
}
let obj2 = {
name: 'Zavier2'
}
let 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
的第一個參數,無論之後如何調用。
let foo = function () {
console.log(this.name)
}
let obj1 = {
name: 'Zavier1'
}
let obj2 = {
name: 'Zavier2'
}
let g = foo.bind(obj1)
g() // 'Zavier1'
let h = g.bind(ojb2) // bind只生效一次!
h() // 'Zavier1'
let o = {
name: 'Zavier Tang',
f:f,
g:g,
h:h
}
o.f() // 'Zavier Tang'
o.g() // 'Zavier1'
o.h() // 'Zavier1'
5. 箭頭函數
在箭頭函數中,this
的值與創建箭頭函數的上下文的 this
一致。
在全局代碼中,this
的值為全局對象:
let foo = (() => this)
//在瀏覽器中
foo() === window // true
// 在node中
foo() === global // true
作為對象的方法:
let foo = (() => this)
let 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
值。
參考:
- 深入理解javascript原型和閉包(完結) - 王福朋 - 博客園
- this - JavaScript | MDN
- javascript中的this作用域詳解 - coder_Jenny - 博客園