ES5之继承
ES5之继承
原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:
每个构造函数都有一个原型对象,这个原型对象有一个指向构造函数的指针,而实例有一个指向他的原型对象的内部指针。
原型链:一个函数的原型对象等于另一个类型的实例,此时该原型对象中就有一个指向另一个原型的指针,相应的,另一个原型对象也包含一个指向另一个构造函数的指针。如果另一个原型又是另一个类型的实例,如此层层递进,形成一个原型链。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承了 SuperType SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
注意: instance.constructor现在指向SuperType
通过new创建的实例中,并没有constructor属性,是从原型中继承来的,因此访问实例的constructor属性,就是访问它的原型对象的constructor属性。
当以读取模式访 问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在 通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。
所有函数的默认原型都是Object实例,因此默认原型都会包含一个内部指针,指向Object.prototype
确定原型和实例的关系
- instanceof( )
- isPrototypeOf( ) 只要是原型链中出现过的原型,都可以说是该 原型链所派生的实例的原型
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这 样做就会重写原型链
缺点
- 对于引用类型,若是原型中存在引用类型,会被所有的实例共享
- 在创建子类型的实例时,不能向超类型的构造函数中传递参数。
借用构造函数
可以解决原型中包含引用类型的问题,也可以向超类型传参。
思想:在子类型的构造函数中调用超类型构造函数,可以使用call() apply() 方法在新创建的对象上执行构造函数。
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
这样的话,每个SubType实例都有着自己的colors属性副本。
借用构造函数方式也可以传递参数
####问题
仅仅借用构造函数模式,难以避免方法都在构造函数中定义,函数复用就不友好。且,超类型的原型对象中的方法,子类型不可见。
组合继承
将原型链和借用构造函数模式结合。
思路:使用原型链实现对原型属性和方 法的继承,而通过借用构造函数来实现对实例属性的继承。
这样,既通过在原型上定义方法实现了函数 复用,又能够保证每个实例都有它自己的属性
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
function SubType(name, age) {
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
// SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29
alert(instance1.constructor.name)
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
原型式继承
可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅 复制。而复制得到的副本还可以得到进一步改造。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
Object.create(proto) 以proto为原型创建对象
原型式即把一个给定对象作为一个新对象的原型,而之后新对象也可以进一步改造。
寄生式继承
与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强 对象,后返回对象。
思路与工厂模式类似,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,后再像真地是它做了所有工作一样返回对象。
寄生式:看起来好像是它做了所有工作,但是实质上他也通过了别人的帮助。
####问题
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一 点与构造函数模式类似
寄生组合式继承
组合继承是 JavaScript 常用的继承模式;不过,它也有自己的不足。组合继承大的 问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是 在子类型构造函数内部
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背 后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型 原型的一个副本而已。
本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型 的原型。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
第一步是创建超类型原型的一个副本。第二 步是为创建的副本添加 constructor 属性,从而***弥补因重写原型而失去的默认的 constructor 属性***。 后一步,将新创建的对象(即副本)赋值给子类型的原型。
可以解决组合继承中的由于创建原型为实例而导致的原型中包含实例属性的问题。