JavaScript———继承

原型链继承

// 父类
function SuperType (colors = ['red', 'blue', 'green']) {
    this.colors = colors;
}

// 子类
function SubType () {}
// 继承父类
SubType.prototype = new SuperType();
// 以这种方式将 constructor 属性指回 SubType 会改变 constructor 为可遍历属性
SubType.prototype.constructor = SubType;

let superInstance1 = new SuperType(['yellow', 'pink']);
let subInstance1 = new SubType();
let subInstance2 = new SubType();
superInstance1.colors; // => ['yellow', 'pink']
subInstance1.colors; // => ['red', 'blue', 'green']
subInstance2.colors; // => ['red', 'blue', 'green']
subInstance1.colors.push('black');
subInstance1.colors; // => ['red', 'blue', 'green', 'black']
subInstance2.colors; // => ['red', 'blue', 'green', 'black']

上述代码使用了最基本的原型链继承使得子类能够继承父类的属性,**原型继承的关键步骤就在于:将子类原型和父类原型关联起来,使原型链能够衔接上,**这边是直接将子类原型指向了父类实例来完成关联。

上述是原型继承的一种最初始的状态,我们分析上面代码,会发现还是会有问题:

  • 在创建子类实例的时候,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的
    构造函数传递参数。
  • 这样创建的子类原型会包含父类的实例属性,造成引用类型属性同步修改的问题。

借用构造函数

// 借用构造函数

function SuperType(name) {
    this.name = name;
}

function SubType() {
    //继承了 SuperType,同时还传递了参数
    SuperType.call(this, "Nicholas");
    //实例属性
    this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了
而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式

组合继承

组合继承通过组合借用构造函数原型链继承来实现继承;

其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性

// 组合继承实现

function Parent(value) {
    this.value = value;
}

Parent.prototype.getValue = function() {
    console.log(this.value);
}

function Child(value) {
    Parent.call(this, value)
}

Child.prototype = new Parent();

const child = new Child(1)
child.getValue();
child instanceof Parent;

然而它还是存在问题:父类的构造函数被调用了两次(创建子类原型时调用了一次,创建子类实例时又调用了一次),导致子类原型上会存在父类实例属性,浪费内存。

原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

// 原型式继承

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

从本质上讲, object()对传入其中的对象执行了一次浅复制

**ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。**这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

在传入一个参数的情况下,Object.create()与 object()方法的行为相同。

Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。

在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型链继承一样。

寄生式继承

寄生式( parasitic)继承是与原型式继承紧密相关的一种思路;创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

// 寄生式继承

function createAnother(original) {
    var clone = object(original); //通过调用函数创建一个新对象
    clone.sayHi = function() { //以某种方式来增强这个对象
        alert("hi");
    };
    return clone; //返回这个对象
}

object()函数不是必需的;任何能够返回新对象的函数都适用于此模式。

问题:

使用寄生继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。

寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

// 寄生组合继承实现

function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //复制原型对象
prototype.constructor = subType; //修改复制的原型对象的constructor属性,指向子类
subType.prototype = prototype; // 将原型对象复制给子类的原型
}

function Parent(value) {
    this.value = value;
}

Parent.prototype.getValue = function() {
    console.log(this.value);
}

function Child(value) {
    Parent.call(this, value)
}

inheritPrototype(Child,Parent)

// 或者通过 Object.create()
// Child.prototype = Object.create(Parent.prototype, {
//     constructor: {
//         value: Child,
//         enumerable: false, // 不可枚举该属性
//         writable: true, // 可改写该属性
//         configurable: true // 可用 delete 删除该属性
//     }
// })

const child = new Child(1)
child.getValue();
child instanceof Parent;

寄生组合继承的模式是现在业内公认的比较可靠的 JS 继承模式,ES6 的 class 继承在 babel 转义后,底层也是使用的寄生组合继承的方式实现的。

继承关系判断

instanceof

我们可以使用 instanceof 来判断二者间是否有继承关系,instanceof 的字面意思就是:xx 是否为 xxx 的实例。如果是则返回 true 否则返回 false:

function Parent () {}
function Child () {}
Child.prototype = new Parent();
let parent = new Parent();
let child = new Child();

parent instanceof Parent; // => true
child instanceof Child; // => true
child instanceof Parent; // => true
child instanceof Object; // => true

instanceof 本质上是通过原型链查找来判断继承关系的,因此只能用来判断引用类型,对基本类型无效

Object.prototype.isPrototypeOf(obj)

还可以利用 Object.prototype.isPrototypeOf 来间接判断继承关系,该方法用于判断一个对象是否存在于另一个对象的原型链上:


function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
posted @ 2022-10-23 20:00  CD、小月  阅读(42)  评论(0编辑  收藏  举报  来源