JS中多继承的几种实现方法

先了模拟一下new操作符的作用:

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

Person.prototype.say = function(){
	console.log(this.name);
}

var p = new Person("KD");
p.say();

var obj = {}
Object.setPrototypeOf(obj,Person.prototype)//obj.__proto__=Person.prototype
Person.call(obj,"KD");
obj.say()
console.log(obj instanceof Person)//true

从上面的代码可以看到new的作用其实就相当于在一个空Object上执行一个call或者apply加上设置空Object的隐式原型链,理解了上面这段简单的代码,往下看会更容易点。


下面进入正题,列举几种多继承的方法,由于JS语言本身并没有多继承的机制,都是通过其他方法模拟出来的,每种方法都一些缺陷。

1)apply/call+mixin

function Coord(x){
    this.x = x;
}
Coord.prototype.outputCoord = function(){
    console.log(this.x,this.y,this.z)
}

function Other(y){
    this.y=y;
}
Other.prototype.outputOther = function(){
    console.log(this.x,this.y,this.z)
}

function SpaceObject(x,y,z) {
    Coord.call(this, x); 
    Other.call(this, y); 
                                  
    this.z=z;
}
SpaceObject.prototype.outputSpace = function(){
    console.log(this.x,this.y,this.z)
}

SpaceObject.prototype = Object.create(Coord.prototype);
SpaceObject.prototype.constructor = SpaceObject;


!function mixinOther() {
    var keys = Object.keys(Other.prototype);
    var i, len;
    for(var i = 0, len = keys.length; i < len; i++) {
        SpaceObject.prototype[keys[i]] = Other.prototype[keys[i]];
    }
}()

var space = new SpaceObject(1,2,3);
space.outputCoord()//1 2 3
space.outputOther()//1 2 3
space.outputSpace()//1 2 3
console.log(space instanceof SpaceObject);//true
console.log(space instanceof Coord);//true
console.log(space instanceof Other);//false

这种方法应该是最常见的一种方式,缺点就是无法用instanceof操作符检测更深层次的父子关系以及无法使用super进行操作,其他方面还好。


2)ES6中的class+mixin(https://github.com/rse/aggregation)

var aggregation = (base, ...mixins) => {

    /*  create aggregation class  */
    let aggregate = class __Aggregate extends base {
        constructor (...args) {
            /*  call base class constructor  */
            super(...args)




            /*  call mixin's initializer  */
            mixins.forEach((mixin) => {
                if (typeof mixin.prototype.initializer === "function")
                    mixin.prototype.initializer.apply(this, args)
            })
        }
    };

    /*  copy properties  */
    let copyProps = (target, source) => {
        Object.getOwnPropertyNames(source)
            .concat(Object.getOwnPropertySymbols(source))
            .forEach((prop) => {
            if (prop.match(/^(?:initializer|constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                return
            Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
        })
    }


    /*  copy all properties of all mixins into aggregation class  */
    mixins.forEach((mixin) => {
        copyProps(aggregate.prototype, mixin.prototype)
        copyProps(aggregate, mixin)
    })

    return aggregate
}

//============================================

class Colored {
    initializer ()     { this._color = "white" }
    get color ()       { return this._color }
    set color (v)      { this._color = v }
}




class ZCoord {
    initializer ()     { this._z = 0 }
    get z ()           { return this._z }
    set z (v)          { this._z = v }
}




class Shape {
    constructor (x, y) { this._x = x; this._y = y }
    get x ()           { return this._x }
    set x (v)          { this._x = v }
    get y ()           { return this._y }
    set y (v)          { this._y = v }
}




class Rectangle extends aggregation(Shape, Colored, ZCoord) {
    toString(){
        return this.x+","+this.y+","+this.z+","+this.color;
    }
}




var rect = new Rectangle(7, 42)
rect.z     = 1000
//rect.color = "red"
console.log(rect);

上面的aggregation是一个github上的源码,有针对ES5和ES6的分别实现,上面是ES6的实现代码。

去掉库的代码看,用起来还是比较省事的。但是有几个问题,1)不支持instanceof,2)除了直接的父类,其他父类需要用initializer来模拟constructor,看起来很别扭。为什么不能用统一的constructor呢?原因很简单,ES6中规定class中的constructor不能用apply/call来调用,只能是new Class的时候用。3)不支持super调用


3)ES6间接继承

如果我想实现A继承自B和C,有两种方法,一种是A直接继承自B和C,另一种是A继承自B,B继承自C,也可以实现多继承

var CalculatorMixin = Base => class extends Base { 
    constructor(...args){
        super(...args);
        console.log("CalculatorMixin",args);
    } 
    calc() {  
        console.log("calc");  
    }  
};  
  
var RandomizerMixin = Base => class extends Base {  
    constructor(...args){
        super(...args);
        console.log("RandomizerMixin",args);
    }
    randomize() {
        super.foo();  
        console.log("randomize");  
    }  
};  
  
class Foo {  
    constructor(...args){
        console.log("Foo",args);
    } 
    foo(){  
        console.log("foo");  
    }  
}  
class Bar extends CalculatorMixin(RandomizerMixin(Foo)) {

}  
  
var b = new Bar(1,2,3);  
b.foo();  
b.randomize();  
b.calc(); 

console.log(b instanceof Bar)//true
console.log(b instanceof Foo)//true
console.log(b instanceof CalculatorMixin)//报错
console.log(b instanceof RandomizerMixin)//报错

上面这种方法利用了ES6中class的extends后面可以使用表达式特性,间接实现多继承,这种方法有个缺点是CalculatorMixin等Mixin其实是个箭头函数,无法用instanceof检测,因为箭头函数没有显示原型。

下面看看如何修正原型链:

var CalculatorMixin = Base => class extends Base { 
    constructor(...args){
        super(...args);
        console.log("CalculatorMixin",args);
    } 
    calc() {  
        console.log("calc");  
    }  
};  
  
var RandomizerMixin = Base => class extends Base {  
    constructor(...args){
        super(...args);
        console.log("RandomizerMixin",args);
    }
    randomize() {
        super.foo();  
        console.log("randomize");  
    }  
};  

  
class Foo {  
    constructor(...args){
        console.log("Foo",args);
    } 
    foo(){  
        console.log("foo");  
    }  
}  
class Bar extends CalculatorMixin(RandomizerMixin(Foo)) {

}  
  
var b = new Bar(1,2,3);  
b.foo();  
b.randomize();  
b.calc(); 

CalculatorMixin.prototype = Object.create({});//需要继承自Object,这里如果写null的话,instanceof Object会是false
RandomizerMixin.prototype = Object.create(null);
Object.setPrototypeOf(RandomizerMixin.prototype,CalculatorMixin.prototype)
Object.setPrototypeOf(Foo.prototype,RandomizerMixin.prototype)


console.log(b instanceof Bar)//true
console.log(b instanceof Foo)//true
console.log(b instanceof CalculatorMixin)//true
console.log(b instanceof RandomizerMixin)//true
console.log(b instanceof Object)//true

是不是有点晕,这里的代码都是做探讨用,重在理解js中继承的原理。修正后应该能比较完美满足多继承的主要特性,甚至包括super和constructor的调用。


4)ES6反射+Proxy

var obj1 = {  
    name: "obj-1",  
    foo() {  
        console.log( "obj1.foo:", this.name );  
    }  
},  
obj2 = {  
    name: "obj-2",  
    foo() {  
        console.log( "obj2.foo:", this.name );  
    },  
    bar() {  
        console.log( "obj2.bar:", this.name );  
    }  
},  
handlers = {  
    get(target,key,context) {  
        if (Reflect.has( target, key )) {  
            return Reflect.get(target, key, context);  
        }  
        else {  
            for (var P of target[Symbol.for( "[[Prototype]]" )]) {  
                if (Reflect.has( P, key )) {  
                    return Reflect.get(P, key, context);  
                }  
            }  
        }  
    }  
},  
obj3 = new Proxy({  
    name: "obj-3",  
    baz() {  
        this.foo();  
        this.bar();  
    }  
},handlers);  
  
obj3[Symbol.for("[[Prototype]]")] = [obj1, obj2];  
obj3.baz();  
//obj1.foo:obj-3  
//obj2.bar:obj-3 

上面这种方法仍然可以通过修正原型链实现instanceof操作符的作用,但是不能用constructor和super。



本文中的方法都做探讨js的继承关系,真正在实际项目中用的可能性比较小,弄懂其中的原理才是重点。


猜你喜欢

转载自blog.csdn.net/kittyjie/article/details/72828470