@[toc]
简介
Class 可以通过关键字extends实现继承。这比ES5通过修改原型链实现继承要清晰、方便。
class Point {
}
class ColorPoint extends Point{
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
子类必须在constructor方法中调用super方法,否则新建实例的时候会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样实例属性和方法,然后再对其进行加工,加上子类自己的属性和方法。如果不调用super,子类就得不到this对象。
ES5 & ES6 继承实质比较
ES5 的继承,实质是先创造子类的实例对象(this),然后将负累的方法添加到this上面(Parent.apply(this))。
ES6 的继承,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后在用子类的构造函数修改this.
注意
- 如果子类没有定义
constructor方法,这个方法会默认被添加,不管有没有显式定义,任何一个子类都有一个constructor方法。
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args){
super(...args);
}
}
- 在子类的构造函数中,只有调用
super之后,才可以使用this关键字,否则会报错。
class Point {
constructor(x,y){
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x,y,color){
this.color = color; // 报错
super();
this.color = color; // OK
}
}
- 父类的静态方法,也会被子类继承
Object.getPrototypeOf()
方法Object.getPrototypeOf()可以从子类上获取父类
Object.getPrototypeOf(ColorPoint) === Point; // true
super 关键字
关键字super,既可以当作函数使用,也可以当作对象使用。
- 当作函数
关键字super作为函数调用时,代表父类的构造函数。子类的构造函数必须执行一次super函数。
class A{}
class B extends A {
constructor() {
super();
}
}
注意:super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。并且作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
- 当作对象
在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A {
p() {
return 2;
}
}
class B extends A {
constructor(){
super();
console.log(super.p()); // 2
}
}
let b = new B();
注意:
- 由于
super指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super调用的。 - 如果属性定义在父类的原型对象上,
super就可以取到, - 在静态方法中,这时
super将指向父类,而不是父类的原型对象。 - 在子类静态方法中,通过
super调用父类方法时,方法内部的this指向当前的子类,而不是子类的实例。 - 使用
super的时候,必须显式指定是作为函数还是作为对象使用,否则会报错。 - 由于对象总是继承其他对象的,所以在任意一个对象中,使用
super关键字。
类的prototype 属性和 _proto_属性
大多数浏览器的ES5 实现中,每一个对象都有_proto_属性,指向对应的构造函数的prototype属性。Class同时具有prototype属性和_proto_属性,因此同时存在两条继承链。
- 子类的
_proto_属性,表示构造函数的继承,总是指向父类 - 子类的
prototype属性的_proto_属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}
class B extends A {
}
B._proto_ === A; // true
B.prototypr._ptoto_ = A.prototype; // true
- 子类继承
Object类
class A extends Object{
}
A._proto_ === Object; // true
A.prototype._proto_ === Object.prototype; // true
在这里A其实就是构造函数Object的复制,A的实例就是Object的实例
- 不存在任何继承
class A {
}
A._proto_ === Function.prototype; // true
A.prototype._proto_ = Object.prototype; // true
在这里,A作为一个基类(不存在任何继承),就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象(Object实例)。所以:A.prototype._proto_指向构造函数(Object)的prototype属性
实例的_proto_属性
子类实例的_proto_属性的_proto_属性,指向父类实例的_proto_属性。子类的原型的原型是父类的原型。
因此,通过子类实例的_proto_._proto_属性,可以修改父类实例行为
原生构造函数的继承
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。
ECMAScript 的原生构造函数大致一下这些:
Boolean()Number()String()Array()Date()Function()RegExp()Error()Object
ES6 允许继承原生构造函数定义子类,因为ES6新建的是父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。
例子:继承Array
class MyArray extends Array{
constuctor(...args){
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length; // 1
arr.length = 0;
arr[0]; // undefined
这里关键字extends不仅可以用来继承类,还可以用来继承原生的构造函数。
***注意:***继承Object的子类,有一个行为差异
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var obj = new NewObj({attr: true});
o.attr === true; // false
在这里,NewObj继承了Object,但是无法通过super方法向父类Object传参。这里因为ES6改变了Object构造函数的行为,一旦发现Object方法不是通过new Object()这种方式调用的,ES6 规定Object构造函数会忽略参数。
Mixin 模式的实现
Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。
- 一个简单的实现
const a = {
a: '1'
}
const b = {
b: '2'
}
const c = {...a, ...b}; // {a: 'a', b: 'b'};
- 更完善的实现
function mix(...mixins) {
class Mix {
constructor() {
for (const mixin of mixins) {
copyProperties(this, new mixin()); // 拷贝实例属性
}
}
}
for (const mixin of mixins) {
copyProperties(this, new mixin()); // 拷贝静态属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
for (const key of Reflect.ownKeys(source)) {
if (key !== 'constructor' && key !== 'prototype' && key !== 'name') {
const desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。