面向对象的程序设计 —— 继承模式

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

  1. 别忘记默认的原型
    所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也是所有自定义类型都会继承toString(),valueof()等默认方法的根本原因。
  2. 确定原型和实例的关系
    两种方法:① 使用instanceof()操作符,测试实例与原型链中出现过的构造函数,结果就会返回true。
    alert(instance instanceof SuperType);//true Object,SuperType,SubType都返回true
    ②使用isPrototypeOf()方法,只要在原型中出现过的原型,都可以说是该原型链所派生的实例的原型
    alert(Object.prototype.isPrototypeOf(instance));//true
  3. 谨慎地定义方法
    子类型有时候需要覆盖类型中的某个方法,或者需要添加超类型中的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function(){//添加方法
    return this.Subproperty;
    }
    SubType.prototype.getSuperValue = function(){//重新写超类型的方法调用重写的这个方法
    return false;
    }
    在通过原型链实现继承时,不能使用对象字母量创建原型的方法。这样会重写原型链
  4. 原型链的问题
    最主要的问题来自包含引用类型值的原型
    在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就是顺理成章地变成了现在的原型属性了。(共享问题)
    第二个问题是:创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

① 解决原型中包含引用类型值所带来的问题,采用借用构造函数的技术(伪造对象或经典继承)。
思想:在子类型构造函数的内部调用超类型构造函数。
② 函数只不过是在特定环境中执行代码的对象,因此使用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属性的副本了。

  1. 传递参数
    可以在子类型构造函数中向超类型构造函数传递参数

     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"
    
  2. 借用构造函数的问题
    无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了

    扫描二维码关注公众号,回复: 10881108 查看本文章

组合继承

也叫经典继承,指的是将原型链和借用构造函数的技术组合到一起,发挥二者之长的一种继承模式。
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。

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()。

发布了17 篇原创文章 · 获赞 0 · 访问量 761

猜你喜欢

转载自blog.csdn.net/CandiceYu/article/details/89889081
今日推荐