现在的位置: 首页 > 综合 > 正文

javascript中的继承

2013年07月08日 ⁄ 综合 ⁄ 共 3630字 ⁄ 字号 评论关闭

javascript也是面向对象的语言,所以自然少不了继承。这个话题老生常谈了,今天总结了一下《Javascript高级程序设计》中写到的6种继承方法,并填加了一种组合式继承的改进方法。

0、演示所用代码:

function Base() { //父类
    this.val = "Base"; //基本类型
    this.ref = []; //引用类型
    this.func = function() { //方法
        console.log("Function from base");
    }
}

function Derived() { //所要实现的子类
    //应包括继承自父类的属性+方法
    this.dval = "Derived"; //子类自有属性
    this.dfunc = function() { //子类有用方法
        console.log("Function from derived");
    }
}

1、构造函数借用:(constructor stealing)

在子类构造方法内,通过apply/call将this作为参数传入:

function Derived() {
    Base.apply(this); //实现继承

    this.dval = "Derived";
    this.dfunc = function() {
        console.log("Function from derived");
    }
}

优点:

  1. 可以向父类构造方法传递参数,即给apply第二个参数:arguments。
  2. 父类中的属性都被复制到子类实例中,属性之间无干扰,无论是引用类型还是封装类型。

缺点:

  1. 每一个实例对象都拥有一份父类方法的拷贝,互不干扰,所以无法统一修改。
  2. 无法拥有父类原型中定义的方法。
  3. 子类的实例对象不能通过 instanceof 操作符判断是否是父类的实例。

2、原型链:

指定子类的prototype为父类的一个实例对象:
function Base() { 
    this.val = "Base"; 
    this.ref = []; 
}
Base.prototype.func=function(){
    console.log("Function from base");
};

	function Derived() {
    this.dval = "Derived";
    this.dfunc = function() {
        console.log("Function from derived");
    }
}

Derived.prototype=new Base(); //实现继承

优缺点和构造函数借用刚好相反。这里特别说明下属性之间相互干扰(对应构造函数借用的优点2)。

给实例对象在原型链上的属性赋值,实际上是给对象添加新的同名属性:

var d = new Derived();
d.val="Derived";
console.log(d.val); //输出Derived
console.log(Derived.prototype.val); //输出Base

所以修改实例对象的引用类型属性时,实际修改的是实例对象所属类的原型对象的属性:

var d = new Derived();
d.ref.push("a");
console.log(new Derived().ref); //输出["a"]

3、组合式继承:

上面两种方式互补一下,即用构造方法借用来继承父类属性,用原型链来继承父类方法:
function Base() { 
    this.val = "Base"; 
    this.ref = []; 
}
Base.prototype.func = function(){
    console.log("Function from base");
};

function Derived() {
    Base.call(this); //继承父类的属性
    this.dval = "Derived";
}
Derived.prototype = new Base(); //继承父类的方法
Derived.prototype.dfunc = function() { //注意顺序:先指定父类,再新添方法
        console.log("Function from derived");
};
Derived.prototype.constructor = Derived; //不是必须,如果不指定,new Derived().constructor==Base()

这看起来没有缺点了,但实际上还有一个冗余,在继承父类属性和方法时各调用了一次父类的构造方法,也就是说父类的这些属性不仅存在于子类的实例对象中,在子类的原型中也有一份!这在某些情况下会产生意想不到的后果:

var d = new Derived();
console.log(d.val); //输出:Base,这是继承过来的初始值
d.val="Derived";
console.log(d.val); //输出:Derived,修改后的值
delete d.val;
console.log(d.val); //期望输出:undefined,实际输出:Base。

4、组合式继承plus

针对上面的情况,解决办法就是在继承父类方法的时候不传递一个父类实例。这很简单,因为我们是想继承父类的方法,而父类的方法是在父类的原型中定义的,那我直接用父类的原型不就好了。
将上面继承父类方法那一行改为:
Derived.prototype=Base.prototype;

再次执行上面的测试代码,输出就正确了。但这么做也有弊端,那就是如果修改了子类的prototype,那父类的prototype也要受到牵连,因为他们指向的是同一个对象。

5、《Javascript高级程序设计》中的其他解决方案

循序渐进的说:

(1)原型式继承

实际就是在工厂方法中进行浅复制:
function object(o){
    function F();
    F.prototype = o;
    return new F();
}
注意,传参是个实例对象。缺点同原型链继承。

ECMAScript5中对此进行了标准化:Object.create(),参数列表:

  • 用作新对象原型的对象(基类的实例对象)
  • 【可选】为新对象添加的额外属性。
可以看出,这就是一种装饰模式的应用,应用举例:
var man = {
    type: "man",//普通人类
    eat: true//能吃饭
};

var vampire = Object.create(man, {
    type: {
        value: "vampire",//吸血鬼
        enumerable: false//让这个属性对for-in循环隐藏,方便输出
        //属性类型一共四个值,还有
        //configurable:表示能否通过delete删除,能否修改属性特性,能否修改为访问器属性
        //writable:表示能否修改属性值
    },
    fly: {
        value: true,//吸血鬼的特殊能力飞翔
        enumerable: true
    }
});
//遍历吸血鬼对象中可遍历属性
//刨除不可遍历的type
//包括:继承自man的eat,自定义的fly
for (var i in vampire) {
    log(print(vampire, i) + " , " + print(man, i));
}

//辅助输出的函数,第一个参数为待考察的实例对象
//第二个参数为考察该对象的属性
//根据属性值输出对象能否执行属性名
function print(o, i) {
    return o.type + (o[i] ? " can " : " can't ") + i;
}

输出:

vampire can fly , man can't fly

vampire can eat , man can eat

(2)寄生式(parasitic)继承

在原型式继承的基础上,对返回对象加以功能上的增强。不用说,还是装饰模式。
function decorator(original) {
    function F() {}
    F.prototype = original;
    var dec = new F();
    dec.newMethod = function() {
        //do something
    };
    return dec;
}

依然要注意,传参是实例对象。是对上面原型式继承的增强。从继承的角度讲,没有任何进步,只是为了引出下面的集成方法。

(3)寄生组合式继承

这是介绍上面两种方法的目的,用这招来帮组合式继承解决它的劣势。
function object(o) {
    function F();
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType) {
    //复制一个supertype的原型
    var prototype = object(superType.prototype);
    //因为上面object方法没有指定构造器,这里补上。
    //当然,constructor指定与否和你是否使用这个字段有关,
    //这句话加不加,无关紧要。
    prototype.constructor = subType;
    //将父类的原型的副本传递给子类的原型。
    subType.prototype = prototype;
}

使用时,把组合式继承用来继承父类方法的那句改为:

inheritPrototype(Derived,Base);

抱歉!评论已关闭.