class(二)--派生类的继承
前言
从我之前的一篇笔记对象的继承中, 我们可以知道JS的继承方式依赖原型链,而比较好的继承方式是寄生组合式继承
先来温习下什么是寄生组合式继承
function Rectangle(length, width) {
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function() {
return this.length * this.width;
};
function Square(length) {
Rectangle.call(this, length, length);
// 1 调用父类构造函数
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value:Square,
enumerable: true,
writable: true,
configurable: true
}
});
// 2 Object.create会在内部创建一个空对象来连接两个原型对象, 再手动将constructor指向自身
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
- 在子构造函数中调用父级构造函数,来将属性定义在自己身上
- 原型继承主要是为了继承原型对象上的方法
这就是在ES6
之前的继承方式,对原型链理解不够深透的话很容易混乱
但在ES6
推出class
这个类之后,一切都变得简单(再次提醒:class
只是语法糖,实质就是对寄生组合式继承
的封装
派生类进行继承
首先我们将继承其他类的的类称为派生类(derived classes)
, 再来看看上面的例子, 利用class
如何轻松实现
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
// 与 Rectangle.call(this, length, length) 相同
super(length, length);
}
}
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
console.log(Square.prototype.constructor === Square) // true
实现继承只需要两步
class
上extends
继承父类- 在构造函数中调用
super()
super
也是ES6
中的内容,它是指向当前对象的原型
的一个指针,实际上就是Object.getPrototypeOf(this)
的值。
根据上面的例子我们来看下
Object.getPrototypeOf(Square) === Rectangle // true
那super(length, lenght)
应该是Rectangle(lenght, length)
才对啊,其实在ES6
中真正出现了方法
的概念,从前只能说是一个函数属性, 在方法中会有一个内部属性[[HomeObject]]
任何对super
的引用都会使用[[HomeObject]]
属性来判断要做什么。第一步是在[[HomeObject]]
上调用Object.getPrototypeOf()
来获取对原型的引用;最后,创建this绑定并调用该方法的对象
。因此是Rectangle.call(this, length, length)
注意点:
- 使用了
extends
就必须在构造器中调用super()
super
必须要在this
前面- 要想不用
super()
:
- 不用构造器, 它会默认创建,并按照顺序传入所有参数
return
返回一个对象,不能是number
等基本数据类型,否则return
的是this
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
// 不传super()
class Square extends Rectangle {
constructor(length) {
}
}
let s1 = new Square(3) //Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorat new Square
// 报错 必须在访问this前面调用super构造器或者在返回一个新的Square实例对象必须调用super构造器
// 直接不传入构造器
class Square extends Rectangle {}
let s2 = new Square(3, 4)
console.log(s2.getArea()) //12
console.log(s2.width) // 4
console.log(s2.length) // 3
// 其实默认调用了构造器
//class Square extends Rectangle {
// constructor(...args) {
// super(...args)
// }
//}
// 返回一个对象
class Square extends Rectangle {
constructor(length) {
return {}
}
}
let s2 = new Square()
console.log(s2 instanceof Square) // false
继承静态成员
其实从之前的super
就能看出个大概了
Object.getPrototypeOf(Square) === Rectangle // true
因此Rectangle
的静态成员自然可以被Square
访问到
Rectangle.create = function(l, w) {
return new Rectangle(l, w)
}
console.log('create' in Square) // true
console.log(Square.hasOwnProperty('create')) // false
继承非类
其实只要一个表达式能够返回一个具有[[Construct]]
属性的以及原型
的函数,就可以对其实使用extends
一般来说,对象的原型
会在通过构造器
或Object.create()
方法创建该对象时被指定。而[[Construct]]
只有箭头函数没有
因此一下几种情况都是可以的
function Parent() {}
class Child extends Parent {
}
console.log(Child.prototype.__proto__ === Parent.prototype) // true
console.log(Child.__proto__ === Parent) // true
let SerializableMixin = {
serialize() {
return JSON.stringify(this);
}
};
let AreaMixin = {
getArea() {
return this.length * this.width;
}
};
function mixin(...mixins) {
var base = function() {};
Object.assign(base.prototype, ...mixins);
return base;
}
class Square extends mixin(AreaMixin, SerializableMixin) {
constructor(length) {
super();
this.length = length;
this.width = length;
}
}
var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.serialize()); // "{"length":3,"width":3}"