乐趣区

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 的原型对象

可以看到,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 指向的是该构造函数。

退出移动版