在es6中已经有语法糖extends去实现类的继承,然而多继承是不被允许也是不被提倡的,因为会导致‘钻石问题’:比如说b和c都继承自a,d继承自b和c,那么d里面就会有同一个方法来自于两个两个祖先,那么当在d中调用这个方法时就会出现逻辑问题,到底是调用b的呢还是c的呢?java就是看到了c++多继承的问题所以就干脆不允许多继承了,js因为自身的灵活性,我们依然可以通过一些方式来实现多继承(很多地方叫做mixin,实际上听起来更正确)。
Call/apply
var A = function(name) {
this.name1 = name; // this.name代表函数名,这里避免冲突
}
var B = function(age) {
this.age = age;
this.getAge = function() {}
}
var C = function(name, age) {
A.call(this, name);
B.call(this, age);
}
// usage
var c = new C('n', 2);复制代码
以上就是利用call或者apply来‘继承’父类属性及方法,其实本质上就是调用方法利用this把属性组合到一起。因为调用A方法是在上下文中加了一个name属性,调用B就是加了age属性,而你在new C的时候,js创造了一个对象,并且把上下文指向了这个对象,所以AB的属性也就自然添加到了这个对象里。
所以下面用这个思路写一个extend方法:(extends是关键字,直接用这名字会报错)
function extend(...args) {
for (var i = 0; i < args.length; i++) {
args[i].call(this)
}
}
// usage
var s = new extend(Class1, Class2);复制代码
当然你可以看到,用这个方法的话你是不能传参的
上面调用函数的方法模拟多继承固然可以满足我们继承祖先的属性方法这一要求,可是这个最后的孩子其实不是真正的后代,因为不难看出他和前面的class不在同一条原型链上,所以用c instanceof A
返回false。而且一个缺点就是那个类的方法必须像this.getAge()这样定义在类里面,如果你new了多个实例就会重复创造这个方法,造成内存的浪费。
利用prototype
为了省内存,必然会想到往prototype添加方法的方式,然而通过上面的调用显然不能指望这些方法也出现在this上,所以我们要通过new来复制这些方法
var A = function() {
this.name1 = '';
}
var B = function() {
this.age = '';
}
B.prototype.getAge = function() {}
var extend = function(...args) {
var ClassC = function() {}
for (var i = 0; i < args.length; i++) {
var base = new args[i]();
for (var f in base) {
ClassC.prototype[f] = base[f];
}
}
return ClassC;
}
// usage
var C = extend(A, B);
var c = new C();复制代码
for...in会把所有原型链上的可枚举属性都列出来,所以拿到那些类的实例后,再把这些属性拷贝到我们自己类的原型上就行。
这个依然不能传参,不能instanceof
混合模式
function A(name) {
this.name1 = name;
}
A.prototype.getName = function() {}
function B(age) {
weight = 100; // 私有属性,体重
this.age = age;
}
B.prototype.getAge = function() {}
B.prototype.getWeight = function() {return weight;}
B.prototype.addWeight = function() {weight++;}
function extend() {
for (var i = 0; i < arguments.length; i++) {
var tempBase = new arguments[i]();
for (var prop in tempBase) {
if (!tempBase.hasOwnProperty(prop)) { // 只放链上的属性
this.prototype[prop] = tempBase[prop];
}
}
}
}
// usage
function Child(name, age) {
A.call(this, name); // 传参,以及添加本身的属性
B.call(this, age);
}
extend.call(Child, A, B);
var child = new Child('lol', 5);复制代码
以上把前面代码改了两点,1是前面直接定义一个class在extend里面,所以直接写死了Class.protorype[prop] = ..., 这里写成this.prototype因为后面在调用extend方法时会把上下文指定成你的class,并且通过了hasOwnProperty去检查这个属性是不是自己的。
2后面你就可以定义自己的构造器了,并实现的传参的需要。
私有属性
这里稍微提一下私有属性,我看了一些博客后发现大家对私有属性理解好像有问题。其实像上面的weight直接写在里面就是私有的,因为私有的定义是这个function里面的其他函数能用以外,其他人都不行。比如私有属性/方法是无法继承的;这个类的实例也无法直接访问到的,要访问/修改必须你自己暴露出getter/setter,就比如上面的getWeight方法,如果你在Child类里面添加了weight的话就会返回它,否则返回父类的那个私有变量。
instanceof
上面所有的都只是假装继承,用instanceof都得不到你想要的结果不能用来判断某对象是不是某类的实例或子类,为了实现这一效果,你要写自己的isInstanceOf()方法,思路是在每个子类里面存一个所有父类的引用然后做对比,另外为了解决多继承的钻石问题要用到MRO算法。下面博客写了这个,然而也不完美,所以各看官自己斟酌。
引用: