如何理解JavaScript的this關鍵字

前言

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


咱們都知道,每個 "代碼段" 都會執行在某一個 上下文環境 當中,而在每個代碼執行以前,都會作一項 "準備工做",也就是生成相應的 上下文環境,因此每個 上下文環境 均可能會不同。html

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

"代碼段" 能夠分爲三種:node

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

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

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

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

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

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

因此,這裏我大膽的將 this 關鍵字的使用分爲兩種狀況:閉包

  1. 全局上下文的 this
  2. 函數上下文的 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 的最終指向的是那個調用它的對象。(也不必定正確)

1. 全局函數

對於全局的方法調用,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 ),即調用方法的對象。(注意嚴格模式的不一樣)

2. 做爲對象的方法

當函數做爲對象的方法調用時,它的 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)

3. 做爲構造函數

若是函數做爲構造函數,那函數當中的 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 {...}

4. 函數調用 applycallbind

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

applycall 不一樣,使用 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'

5. 箭頭函數

箭頭函數是 ES6 語法的新特性,在箭頭函數中,this 的值與建立箭頭函數的上下文的 this 一致。

在全局代碼中,this 的值爲全局對象:

var foo = (() => this)
//在瀏覽器中
foo() === window // true
// 在node中
foo() === global // true

其實箭頭函數並無本身的 this。因此,調用 this 時便和調用普通變量同樣在做用域鏈中查找,獲取到的便是建立此箭頭函數的上下文中的 this

當箭頭函數在建立其的上下文外部被調用時,箭頭函數即是一個閉包,this 的值一樣與原上下文環境中的 this 的值一致。因爲箭頭函數自己是不存在 this,經過 callapplybind 修改 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

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

與普通函數對比,箭頭函數的 this 值是在函數建立建立肯定的,並且沒法經過調用方式從新設置 this 值。普通函數中的 this 值是在調用的時候肯定的,可經過不一樣的調用方式設定 this 值。

總結

this 關鍵字的值取決於其所處的位置(上下文環境):

  1. 在全局環境中,this 的值指向全局對象( window 或 global )。
  2. 在函數內部,this 的取值取決於其所在函數的調用方式,也就是說 this 的值是在函數被調用的時候肯定的,在建立函數時沒法肯定。固然,箭頭函數是個例外,箭頭函數自己不存在 this,而在箭頭函數中使用 this 獲取到的即是建立其的上下文中的 this。同時,使用函數的繼承方法 callapplybind 會修改 this 的指向。但值得注意的是,使用 bind 方法會使 this 的值永久的綁定到給定的對象,沒法再經過調用 callapply 方法修改 this 的值,箭頭函數調用 callapplybind 方法沒法修改 this

原文連接


參考:

相關文章
相關標籤/搜索