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 來呼叫函式,或者說發生建構函式呼叫時,會自動執行下面的操作:
- 建立(或者說構造)一個全新的物件
- 這個新物件會被執行原型連結
- 這個新物件會繫結到函式呼叫的 this
- 如果函式沒有返回其他物件,那麼 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
複製程式碼
? 參考
- 《ECMAScript 6 入門》let 和 const 命令 - 頂層物件的屬性 by 阮一峰
- 《【進階 3-5 期】深度解析 new 原理及模擬實現》 By 木易楊
- 《你不知道的 JavaScript 上卷》第二部分 第 2 章
- 《Node.js 開發指南》附錄 A