有關this

thisJavascript函數內部的一個特殊對象,引用的是函數運行時的環境對象,也就是說,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方法綁定調用對象

函數可使用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

上述代碼中將對象的方法賦值給新變量aliasalias函數執行時,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變量的,因此沒法訪問到外部函數saythis變量,咱們能夠將外部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)

箭頭函數內部是沒有thisargumentssupernew.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
相關文章
相關標籤/搜索