如何理解JavaScript的this關鍵字

前言:王福朋老師的 JavaScript原型和閉包系列 文章看了不下三遍了,最爲一個初學者,每次看的時候都會有一種 “大徹大悟” 的感覺,而看完之後卻總是一臉懵逼。原型與閉包 可以說是 JavaScirpt 中理解起來最難的部分了,當然,我也只是瞭解到了一些皮毛,對於 JavaScript OOP 更是缺乏經驗。這裏我想總結一下 Javascript 中的 this 關鍵字,王福朋老師的在文章裏也花了大量的篇幅來講解 this 關鍵字的使用,可以說 this 關鍵字也是個值得注意的。


我們都知道,每一個 ”代碼段“ 都會執行在某一個 上下文環境 當中,而在每一個代碼執行之前,都會做一項 “準備工作”,也就是生成相應的 上下文環境,所以每一個 上下文環境 都可能會不一樣。

上下文環境 是什麼?我們可以去看王福朋老師的文章(鏈接在文末),講解的很清楚了,這裏不贅述了。

”代碼段“ 可以分爲三種:

  • 全局代碼
  • 函數體
  • eval 代碼

與之對應的 上下文環境 就有:

  • 全局上下文
  • 函數上下文

elav 就不討論了,不推薦使用)

當然,這和 this 又有什麼關係呢?this 的值就是在爲代碼段做 “準備工作” 時賦值的,可以說 this 就是 上下文環境 的一部分,而每一個不同的 上下文環境 可能會有不一樣的 this值。

每次在尋找一個問題的解決方案或總結一個問題的時候,我總會去嘗試將這個問題進行合適的分類,而從不同的方面去思考問題。

所以,這裏我大膽的將 this 關鍵字的使用分爲兩種情況:

  1. 全局上下文的 this
  2. 函數上下文的 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. 函數調用 applycallbind

當一個函數在其主體中使用 this 關鍵字時,可以通過使用函數繼承自Function.prototypecallapply 方法將 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'

applycall 不同,使用 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

箭頭函數 foothis 被設置爲創建時的上下文(在上面代碼中,也就是全局對象)的 this 值,而且無法通過其他調用方式設定 foothis 值。

與普通函數對比,箭頭函數的 this 值是在函數創建創建確定的,而且無法通過調用方式重新設置 this 值。普通函數中的 this 值是在調用的時候確定的,可通過不同的調用方式設定 this 值。


參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章