在 ES6
中的 Class
其实可以看作是一个语法糖,它的绝大部分功能,ES5
都可以做到,新的 Class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,比如下面这个例子
1 | // ES6 写法 |
我们可以看到 ES5
的构造函数 Person
,对应 ES6
的 Person
类的 constructor
方法,不过需要注意的是,『Class
的内部所有定义的方法,都是不可枚举的(non-enumerable
)』,比如在上面的例子当中
1 | // ES6 当中 |
静态方法
静态方法一般用来提供一些工具方法,所有在类中定义的方法,都会被实例继承,如果在一个方法前,加上 static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为『静态方法』
1 | // ES6 中 |
静态属性
Class
内部『只有静态方法,没有静态属性』,静态属性指的是 Class
本身的属性,即 Class.propName
,而不是定义在实例对象(this
)上的属性,以前,我们添加静态属性只可以这样
1 | // ES6 中 |
然而现在有一个提案,对实例属性和静态属性都规定了新的写法,而且 Babel
已经支持,所以现在我们可以写成
1 | // 暂未统一实现 |
对应到 ES5
就是
1 | function Person() {} |
私有方法
ES6
『不提供』私有方法,只能通过变通方法模拟实现,一种做法是在命名上加以区别
1 | class Widget { |
另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的
1 | class Widget { |
有一种方法是利用 Symbol
值的唯一性,将私有方法的名字命名为一个 Symbol
值
1 | const bar = Symbol('bar') |
私有属性
与私有方法一样,ES6
『不支持』私有属性,但是可以通过 WeakMap
来实现私有属性
1 | const privateData = new WeakMap() |
getters & setters
与 ES5
一样,在 Class
的内部可以使用 get
和 set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
1 | class People { |
因为定义了 name
的读写器,而没有定义 _name
的读写器,所以访问这两个属性的结果是不同的
继承
我们先来看看 ES5
当中比较推荐的继承方法,寄生组合式继承
1 | function Parent(name) { |
这种方式的继承只调用了一次 Parent
构造函数,并且可以避免在 Parent.prototype
上面创建不必要的、多余的属性,与此同时,原型链还能保持不变,所以还能够正常使用 instanceof
和 isPrototypeOf
下面我们再来看看 ES6
当中的继承,Class
通过关键字 extends
来继承一个类,并且可以通过 super
关键字来引用父类,这比 ES5
的通过修改原型链实现继承,要清晰和方便很多
1 | class Parent { |
这里有几个需要注意的地方
super
关键字表示父类的构造函数,相当于ES5
的Parent.call(this)
- 子类必须显式的在
constructor
方法中调用super
方法,否则新建实例时会报错,这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工,如果不调用super
方法,子类就得不到this
对象 - 如果子类没有定义
constructor
方法,这个方法会被默认添加,也就是说不管有没有显式定义,任何一个子类都有constructor
方法 - 在子类的构造函数中,只有调用
super
之后,才可以使用this
关键字,否则会报错(因为只有super
方法才能返回父类实例) Object.getPrototypeOf
方法可以用来从子类上获取父类(判断一个类是否继承了另一个类)
子类的 __proto__
在 ES6
中,父类的静态方法,可以被子类继承,比如下面这个例子
1 | class Foo { |
这是因为 Class
作为构造函数的语法糖,同时有 prototype
属性和 __proto__
属性,因此同时存在两条继承链
- 子类的
__proto__
属性,表示构造函数的继承,总是指向父类 - 子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性
即
1 | class Parent { } |
细心观察可以发现,相比寄生组合式继承,ES6
的 Class
多了一个 Object.setPrototypeOf(Child, Parent)
的步骤
继承目标
extends
关键字后面可以跟多种类型的值
1 | class B extends A { } |
上面代码的 A
,只要是一个有 prototype
属性的函数,就能被 B
继承,由于函数都有 prototype
属性(一般情况下,排除一些箭头函数或者 bind(null)
之类的操作),因此 A
可以是任意函数,除了函数之外,A
的值还可以是 null
,当 extend null
的时候
1 | class A extends null { } |
super 关键字
- 使用
super
的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错 super
作为函数调用时super
作为函数调用时,代表父类的构造函数(子类的构造函数必须执行一次super
函数)- 虽然代表了父类的构造函数,但是
super
内部的this
指的是子类的实例(相当于Father.prototype.constructor.call(this)
) - 作为函数时,
super()
只能用在『子类』的构造函数之中,用在其他地方就会报错
super
作为对象调用时- 在普通方法中,指向父类的原型对象,在静态方法中,指向父类(与父类静态方法相呼应)
- 当
super
指向父类的原型对象时,定义在父类实例上的方法或属性,是无法通过super
调用的(定义在prototype
上的则可以取到) - 通过
super
调用父类的方法时,super
会绑定子类的this
- 通过
super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性,如下代码
1 | class A { |
总结
Class
的内部所有定义的方法,都是不可枚举的- 类和模块的内部,默认就是严格模式,所以不需要使用
use strict
指定运行模式 - 一个类必须有
constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加 - 不存在变量提升(
hoist
) - 类的方法内部如果含有
this
,它默认指向类的实例 class
中的方法有三种类型:构造函数、静态方法、原型方法class
内部只有静态方法,没有静态属性