JS 總結之關於 this 應該知道的幾個點

Karon_發表於2018-12-19

JS 中的 this 對每位前端工程師都不陌生,經常看到物件這裡 this 那裡 this,那什麼是 this?答案就是上下文物件,即被呼叫函式所處的環境,也就是說,this 在函式內部指向了呼叫函式的物件。

通俗的講,就是誰呼叫了函式

? 情況 1

this 指向 window

var name = 'xiaoming' // 思考,為什麼不能用 let 或者 const ?
function foo () {
  console.log(this.name)
}
foo() // xiaoming
複製程式碼

誰呼叫了這個函式,答案就是 window。這好理解,因為這裡的變數和函式都是直接掛在 window 上的,等同於 window.foo()。需注意,嚴格模式下,this 為 undefined

? 情況 2

this 指向一個物件

var name = 'xiaoming'
var foo = {
  name: 'Jon',
  getName () {
    console.log(this.name)
  }
}
foo.getName() // Jon
複製程式碼

誰呼叫了這個函式,答案是 foo 物件,所以列印了 Jon 而不是 xiaoming

var bar = foo.getName
bar() // xiaoming
複製程式碼

如果賦值到另一個變數,就變成 window 呼叫,所以列印了 xiaoming

? 情況 3

this 指向了一個用 new 新生成的物件

function Person (name) {
  this.name = name
  this.getName = function () {
    console.log(this.name)
  }
}

var jser = new Person('Jon')
jser.getName() // Jon
複製程式碼

這種方式成為 new 繫結,也叫做構造呼叫

JavaScript 中,new 的機制實際上和麵向類的語言完全不同,建構函式只是一些使用 new 操作符時被呼叫的函式,它們並不會屬於某個類,也不會例項化一個類。

實際上,除 ES6 的 Symbol()外,所有函式都可以用 new 來呼叫,所以並不存在所謂的建構函式,只有對於函式進行構造呼叫

使用 new 來呼叫函式,或者說發生建構函式呼叫時,會自動執行下面的操作:

  1. 建立(或者說構造)一個全新的物件
  2. 這個新物件會被執行原型連結
  3. 這個新物件會繫結到函式呼叫的 this
  4. 如果函式沒有返回其他物件,那麼 new 表示式中的函式呼叫會自動返回這個新物件

這個例子中,使用 new 來呼叫 Person(..) 時,我們會構造一個新物件(jser)並把它繫結到 Person(..) 呼叫中的 this 上。 或者可以這麼想,誰呼叫了 getName ?就是 jser 呼叫了,所以 this 指向了 jser

? 情況 4

用 call,apply 和 bind 來修改 this 指向

? call / apply

call 和 apply 是以不同物件作為上下文物件來呼叫某個函式,舉個例子:

var bar = {
  name: 'bar',
  getName () {
    console.log(this.name)
  }
}

var foo = {
  name: 'foo'
}

bar.getName.call(foo) // foo
複製程式碼

看起來像是借用函式,物件 foo 借用了 bar 的函式 getName,所以我們判斷一個物件型別,經常這麼搞:

let foo = [1,2,3,4,5]
Object.prototype.toString.call(foo) // "[object Array]"
複製程式碼

apply 和 call 的用法一樣,不同點在於 call 用參數列給呼叫函式傳參,而 apply 使用了陣列

? bind

bind 可以永久性的修改函式中的 this 的指向,無論誰呼叫,this 指向都一樣,並返回了完成繫結的函式,看例子:

var bar = {
  name: 'bar',
  getName () {
    console.log(this.name)
  }
}

var foo = {
  name: 'foo'
}

foo.func = bar.getName.bind(bar)
foo.func() // bar
複製程式碼

這裡的 func 不受 foo 影響,this 還是指向了 bar

var bar = {
  name: 'bar',
  getName () {
    console.log(this.name)
  }
}

func = bar.getName.bind(bar)
func() // bar
複製程式碼

這裡的 func 也不受 window 影響,this 還是指向了 bar

綜合上述,bind 強制修改了 this,誰呼叫了函式 this 都不能被修改

? 忽略 this

如果你把 null 或者 undefined 作為 this 的繫結物件傳入 call、apply 或者 bind,這些值再呼叫時會被忽略,實際應用的是預設繫結。

? 箭頭函式

ES6 中介紹了一種無法使用這些規則的特殊函式型別:箭頭函式,根據外層(函式或者全域性)作用域來決定 this,箭頭函式常用於回撥函式

? 情況 1 中,為什麼不能用 let 宣告?

ES6 中,let 命令、const 命令、class 命令宣告的全域性變數,不屬於頂層物件的屬性,window 無法訪問到。var 命令和 function 命令宣告的全域性變數,屬於頂層物件的屬性,window 能訪問到。

所以 情況 1 中改為:

let name = 'xiaoming'
function foo () {
  console.log(this.name)
}
foo() // undefined
複製程式碼

? 參考

相關文章