js中只有 实现继承, 没有接口继承.
1.使用原型实现继承
构造函数,对象实例 中都有指向 原型对象的指针
但是构造函数实例和对象实例二者并没有直接联系(除了对象实例是用构造函数new出来这一点)
试想,当类型A的原型对象 是其它类型(比如B类型)的 对象实例(b) 的时候
那么A的prototype指向了b
如果b本身的原型对象中的contructor指向了B,那么A继承自B
如果b本身的原型对象是C类型的对象实例,那么A继承B,B继承C
如果c本身的原型对象是 ...
继续重复上面的步骤, 就形成了原型链
TypeB TypeB.prototype
bridge(TypeA.prototype | instanceB)
TypeA
instanceA
如上图:
TypeA 以及 instanceA 都有指针指向 bridge(它既是TypeA的原型对象,又是TypeB的instance)
既然 bridge是TypeB的对象实例,那么自然有指针指向TypeB的原型对象(TypeB.prototype)
这时候instanceA 拥有了所有TypeB.prototype上的属性和方法.
而 TypeB.prototype 上有constructor属性指向TypeB,instanceA也可以说是TypeB类型
记住,无论原型链有多少环节,它总会有个末端,末端类型的原型对象 它是个Object类型的实例.
所以会继承Object的所有属性和方法
确定原型与实例之间的关系
使用instanceof操作符 或者 Xxxx.prototype.isPrototypeOf(instance) 函数.
只要在这个instance的原型链上出现过的构造函数,它们就会返回true,表示是这个类型.
一定要先确定原型对象,然后再在原型上添加/覆盖 原型上的方法.
而且,添加或覆盖 属性/方法 的时候,不要使用字面量的形式.那会切断和父级的链接.
单独使用原型实现继承的缺点:
①.在创建对象的几种方式中,提到过原型模式.
如果属性是引用类型的时候,会引发问题,所以这样的属性要放到构造函数中,而非原型对象上.
但是,使用原型进行继承,会把别的类型的实例当做原型对象,
那么别的实例中定义的属性,自然成为了本类型的原型属性,但这是个引用类型的属性,又回到了上面的问题
②.没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数. // todo 如何理解?
由于上述缺点,js中在实现继承的时候,不会单独的使用原型 来实现.
2.借用构造函数
原理很简单,就是在子构造函数中,调用超类的构造函数.
function Super(name){ this.name = name; }
function Child(){
Super.call(this,"my name"); // 继承Super
this.age = 30 ; // 定义实例属性
}
console.log(new Child().name);//display 'my name'
这个方式对比单独使用原型的好处是,可以向超类型传递参数.
坏处是,构造函数模式的通病,定义的方法无法复用.而且超类型原型上的属性和方法,无法被继承.
所以,开发中也不会单独使用这种模式来实现继承.
3.组合继承
组合使用原型与借用构造函数来实现继承.
原理很简单,超类实例属性用借用构造函数来继承;超类的原型属性和方法,用原型来继承.
这种模式,是最经典的实现继承的方式.可以使用instanceof 以及 isPrototypeOf 来检查.
function Super(name){
this.name = name ;
}
Super.prototype.shareProp = "shareProp";
Super.prototype.sayName = function(){
console.log(this.name);
}
function Child(name ,age){
Super.call(this,name);
this.age = age ;
}
Child.prototype = new Super();
Child.prototype.constructor = Child ;
Child.prototype.sayAge = function(){
console.log(this.age);
};
var c = new Child(); // 这时候实例c就拥有了 name,age,shareProp 属性,以及 sayName,sayAge 方法
组合继承的缺点是
会调用两次超类构造函数: new Super() 以及 Super.call(this,xxx);
这算是个小缺点吧.
4. 原型式继承
在已有对象实例的情况下,借助它创建新的对象实例,而不用自定义新的类型.
function createObject(origin){
function F(){}
F.prototype = origin ;
return new F();
}
var origin = {
name:"origin",
friends:["a","b","c"]
};
var one = createObject(origin);
one.friends.push("d");
var another = createObject(origin);
another.friends.push("e");
console.log(origin.friends); //display a ~ e
以上的函数,在ES5中已经标准化了, 即
Object.create(origin,opts);
origin : 参考对象.(该对象会被浅复制,然后赋给新对象)
opts : 是指描述 加入到新对象的附加属性/方法;
格式和 defineProperties()中定义特性的格式一样{name:{value:"xx"},age :{ value : 20 }}
5. 寄生式继承
在原型式继承上 进行了封装.
不过耦合性太高,复用度不好.
function CreateAdvObject(origin){
//或者是 var clone = Object.create(origin); 只要可以生产对象就可以
var clone = createObject(origin);
clone.sayName = function(){}
return clone;
}
6. 寄生组合式继承
为了解决 组合继承中 调用了两次超类构造函数的问题.就是为了更高效一些.
被称为 最完美的继承方式
思路就是:
不必为了指定子类型的类型,而去调用构造函数,只需要一个超类原型对象的副本而已.
那么,这个副本,就由寄生式继承来创建吧.
换句话说,将调用超类的构造函数做子类原型 ,转化为copy超类原型给子类用
function InheritPrototype(targetType , superType){
targetType.prototype = Obeject.create(superType.prototype) ;
targetType.prototype.constructor = superType ;
}
function Super(){}
function Child(){
Super.call(this, arguments);
}
// 代替了原来的 两段代码
// Child.prototype = new Super(); Child.prototype.constructor = Super;
InheritPrototype(Child,Super);
Child.prototype.sayChild = function(){
console.log("sayChild");
}