首先我们先看一下最初js实现继承的方式
1.构造函数继承
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.apply(this); // call super constructor.
}
var rect = new Rectangle();
rect.x; // 0
rect.y; // 0
rect.move(1, 1); // rect.move is not a function
特点:只继承了父类构造函数的属性,没有继承父类原型的属性。
2.原型链继承
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
}
// 子类续承父类
Rectangle.prototype = new Shape();
var rect = new Rectangle();
rect.x; // 0
rect.y; // 0
rect.move(1, 1); // Shape moved.
特点:新实例无法向父类构造函数传参。
3.组合继承(常用)
// Shape - 父类(superclass)
function Shape(x = 0, y = 0) {
this.x = x;
this.y = y;
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle(...arg) {
Shape.apply(this, arg); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = new Shape();
var rect = new Rectangle(1, 2);
rect.x; // 1
rect.y; // 2
rect.move(1, 1); // Shape moved.
特点:新实例无法向父类构造函数传参。
看似完美,但实际上存在很多问题的。下面我们打印rect.constructor看看
为了究其原因,我们还是直接打印rect看看吧
rect.__proto__下只有x、y两个属性,根据原型链逐级寻找特性,所以constructor打印的是rect.proto.proto.constructor
即:
rect.__proto__.constructor===rect.__proto__.__proto__.constructor //true
多大点事儿,我给它加上不就是了
// Shape - 父类(superclass)
function Shape(x = 0, y = 0) {
this.x = x;
this.y = y;
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle(...arg) {
Shape.apply(this, arg); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = new Shape();
//构造器指向Rectangle
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle(1, 2);
rect.x; // 1
rect.y; // 2
rect.move(1, 1); // Shape moved.
完美,全剧终
开个玩笑,言归正传。
不知道细心的朋友有没有发现这个?
rect.__proto__.x//0
rect.__proto__.y//0
其实这是因为new Shape()构造了Shape的一个实例,然后我们将Rectangle的原型链指向了Shape的这个实例造成的,我们只需要move方法,而不是x,y这些属性。虽然说我们打印rect.x不会取到rect._proto_上面的x,但终究是原型链上的“累赘”
我们可以换一种写法
原型链直接指向被继承构造函数的原型链
// 子类续承父类
// Rectangle.prototype = new Shape();
Rectangle.prototype = Shape.prototype;
那么两者有何区别呢?别慌,我们还是打印一下rect看看
好家伙,原型链大有不同,move直接变成了直接原型链上的方法,而非第二级Shape上的。用起来都正常,看似简介,但不可取!
利用Object.create改变构造函数的原型链的指向
// 子类续承父类
// Rectangle.prototype = new Shape();
// Rectangle.prototype = Shape.prototype;
Rectangle.prototype = Object.create(Shape.prototype);
完美!
再看一下用class extend实现的
// Shape - 父类(superclass)
class Shape{
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
move(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
}
}
// Rectangle - 子类(subclass)
class Rectangle extends Shape{
}
var rect = new Rectangle(1, 2);
rect.x; // 1
rect.y; // 2
rect.move(1, 1); // Shape moved.
除了constructor类型是class外,其他的都一模一样
下面介绍一下这篇文件,也是这篇文章让我获得的写这篇文章的灵感。
你真的会用Object.create?
巧用Object.create第二个参数
// Shape - 父类(superclass)
function Shape(x = 0, y = 0) {
this.x = x;
this.y = y;
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle(...arg) {
Shape.apply(this, arg); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype, {
// 构造器指向Rectangle
constructor: {
value: Rectangle
}
});
var rect = new Rectangle(1, 2);
rect.x; // 1
rect.y; // 2
rect.move(1, 1); // Shape moved.