ECMAScript只支持实现继承,而且其实继承主要是依靠原型链来实现的
原型链
因为每个对象和原型都有原型,对象的原型指向原型对象,而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数,原型,实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
原型链的基本概念:
让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,另一个原型中也包含着一个指向另一个构造函数的指针。假设另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就够成了实例与原型的链条。
实现原型链的一种基本模式:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
fucntion SubType(){
this.subproperty = fasle;
}
//继承了superType属性和方法
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.Subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue);//true
subType继承了SuperType,继承是通过创建SuperType的实例,并将该实例赋给subType.prototype实现的
实现的本质是重写原型对象,代之以一个新类型的实例。
例子中的实例以及构造函数和原型之间的关系图
① 上面代码,我们没有通过SubType默认原型,而是给换了一个新原型,就是SuperType的实例。新原型仅有作为一个SuperType的实例所拥有的全部属性和方法,内部还有一个指针,指向了SuperType的原型。
② getSuperValue()方法仍然还是在SuperType.prototype中,但property则位于SubType.prototype中、因此property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中了。
③ 在通过原型链实现继承的情况下,搜索过程就是以沿着原型链继续向上。
④ 调用instance.getSuperValue会经历三个搜索步骤:①搜索实例 ②搜索SubType.prototype ③搜索SuperType.prototype
- 别忘记默认的原型
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也是所有自定义类型都会继承toString(),valueof()等默认方法的根本原因。 - 确定原型和实例的关系
两种方法:① 使用instanceof()操作符,测试实例与原型链中出现过的构造函数,结果就会返回true。
alert(instance instanceof SuperType);//true Object,SuperType,SubType都返回true
②使用isPrototypeOf()方法,只要在原型中出现过的原型,都可以说是该原型链所派生的实例的原型
alert(Object.prototype.isPrototypeOf(instance));//true - 谨慎地定义方法
子类型有时候需要覆盖类型中的某个方法,或者需要添加超类型中的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){//添加方法
return this.Subproperty;
}
SubType.prototype.getSuperValue = function(){//重新写超类型的方法调用重写的这个方法
return false;
}
在通过原型链实现继承时,不能使用对象字母量创建原型的方法。这样会重写原型链 - 原型链的问题
最主要的问题来自包含引用类型值的原型
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就是顺理成章地变成了现在的原型属性了。(共享问题)
第二个问题是:创建子类型的实例时,不能向超类型的构造函数中传递参数。
借用构造函数
① 解决原型中包含引用类型值所带来的问题,采用借用构造函数的技术(伪造对象或经典继承)。
思想:在子类型构造函数的内部调用超类型构造函数。
② 函数只不过是在特定环境中执行代码的对象,因此使用apply(),call()方法也可以在(将来)新创建的对象上执行构造函数。
function SuperType(){
this.colors = ["red","green","black"];
}
fucntion SubType(){
SuperType.call(this);//继承SuperType
}
var instance1 = new SubType();
var instance2 = new SubType();
instance1.colors.push("blue");
alert(instance1.colors);//"red,green,black,blue"
alert(instance2.colors);//"red,green,black"
③ “借调”了超类型的构造函数。使用call()方法,我们实际上是在(为了将要)新创建的SubType实例的环境下调用SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例都会具有自己的colors属性的副本了。
-
传递参数
可以在子类型构造函数中向超类型构造函数传递参数function SuperType(){ this.name = name; } fucntion SubType(){ Super.call(this,"abc");//继承SuperType,同时还传参 this.age = 29;//实例属性 } var instance = new SubType(); alert(instance.name);//"abc" alert(instance.age);//"29"
-
借用构造函数的问题
无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了
组合继承
也叫经典继承,指的是将原型链和借用构造函数的技术组合到一起,发挥二者之长的一种继承模式。
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。
function SuperType(name){//定义两个属性name,colors
this.name = name;
this.colors = ["red","green","black"];
}
SuperType.prototype.sayName = function(){//SuperType原型定义一个方法
return this.name;
}
function SubType(name,age){
SuperType.call(this,name);//SubType调用SuperType时传入name参数,定义自己的属性age
this.age = age;
}
SubType.prototype = new SuperType();//SubType的实例赋值给SubType
SubType.prototype。constructor = SubType;
SubType.prototype.satAge = function(){
alert(this.age);
}
//定义方法这样一来,可以让两个不同的subType实例既分别拥有自己属性包括colors属性也可以使用相同的方法了
var instance1 = new SubType("abc",29);
instance1.colors.push("black");
alert(instance1.colors);//"red,green,blue,black"
instance1.sayName();//"abc"
instance.sayAge();//29
var instance2 = new SubType("Greg",21);
alert(instance1.colors);//"red,green,blue"
instance1.sayName();//"Greg"
instance.sayAge();//21
原型式继承
道格拉斯·克罗克福德的想法是借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = { //另一个对象基础是person对象
name:"A";
friends:["B","C","D"];
}
var anotherperson = Object(person);
anotherperson.name = "E";
anotherperson.friends.push("F");
var yetAnotherperson = Object(person);
yetAnotherperson.name = "H";
yetAnotherperson.friends.push("I");
alert(person.friends);//"B,C,D,F,I"
在object()函数内部,先创建了一个临时性构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
本质上讲,object()对传入其中的对象执行了一次浅复制。
把它传入到object()函数中,该函数返回一个新对象。新对象将person作为原型。所以它的原型中就包含一个基本类型值属性和一个引用类型属性。person.friends不仅仅属于person,也被anotherPerson,yetAnotherperson共享。相当于又创建person对象两个副本。
原型式继承,要求你必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把它传递给object函数,然后再根据具体需求得到对象加以修饰即可。
ECMAScript5通过新增Object.create()方法规范了原型式继承。两个参数:一个用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象。与Oject()方法行为相同
var anotherperson = Object(person); 替换为 var anotherperson = Object.create(person);
var yetAnotherperson = Object(person); 替换为 var yetAnotherperson = Object。create(person);
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象
function createAnother(original){
var clone = Object(original);//通过调用函数创建一个新对象
clone.sayHi = function(){//以某种方式来增强这个对象
alert("Hi");
}
return clone;//返回这个对象
}
var person = {
name:"A",
friends:["B","C","D"];
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi();//"Hi"
person返回了一个新对象——anotherPreson,新对象不仅具有person所有属性和方法,而且还有自己的sayHi()方法。
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。
寄生组合式继承
组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。
子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性,
function SuperType(name){
this.name = name;
this.colors = ["red","green","black"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);//第二次调用superType()在新对象上创建了实例属性name,colors这两个属性就屏蔽了原型中的两个同名属性
this.age = age;
}
SubType.prototype = new SuperType();//第一次调用superType(),得到两个属性都是superType的实例属性。
SubType.prototype。constructor = SubType;
SubType.prototype.satAge = function(){
alert(this.age);
}
这样写,有两组name和colors属性,一组在实例上,一组在subType原型中
① 寄生组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
② 基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
③ 本质:就是使用寄生式继承类继承超类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype(subType,superType){
var prototype = Object(superType.prototype);//创建对象 创建超类型原型的副本
prototype.construcoter = subType;//增强对象,为创建的副本添加constructor属性
subType.prototype = prototype;//指定对象,为新创建的对象(副本)赋值给子类型的原型
}
④ 上面例子①②替换掉,变为inheritPrototype(SubType,SuperType);
⑤ 这样,提高效率在于只调用了一次SuperType构造函数,并且因此避免了在SubType.prototype上面创建不必要的多余的属性。与此同时,原型链还能保持不变,因此,能够正常使用instanceof和isPrototypeOf()。