一直以来,JavaScript 都没有类的概念。如果我们想要是创建一个类,通常是通过 构造函数 来实现。

  1. // ES 5
  2. // 添加实例属性
  3. function Animal(name,type) {
  4. this.name = name;
  5. this.type = type;
  6. this.owner = 'xiaoming'
  7. }
  8. // 添加实例共享的方法
  9. Animal.prototype.sayName = function() {
  10. console.log(this.name);
  11. }
  12. // 生成实例
  13. const huahua = new Animal('huahua', 'cat')
  14. huahua.sayName(); // 'hauhua'

我们在构造函数 Animal 中定义实例的属性,并通过 Animal.prototype 对象为实例添加公共的方法。

而 ES6 将这种写法进行了封装,实现了另一种写法—— Class —— 来构造类,与之前相比,写法更为简洁,也更符合类的概念。同时,React 也是通过 class 类来声明组件,所以,搞懂它,十分有必要。

如何用 Class 语法,实现上面的 Animal ?

  1. class Animal {
  2. constructor(name, type){
  3. this.name = name;
  4. this.type = type;
  5. }
  6. sayName() {
  7. console.log(this.name)
  8. }
  9. }
  10. const xiaoqiang = new Animal('xiaoqiang', 'insect')
  11. xiaoqiang.sayName(); // 'xiaoqiang'

这种写法带来的简洁性,我们现在已经可以看到。接下来看一看,这种方式,与 ES5 的写法还有哪些不一致?

1. 构造函数

我们可以看到,在 Class 类中,存在一个 constructor 方法,我们在这个方法内做的事情,和使用 ES5 中的构造函数 Animal 一致,那这个方法,是不是就是类的构造函数呢?

  1. Animal.constructor === xiaoqiang.__proto__.constructor; // false
  2. Animal === xiaoqiang.__proto__.constructor; // true

事实证明,类 Animal 才是真正的构造函数,而 constructor, 只是 Animal 内部,实现构造的一个方法。

在 Class 中,我们通过 constructor 方法接收参数,并进行实例属性的定义。

如果实例本身的属性值不需要根据参数设置,我们可以直接在 Class 的顶部设置实例的属性。

  1. class Toy{
  2. owner = 'xiaoming';
  3. friend = 'hh';
  4. }
  5. const some = new Toy();
  6. some.owner; // 'xiaoming'
  7. some.friend; // 'hh'

2. constructor

上面我们讲到, constructor 是 Class 类内部用来实现构造的一个方法。当我们执行 new Animal() 时,会执行 constructor , 如果我们定义类时没有定义 constructor 时,Class 类会默认定义它。

就像上面的例子 Toy。

  1. console.log(Toy.constructor); // Function() { [native code] }

3. 公共方法的定义

ES5 中,我们将公共的方法直接定义在原型对象上。比如在第一个例子中,我们通过 Animal.prototype.sayName = function() {} 的方式添加公共方法。

而在 Class 中,添加在 consturctor 方法之外函数,都是公共的方法,本质上,这些方法最终也还是添加到了实例的原型对象上,此时,或许就能更加理解 Class 是一个语法糖这种说法了。

  1. class Animal {
  2. // 通过 class 添加方法
  3. say() {
  4. console.log('111111')
  5. }
  6. run() {
  7. console.log('222222')
  8. }
  9. }
  10. // 通过原型对象添加方法
  11. Animal.prototype.cry= function() {
  12. consol.log('33333333')
  13. }
  14. // 无论通过哪种方式,方法都是添加在了原型对象上
  15. Animal.prototype; // {cry: ƒ, constructor: ƒ, say: ƒ, run: ƒ}

4. this 的指向

了解 React 的人一定见过这样的代码:

  1. class Root extends Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. name: 'wowo'
  6. };
  7. }
  8. sayName() {
  9. console.log(this.state.name)
  10. }
  11. render() {
  12. return <div onClick={this.sayName.bind(this)}>lalalal </div>
  13. }
  14. }

我们在最后的 render 方法中返回的组件中绑定方法时,需要将 this 值重新绑定,否则,你会看到:

class.png

什么意思呢?就是 this 此时变成了 undefined 。为啥来?说来话长,我就长话短说吧!

首先,JavaScript 普通函数中 this 的值,是在这个函数被调用时才确定,而 react 组件实例是通过 JSX 渲染的,经过渲染之后,调用函数时 this 值早已经不是实例自己了,而是指向了全局环境。

第二,既然指向了全局,this 也应该是 window 啊,这是因为在 class 和模块内部 ,默认使用严格模式。

当然,解决办法也有很多,我就不重复了。

我们接着来看 Class 类中普通的函数,我们在调用的时候也要绑定 this 吗?

  1. class Animal {
  2. say() {
  3. console.log('111111')
  4. }
  5. run() {
  6. this.say();
  7. console.log('222222')
  8. }
  9. }
  10. const cat = new Animal();
  11. cat.say(); // 11111 22222
  12. const fun = cat.say;
  13. fun(); // TypeError: Cannot read property 'say' of undefined

我们可以看到,当我们以 cat.say() 的方式调用时,this 值仍绑定在实例 cat 上,而当我们将这个方法赋值给新的变量 fun 后,this 值就丢失了,react 中 this 的丢失,本质上就像是这样。

当然,除了这些,还有私有变量和私有属性,这个两个问题在 Class 中仍旧没有得到解决。依旧要依靠别的方式解决。

参考

ES6 标准指南——阮一峰