一文读懂原型链继承

141 阅读3分钟

首先我们先看一下最初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看看

image.png

为了究其原因,我们还是直接打印rect看看吧

image.png

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.

image.png

完美,全剧终



开个玩笑,言归正传。

不知道细心的朋友有没有发现这个?

image.png

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看看

image.png

好家伙,原型链大有不同,move直接变成了直接原型链上的方法,而非第二级Shape上的。用起来都正常,看似简介,但不可取!

利用Object.create改变构造函数的原型链的指向

// 子类续承父类
// Rectangle.prototype = new Shape();
// Rectangle.prototype = Shape.prototype;
Rectangle.prototype = Object.create(Shape.prototype);

image.png

完美!

再看一下用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.

image.png

除了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.