likes
comments
collection
share

这次,彻底弄懂 ES6 的 Class

作者站长头像
站长
· 阅读数 1

Class 声明

可以使用类声明和类表达式来定义类。

这次,彻底弄懂 ES6 的 Class

类声明中一般会在构造函数 constructor 中添加属性:

这次,彻底弄懂 ES6 的 Class

类声明属性还有另一种方式:

这次,彻底弄懂 ES6 的 Class

然后就是声明类的方法:

这次,彻底弄懂 ES6 的 Class

也可以声明getter/setter:

这次,彻底弄懂 ES6 的 Class

还有声明类的静态方法、私有属性、私有方法,这里就不作说明。

Class 是语法糖

之所以说 Class 是语法糖,是因为 Class 的本质是一个函数。以下类的声明:

这次,彻底弄懂 ES6 的 Class

等价于:

这次,彻底弄懂 ES6 的 Class

new Class 其实就是传统的 new Function 方式声明对象的语法糖。

Class 创建的函数和普通函数还是有一些区别:

Class 创建的函数必须要用new调用它,直接调用会报错。

这次,彻底弄懂 ES6 的 Class

Class 创建的函数内部会添加个内部属性标记 [[IsClassConstructor]]: true,通过检查该属性来进行控制。

声明的类方法不可枚举,类定义将 prototype 中的所有方法的 enumerable 标志设置为 false

构造函数中的代码是严格模式。

new Class 执行过程

  • 创建一个空的简单 JavaScript 对象
  • 创建的空对象的 [[Prototype]] 指向Class创建的函数的原型对象,类的方法会放入到原型中。
  • 执行构造函数,并将创建的空对象绑定为 this 的上下文,给 this 对象添加属性。
  • 返回这个新的对象

this 问题

对象的方法传递到其他地方执行,this 将不再指向其对象。

这次,彻底弄懂 ES6 的 Class

解决方法有 bind 和传递一个包装函数外还有一种类字段声明的方法。

这次,彻底弄懂 ES6 的 Class

通过箭头函数可以让 this 始终指向 cat 对象。

Class 继承

ES6 中可以用 extends 实现继承:

这次,彻底弄懂 ES6 的 Class

其实相当于原型继承:

这次,彻底弄懂 ES6 的 Class

extends 继承就是将 Cat 原型对象的原型设置为 Animal 的原型现成原型链,而 Cathide 方法添加到 Cat 原型中。

Class Cat 代码中没有声明 constructor,没有 constructor 时会自动生成以下constructor

这次,彻底弄懂 ES6 的 Class

supper 的执行必须要在使用 this 之前,如果不使用 supper 执行就使用 this 会报错。

这次,彻底弄懂 ES6 的 Class

thisundefined,也就是说执行 supper后,this 才会被赋值一个对象。

其实继承的子类在 new 操作上和普通的函数 new 操作有一定的区别。

普通函数 new 操作:

  1. 创建一个空对象,这个对象的原型 [[Prototype]] 属性设置为该函数的原型对象。
  2. 将这个对象赋值给 this
  3. 然后再执行函数里的代码,给 this 添加属性。
  4. 最后返回这个对象。

而继承子类的 new 操作是:

  1. 不会创建一个空对象赋值给 this
  2. 通过 supper 执行父类的 constructor 时创建一个对象,将父类的属性添加到这个对象中,并将设置这对象的原型链。
  3. this 赋值这个对象。
  4. 返回这个对象。

因此不执行 supper 对象就创建不了,this 也就是 undefined,给 this 添加属性时就提示报错了。

super 还可以用来调用父类方法,super.method(...) 。

检查类型

typeof

这次,彻底弄懂 ES6 的 Class

可以检查基本数据类型和 function,但是有个例外,null 会返回 object, 因为在 JavaScript 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object

instanceof

这次,彻底弄懂 ES6 的 Class

能检测出引用类型,缺点:不能检测出基本类型,且不能跨 iframe。

instanceof Class 的大致过程如下:

Class 是否有静态方法 Symbol.hasInstance,有的话直接调用这个方法。

Symbol.hasInstance 用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。

这个静态方法在内置对象中是只读不可枚举的,但可以配置,大多数内置对象是没有这个静态方法,这一般用于自定类中修改instanceof 操作符的行为。

没有这个静态方法的话,就检查 Class.prototype 是否等于 obj 的原型链中的原型之一。

这次,彻底弄懂 ES6 的 Class

Object.prototype.toString.call

这次,彻底弄懂 ES6 的 Class

检测出值类型和引用类型。但是 Object.prototype.toString 的行为是可以通过 Symbol.toStringTag 来自定义的。

Object.prototype.toString() 返回 [object Type],这里的 Type 是对象的类型。如果对象有 Symbol.toStringTag 属性,其值是一个字符串,则它的值将被用作 Type

这次,彻底弄懂 ES6 的 Class

ES6 之前早期的内置对象是没有这个属性的,Object.prototype.toString() 会返回的是一个特殊的标签。

ES6 之后的内置对象一般都有会这个属性,并且这个属性是只读不可以枚举的,但可以配置,因此一般常常用于自定义类中定义。

那么在检查类型的时候希望考虑防止自定义类修改 Object.prototype.toString() 的行为,可以这么判断:

这次,彻底弄懂 ES6 的 Class

代码中先将 Symbol.toStringTag 设置为undefined,然后再 Object.prototype.toString.call() 检测类型,如果 Symbol.toStringTag 是对象自身属性,将其还原,如果是其原型的属性,将添加的 Symbol.toStringTag 自身属性删除。

Class 缺点

ES6 Class也存在一定的问题,以导致 react 放弃Class 组件声明而用函数组件和 hook 方式替代,那ES6 Class 有什么缺点呢?

this 丢失问题

使用 Class 必然会使得 this 的使用率增多,自然this 丢失的问题也就经常需要处理。

语法糖

由于 ES6 Class 只是一个语法糖,使用者需要理解 JavaScript 的 new function 和原型的原理才能更好使用 Class,并且缺乏抽象类和接口的支持,继承和代码复用实现更加复杂。JavaScript 可以字面量声明对象的机制使得 Class 使用变得复杂化。

因此代码复用更简洁且更易维护的函数式编程慢慢被各大框架所喜爱。