JS 创建实例对象的方法是通过构造函数的方式,与传统编程语言差异很大,不容易理解,所以引入了 ClassES6
class 是 构造函数 和 原型 的语法糖
语法
class 是一个函数:
typeof MyClass // 'function'
以上代码等同于:
constructor 中的代码相当于函数内的代码(即对象实例上的属性),其他方法相当于原型上的
constructor 函数如果返回了对象,则这个对象就是生成的类实例对象 (类似 构造函数)
class 里也有 new.target
但是还是有一些不同:
- 通过 class 创建的函数具有特殊的内部属性标记
[[IsClassConstructor]]: true
,所以只能通过 new 调用 - 类方法不可枚举
- 类中自动开启严格模式
特性
有函数的特性:类可以作为变量,也有类表达式
也有对象的特性:可以设置 getter/setter,可以使用计算属性
类字段ES2022
实例属性现在除了可以定义在 constructor() 方法里面的 this 上面,也可以定义在最外层
类字段在每个独立对象中设置,而不是像类方法那样在 MyClass.prototype 上
静态属性ES2022
在类字段前面加上 static 关键字
作用:属于整个类,但不属于某个具体对象的属性
- 通过类名来调用:
MyClass.name
- 可以被继承(但是是浅拷贝继承)
静态方法ES2022
在函数前面加上 static 关键字
作用:属于整个类,但不属于某个具体对象的方法
- 通过类名来调用:
MyClass.f()
- 静态方法的 this 指的是类,而不是实例
- 静态方法可与非静态方法重名
- 可以被继承
静态块ES2022
允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化
受保护属性和方法
通常以下划线 _
作为前缀,这是约定,并没有在语法层面限制
然后使用 get/set 来访问
私有属性和方法ES2022
- 以
#
作为前缀,这是语法层面的限制 - 只能在类的内部被访问,外部访问会报错
- 前面可以加 static,表示静态私有属性或方法
- 不能通过中括号的形式
this[propName]
访问 - this 指向和公共方法一样
- 不能被继承
相当于传统的闭包:
this
类的方法内部如果含有 this,它默认指向类的实例。
但是,如果将这个方法提取出来单独使用,根据 this 绑定规则,所以 this 实际指向的是 undefined
解决方法:
1.在构造方法中绑定 this
2.箭头函数:
继承
一个类扩展另一个类的一种方式,可以在现有功能之上创建新功能,extends 后面可以跟构造函数(包括类)或 null 的表达式
extends
- extends 将 子类 的 prototype 的
__proto__
指向 父类 的 prototype,实现了方法的继承,即:SubClass.prototype.__proto__ = MyClass.prototype
- 同时将 子类 的
__proto__
指向 父类,实现了静态属性和方法的继承,即:SubClass.__proto__ = MyClass
在 extends 后面可以接任意表达式,如一个函数调用的结果作为父类
constructor
如果子类没有 constructor,则会默认生成下面的(调用父类的构造函数并传递所有参数)
但是如果写了自己的 constructor,则必须在 constructor 里调用 super(…),并且一定要在使用 this 之前调用 为什么?
继承类(derived constructor)的构造函数与其他函数之间有区别,它具有特殊内部属性
[[ConstructorKind]]:"derived"
,影响了它的 new 行为:
- 当通过 new 执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给 this
- 但是当继承的 constructor 执行时,它不会执行此操作。它期望父类的 constructor 来完成这项工作。因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用 super() 方法,子类就得不到自己的 this 对象。所以在执行 super 之前,this 指向的对象还没有创建,会报错
重写方法
子类可以重写父类的同名方法
super
子类可以重写父类的方法,但有时候又不希望完全重写,而是在父类的方法上面进行扩展
super 用法:
- 作为对象调用(在静态方法中,super 表示父类。在其他方法中,super 表示父类的原型对象):
super.method(...)
或super.prop
- 在子类普通方法中通过 super 调用父类的方法时,父类方法内部的 this 指向当前的子类实例
- 在子类的静态方法中通过 super 调用父类的方法时,父类方法内部的 this 指向当前的子类,而不是子类的实例
- 上面是读操作的规则。如果对 super 进行写操作(如
super.x = 1
),则 super 表示子类实例
- 作为函数调用(代表父类的构造函数)
super(...)
:调用一个父类 constructor(只能在子类的 constructor 中)
Warning
箭头函数没有 super
重写类字段
子类中可以重写父类的类字段,但是父类在其 constructor 中访问同名的类字段时还是会使用父类自己的类字段,而不是子类的类字段 因为类字段是这样初始化的:
- 对于基类,在构造函数调用前初始化
- 对于派生类,在 super() 之后立刻初始化 在父类构造器被执行的时候,子类还没有自己的类字段
NOTE
这种行为仅在一个被重写的字段被父类构造器使用时才会显现出来(这种字段与方法之间微妙的区别只特定于 JavaScript)
可以通过使用方法或者 getter/setter 替代类字段,来修复这个问题
HomeObject
super 如何获取到父类原型的方法并使用自己的 this?super 的原理是?
简单尝试:
使用 this.__proto__.method()
获取父 method(为了简化使用对象)
貌似可行,继续多次继承:
代码无法再运行了:因为 (1) 和 (2) 的 this 都是 longEar,在 (2) 处形成了死循环。
所以,对象的原型不能用 this 来查找,那怎么在子类的方法中获取父类的原型呢?最简单直接的方式就是把它存起来。
为了解决这个问题,JS 为函数增加了一个特殊的内部属性:[[HomeObject]]
当一个函数被定义为《类方法》或者《对象方法》时,它的 [[HomeObject]]
属性就成为了该对象
[[HomeObject]]
不能被更改,这个绑定是永久的
然后 super 可以使用它来解析父原型及其方法
多重继承
JS 不支持多重继承,但是可以通过将方法拷贝到类的 prototype 中来实现 mixin
对比
class 只是语法糖,相对于传统构造函数进行对比:
class | 构造函数 |
---|---|
constructor | 构造函数本身 |
类方法 | 原型上的方法 |
类字段 | 构造函数中的属性初始化 |
静态属性/方法 | 构造函数本身添加的属性/方法 |
私有属性/方法 | 闭包 |
extends | 原型继承 |
super | call 或 apply |