this
是Javascript
函數內部的一個特殊對象,引用的是函數運行時的環境對象,也就是說,this
是動態的(箭頭函數除外),是在運行時進行綁定的,並非在編寫時綁定(箭頭函數是編寫時綁定)。 this
的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。javascript
this
綁定根據函數的調用方式基本上有四種規則:java
函數的最一般用法,this
表明全局對象:數組
function sayColor() { console.log(this.color) } var color = 'red' sayColor() // red
函數做爲某個對象的方法調用時,this
指向這個上級對象:閉包
let car = { color: 'black', sayColor } car.sayColor() // black
當存在多重調用時,最後一層會決定調用位置:app
let house = { color: 'white', car } house.car.sayColor() // black
函數可使用apply、call、bind
方法來改變調用對象。這些方法的第一個參數是一個對象,它們會把這個對象綁定到this
:函數
let bindObj = { color: 'green' } sayColor.apply(bindObj) // green sayColor.call(bindObj) // green sayColor.bind(bindObj)() // green
第一個參數爲空時,默認綁定全局對象:this
sayColor.apply() // red
當函數做爲構造函數被調用時,this
指向建立的新對象:指針
function Person(color) { this.color = color } let p = new Person('yellow') p.color // yellow
使用new操做符調用構造函數時,首先會建立一個新對象,而後將構造函數的做用域賦給新對象,this
就指向了新對象,過程與下面代碼相似:code
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
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