原型链实现继承的几种模式

本文探讨了JavaScript中实现继承的几种原型链模式,包括借用构造函数、组合继承、原型式继承及其优化——寄生组合式继承。每种模式都有其优缺点,如构造函数不能复用、两次调用超类型构造函数等问题。寄生组合式继承被认为是最佳实践,因为它只调用一次超类型构造函数并避免了多余的属性创建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原型链很强大,可以用来实现继承,但也存在一些问题。其中最主要的问题是来自包含引用类型值的原型。看如下例子:

function Super() {
    this.colors = ['red', 'blue', 'green'];
}
function Sub() {
}
Sub.prototype = new Super();
var instance1 = new Sub();
instance1.colors.push('black');
alert(instance1.colors); //red,blue,green,black

var instance2 = new Sub();
alert(instance2.colors); //red,blue,green,black

我们看到Sub()的两个实例,由于Sub.prototype继承了Super(),因此这两个实例实际上是共享了Super的属性和方法,因此colors是Sub所有实例共享的属性,而这并不是我们的意图。
第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递函数。

1.来看看第一个方法借用构造函数:

function Super(name) {
    this.name = name;
}
function Sub() {
    Super.call(this, 'Shen');
    this.age = 30;
}
var instance = new Sub();
alert(instance.name); //Shen
alert(instance.age); //30

在子类中我们用call方法在当前环境下(即Sub下)调用了超类的构造函数,并在子类中添加自己的属性,虽然解决了原型链的问题,但是又有新问题就是:函数不能复用。

2.接下来我们看看最常用的组合继承方法:
思路是:使用原型链实现对原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,这样既通过了在原型上定义的方法实现了继承,又可以保证每个实例有自己的属性。

function Super(name) {
    this.name  = name;
    this.colors = ['red', 'blue', 'green'];
}
Super.prototype.sayName = function() {
    alert(this.name);
};
function Sub(name, age) {
    Super.call(this, name);
    this.age = age;
}
//继承方法
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function() {
    alert(this.age);
}
var instance1 = new Sub('Shen', 30);
instance1.colors.push('black');
alert(); //red,blue,green,black
instance1.sayName(); //shen
instance1.sayAge(); //30

var instance2 = new Sub('Han', 22);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //Han
instance2.sayAge(); //22

组合继承了避免了原型链和构造函数的缺陷,是最常用的继承模式。

3.原型式继承

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

在object函数内创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时对象的实例。看个例子:

var person = {
    name: 'Shen',
    friends: ['Wang', 'Han', 'Li']
};
var anotherPerson = object(person);
anotherPerson.name = 'Han';
anotherPerson.friends.push('Row');

var otherPerson = object(person);
otherPerson.name = 'Col';
otherPerson.friends.push('zzz');
alert(person.friends); //Wang,Han,Li,Row,zzz

所有object创建的对象都会共享object()方法传入对象的方法和属性,采用这种方法可以不必创建构造函数,而只想让一个对象与另一个对象保持类似的情况下使用,不过引用类型值得属性始终都会共享相应的值,和原型模式一样。
ES5通过新增了Object.create()方法规范了原型式继承。

4.前面说过组合继承是最常用的继承模式,不过它也有自己的不足:无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类原型的时候,一次是在子类构造函数内部。
例如:var instance = new Sub('Jack', 30); 第一次调用Super是Sub.prototype = new Super(); 第二是Super.call(this, name); 第二次调用创建的两个实例属性会屏蔽掉第一次调用生成的实例属性。
还好有解决办法,几声组合式继承:不必为子类型的原型而调用超类型的构造函数,我们只是需要超类型的一个副本,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。:

function inheritPrototype(sub, super) {
    var prototype = object(super.prototype);
    prototype.constructor = sub;
    sub.prototype = prototype;
}

第一行代码是创建超类型原型的一个副本,第二行代码是为创建的副本添加constructor属性,弥补因重写原型而失去默认的constructor属性。最后一行代码是将副本赋值给子类型的原型。

例如:

function Super(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Super.prototype.sayName = function() {
    alert(this.name);
};
function Sub(name, age) {
    Super.call(this, name);
    this.age = age;
}
inheritPrototype(Sub, Super);
Sub.prototype.sayAge = function() {
    alert(this.age);
};

寄生组合式继承只调用一次Super构造函数,并且避免了Sub.prototype上面创建不必要的、多余的属性。普遍认为是引用类型最理想的继承方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值