class中this的指向

最近在學react的時候,因爲react都是用class來構建組件,對class中this的指向有一點迷惑。現在做些總結。

this的綁定優先級

this的綁定一共有四種弄方式,優先級逐級遞減。
this的本質就是:this跟作用域無關的,只跟執行上下文有關。

1、new創建出來的實例去調用,this指向當前實例

eg.

// 實例上的綁定
let cat = new Cat()
cat.speak()

2、顯示綁定

使用call,apply,或者bind

function jump() {
  console.log(this.name)
}
const obj = {
  name: '米粒',
  jump
}

jump = jump.bind(obj)

jump() // 米粒

3、對象中的方法綁定

function jump() {
  console.log(this.name)
}
let obj = {
  name: '米粒',
  jump
}

obj.jump() // 米粒

4、默認綁定

在嚴格模式下,this是undefined,否則是全局對象

Class中屬性與方法的綁定

class Cat {
    constructor(name, age) {
        // name做綁定 age未綁定
        this.name = name
    }
    jump() {
        console.log('jump',this)
    }
    // 可以直接調用 靜態方法通常用於爲一個應用程序創建工具函數。
    static go() { 
        console.log(this)
    }
}
// 可以直接調用 與使用static差不多
Cat.drink = function() {
    console.log('drink',this)
}

Cat.prototype.eat = function() {
    console.log('eat',this)
}
// 使用箭頭函數綁定
Cat.prototype.walk = () => {
    console.log('walk',this)
}

let cat = new Cat('cat中的米粒', '5個月')

打印出cat與Cat的原型對象
image.png

可以看到,Cat所創建出來的實例,其方法掛載在實例的__proto__上面,即掛載在原型對象上。因爲cat.__proto__與Cat.prototype指向同一個對象,所以當在cat.__proto__上掛載或者覆蓋其原有方法時,所有由Cat所創建出來的實例,都將會共享該方法,所有實例都是通過__proto__屬性產生的原型鏈到原型對象上尋找方法。

但是靜態方法不會共享給實例,因爲沒有掛載在原型對象上面。

而屬性是掛載在實例上的,即每一個創建出來的實例,都擁有自己的不同值的屬性。

class中this的綁定

前景提要:當我們打印typeof Cat可知是函數類型,ES6中的class類其實只是個語法糖,皆可以用ES5來實現。由構造函數Cat創建的實例cat是一個對象。在初始化cat實例的時候,在constructor中就會把this上的屬性掛載到實例對象上面。

另外牢記,this的指向與作用域無關,與調用執行函數時的上下文相關。
示例一:

class Cat {
    constructor(name, age) {
        this.name = name
    }
    run() {
        console.log('run',this)
    }
}
let cat = new Cat('米粒', '5個月')
cat.name // '米粒'
cat.run() // run Cat {name: "米粒"}

當調用cat.run()的時候,當前上下文是cat,所以其this指向的是cat這個實例。
示例二:

class Cat {
    constructor(name, age) {
        this.name = name
        this.jump = this.jump.bind(this)
        this.drink = () => {
            console.log('drink',this)
        }
    }
    run() {
        console.log('run',this)
    }
    jump() {
      console.log('jump',this)
    }
    static go() { 
        console.log('go',this)
    }     
}

Cat.prototype.walk = () => {
    console.log('walk',this)
}

let cat = new Cat('米粒', '5個月')
let run = cat.run
let jump = cat.jump
let go = Cat.go
let walk = cat.walk
let drink = cat.drink


run() // run undefined
jump()  // jump Cat {name: "米粒", jump: ƒ}
Cat.go() // go class Cat {}
go() // go undefined
cat.walk() // walk Window
walk() // walk Window
cat.drink() // drink Cat {name: "米粒", jump: ƒ, drink: ƒ}
drink() // drink Cat {name: "米粒", jump: ƒ, drink: ƒ}

先看run方法:當把實例中的方法賦值給一個變量,但是隻是賦予了方法的引用,所以當變量在執行方法的時候,其實改變了方法的執行時的上下文。原來執行的上下文是實例cat,後來賦值之後再執行,上下文就變成了全局,this默認綁定。class中使用的是嚴格模式,在該模式下,全局的this默認綁定的是undefined,不是在嚴格模式下的時候,若在瀏覽器中執行,則this默認綁定window。

jump方法:因爲在構造函數執行的時候,顯式綁定了jump執行的上下文——cat實例。由文章開頭this綁定的優先級可知,顯式綁定>默認綁定。所以jump的執行上下文依然是cat實例

go方法:go方法使用靜態方法定義,無法共享個實例cat,只能在構造函數Cat上直接調用。

walk與drink方法:這兩個方法是用箭頭函數定義的。箭頭函數的this是在定義函數時綁定的,不是在執行過程中綁定的。簡單的說,函數在定義時,this就繼承了定義函數的對象。

所以walk是在Cat.prototype.walk時定義的,此時的this指向是window。無論之後賦值給哪個變量,也只是函數的引用,所以其this還是window。

同理,drink在定義的時候,this指向的是該構造函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章