概述
先了模拟一下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的继承关系,真正在实际项目中用的可能性比较小,弄懂其中的原理才是重点。
最后
以上就是魔幻大白为你收集整理的JS中多继承的几种实现方法的全部内容,希望文章能够帮你解决JS中多继承的几种实现方法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复