`this Javascript this this`的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。 綁定規則 綁定根據函數的調用方式基本上有四種規則: 全局性調用 函數的最通常用法, 代表全局對象: 作為對象的方法調用 函數作為某個對象的方法調用時, 指向這個上級對象: 當存在多重調 ...
this
是Javascript
函數內部的一個特殊對象,引用的是函數運行時的環境對象,也就是說,this
是動態的(箭頭函數除外),是在運行時進行綁定的,並不是在編寫時綁定(箭頭函數是編寫時綁定)。 this
的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。
綁定規則
this
綁定根據函數的調用方式基本上有四種規則:
全局性調用
函數的最通常用法,this
代表全局對象:
function sayColor() {
console.log(this.color)
}
var color = 'red'
sayColor() // red
作為對象的方法調用
函數作為某個對象的方法調用時,this
指向這個上級對象:
let car = {
color: 'black',
sayColor
}
car.sayColor() // black
當存在多重調用時,最後一層會決定調用位置:
let house = {
color: 'white',
car
}
house.car.sayColor() // black
使用apply、call、bind方法綁定調用對象
函數可以使用apply、call、bind
方法來改變調用對象。這些方法的第一個參數是一個對象,它們會把這個對象綁定到this
:
let bindObj = {
color: 'green'
}
sayColor.apply(bindObj) // green
sayColor.call(bindObj) // green
sayColor.bind(bindObj)() // green
第一個參數為空時,預設綁定全局對象:
sayColor.apply() // red
構造函數調用
當函數作為構造函數被調用時,this
指向創建的新對象:
function Person(color) {
this.color = color
}
let p = new Person('yellow')
p.color // yellow
使用new操作符調用構造函數時,首先會創建一個新對象,然後將構造函數的作用域賦給新對象,this
就指向了新對象,過程與下麵代碼類似:
let o = new Object()
Person.call(o, 'yellow')
o.color // yellow
當然,構造函數不通過new調用時,與普通函數無區別,可以理解為實際上並不存在所謂的構造函數,只有對於函數的構造調用:
Person('yellow')
// this指向了全局對象window
window.color // yellow
以上就是this
綁定的四種規則,函數調用時,先找到調用位置,然後判斷需要應用四條規則中的哪一條。
應用
現在來看下實際應用的幾種特殊情況。
回調函數
當做回調函數調用時,一般是全局調用:
setTimeout(sayColor) // red 指向window
let obj = {
color: 'green',
say () {
sayColor()
}
}
obj.say() // red 指向window
但在一些上下文中會進行隱式綁定,比如事件中的this
是指向於事件的目標元素的,還有一些數組的操作方法可以使用第二個參數來綁定this
:
[1, 2, 3].forEach(function () { console.log(this.color) }) // red red red 指向window
[1, 2, 3].forEach(function () { console.log(this.color) }, car) // black black black 指向car對象
綁定丟失
function sayColor() {
console.log(this.color)
}
let car = {
color: 'black',
sayColor
}
var color = 'red'
let alias = car.sayColor
alias() // red
上述代碼中將對象的方法賦值給新變數alias
,alias
函數執行時,this
指向了全局對象。
函數的名字僅僅是一個包含指向函數對象的指針的變數,car對象的sayColor屬性保存在一個屬性描述符對象中:
Object.getOwnPropertyDescriptor(car, 'sayColor')
// {value: ƒ, writable: true, enumerable: true, configurable: true}
其中描述符對象的value屬性保存了指向sayColor函數的指針,let alias = car.sayColor
語句將指向sayColor函數的指針賦值給變數alias
,執行alias
函數就是在全局對象中執行函數sayColor
。
當做回調函數時:
setTimeout(car.sayColor, 500) // red 指向window
因為Javascript中的函數參數都是按值傳遞的,上述代碼將指向sayColor
函數的指針賦值給了setTimeout
函數的參數,也相當於在全局環境中執行sayColor
函數。
閉包
匿名函數的執行一般具有全局性,在閉包中由於編寫方式可能不會那麼明顯:
let obj = {
color: 'green',
say () {
return function() {
console.log(this.color)
}
}
}
obj.say()() // red this指向全局window
內部匿名函數是有自己的this
變數的,所以無法訪問到外部函數say
的this
變數,我們可以將外部this
變數保存於一個閉包能夠訪問的變數之中:
let obj = {
color: 'green',
say () {
let self = this
return function() {
console.log(self.color)
}
}
}
obj.say()() // green
當然,現在可以用箭頭函數來綁定this:
let obj = {
color: 'green',
say () {
return () => {
console.log(this.color)
}
}
}
obj.say()() // green
優先順序
全局性調用優先順序是最低的。
使用apply
等函數綁定this
的優先順序高於對象調用:
let car = {
color: 'black',
sayColor
}
let bindObj = {
color: 'green'
}
car.sayColor() // black
car.sayColor.apply(bindObj) // green
使用new
操作符綁定高於使用apply
等函數:
function Person(color) {
this.color = color
}
let obj = {}
let bindPerson = Person.bind(obj)
let p = new bindPerson('yellow')
p.color // yellow this指向了創建的對象p
obj.color // undefined 沒有指向obj
箭頭函數與this
ES6中引進了箭頭函數,可以簡化匿名函數的語法:
setTimeout(() => { console.log(this.color) }, 50)
箭頭函數內部是沒有this
、arguments
、super
、new.target
特殊變數的,訪問它們時會指向最近的外層非箭頭函數的相應變數:
function sayColor() {
return () => {
return () => {
console.log(this.color)
}
}
}
sayColor.call({ color: 'red' })()() // red 指向了外層sayColor函數的this對象
sayColor.call({ color: 'red' }).call({ color: 'green' })() // red 依然指向外層sayColor函數的this對象
sayColor.call({ color: 'red' }).call({ color: 'green' }).call({ color: 'yellow' }) // red箭頭函數使用call是無法綁定this的
所以,箭頭函數可以起到固定化this
指向的效果,一定程度上可以說this
是靜態的,參考上面閉包的代碼:
// ES6箭頭函數
let obj = {
color: 'green',
sayColor () {
return () => {
console.log(this.color)
}
}
}
// ES5
let obj = {
color: 'green',
sayColor () {
let self = this
return function() {
console.log(self.color)
}
}
}
當然,靜態並不意味著箭頭函數的this
是永遠不變的,而是隨著外層函數的this
變化而變化:
let obj = {
color: 'green',
sayColor () {
return () => {
console.log(this.color)
}
}
}
obj.sayColor()() // green
obj.sayColor.call({ color: 'red' })() // red
不適用情況
在事件中想將this
指向目標元素時,箭頭函數是不適用的:
btn.addEventListener('click', () => {
console.log(this)
})
上述代碼中this
指向了全局對象,而不是事件的目標元素。
將函數當做對象的方法調用並且想將this
指向對象時,也是不適用的:
let obj = {
color: 'green',
sayColor: () => {
console.log(this.color)
}
}
上述代碼中this
也指向了全局對象。
總之,需要this
動態時使用非箭頭函數,需要this
靜態時使用箭頭函數:
function Person() {
this.color = 'yellow'
setTimeout(() => { console.log('person color is',this.color) }, 50)
setTimeout(function() { console.log('global color is',this.color) }, 50)
}
var color = 'red'
new Person()
// 輸出
person color is yellow
global color is red