本文通过女娲造人的比喻,系统梳理JavaScript中对象的创建方式及六种继承模式。详细解析原型链、借用构造函数、组合继承、原型式继承、寄生式继承及寄生组合式继承的原理与优缺点,重点阐述如何通过组合构造函数与原型模式实现属性与方法的有效继承,并对比各模式的适用场景。
JavaScript六种继承模式概述
JavaScript中主要存在六种继承模式,分别为原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承以及寄生组合式继承。为了更直观地理解并记忆这些模式,这里通过一个构想的过程对六种模式进行简要阐述。
这个故事篇幅较长,暂且将其命名为“女娲造人”。
对象的创建模式
创建对象
女娲最初逐一手工捏制人类(即创建对象),这种方式效率极低。因此,她设计了一台机器(函数),只需告知机器想要创造的人类具备哪些特征与功能,由机器完成制造。这对应于工厂模式的概念:使用统一接口创建对象,但会产生大量重复代码,因此引入了函数(模具)的概念。
然而,机器造人过程依然繁琐(涉及挖土、和泥、制作五官等步骤)。于是封装思想应运而生:提前制作好眼睛、鼻子等部件备用,改造机器,告知机器要创造的人具备何种眼睛和鼻子,机器直接安装即可。这种经过改造的机器即为构造函数。
即便如此,仍存在一个问题:若要让所有造出的人都能“跑”,机器需为每个人单独安装“跑”的功能,工序繁琐且易出错。于是引入第三方(将函数方法定义在构造函数外部,处于全局作用域)。由第三方负责为所有造出的人安装“跑”的功能,机器直接调用即可,省去了每次加工的步骤。虽然人们都能跑了,但问题随之而来:造出的人还需要“跳”、“走”等N个功能,难道要寻找N个第三方吗?这样建机器的意义便大打折扣。于是,女娲(开发人员)创造了原型模式……
原型模式详解
在原型模式中,每个函数都有一个prototype属性,它是一个指针,指向原型对象。原型对象包含所有实例共享的属性和方法。该原型对象拥有一个constructor属性,该属性包含一个指向prototype属性所在函数的指针。
这一概念看似复杂,但从女娲的角度理解则更为清晰:造物主女娲娘娘发明了各种各样的模具(原型对象)。开始造人时:1. 造一类人,使用专门针对该类人的模具。既然能造万物,造什么就用什么模具。所有造人机器(函数)各自拥有唯一的一个模具(原型对象),并且机器上有一个标签[prototype],指向该模具;该模具有能贴生产标志的[constructor]属性,指向这台机器,表示这是由该机器生产的模具。因此,要造什么样的一类人,只需修改模具即可。这就是原型对象的优点:方便且快捷。
生产过程如下:
1. 造机器A:function jqA(){}; //拥有prototype属性,指向模具(原型对象)
2. 造模具:jqA.prototype={
constructor: jqA, //相当于贴上标签,由A机器生产,
name:'lily',
skin:'white',
run: function(){
alert(this.name+'run'); }
该模具负责造出名为lili、皮肤为white、能run的这类人。
3. 造一个这类型的人:var person1=new jqA();
再造一个这类型的人:var person2=new jaA();
person1和person2都拥有[[prototype]]属性,表示经过模板A处理,指向A模板。
组合使用构造函数和原型模式
看似完美,但问题又来了:这样生产出来的人都一样,迎面走来五个一模一样的白皮肤美女,随后又有五个一模一样的矮挫丑,这太可怕了。因此,机器A在使用模板的同时,还可以根据指令使造出的人具有不同特点,例如:这个蓝眼睛,那个胖点……这种额外功能通过构造函数实现——即组合使用构造函数和原型模式。
生产过程如下:
//组合使用构造函数模式和原型模式
function Person(name,skill,country) {
this.name=name;
this.age=skill;
this.country=country;
this.member=["刘封","刘婵"];
} //机器可以听命令
Person.prototype={
constructor:Person,
sayName:function () {
alert(this.name);
}
} //还可以用模板
var person1=new Person('马超','铁骑','蜀国');
var person2=new Person('刘备','仁德','蜀国');
动态原型模式
此时,女娲懒得同时照顾机器和模板,于是直接将模板装入机器中:在构造函数中初始化原型对象——即动态原型模式,更加便捷。
生产过程如下:
function Person(name,skill,country) {
this.name=name;
this.skill=skill;
this.country=country;
if(typeof this.sayCountry !=undefined){
Person.prototype.sayCountry=function () {
alert(this.country);
};
}
}
var friend=new Person('张飞','咆哮','蜀国');
friend.sayCountry();
寄生构造函数模式
还有问题吗?好的,提供寄生构造函数模式:机器中增加一个内部机器,该内部机器负责生产,并将生产的人提供给外部机器,外部机器向外提供这类人即可。(通常这种情况较少用到……)
继承模式解析
继承(我的理解—_—)
问题:女娲要造另一批人B,这批人的模板B已造好,但希望这批人具备之前造过的那批人的特点,该怎么办?先让这些人过滤一下先前的模板A,再放入B中造就OK,这样类‘B人’就继承了‘A’类人的特点。如何过滤:父实例=子原型 建B的模板,造一个a出来,这个a肯定要过滤A模板,所以让B的模板等于a就OK,问题解决。
//父函数,机器A,A类人。它的实例a中有[[Prototype]]属性和自定义的property属性
function SuperType(){
this.property=true;
}
//在SuperType原型对象(模具A)中添加getSuperValue方法
SuperType.prototype.getSuperValue=function(){
return this.property
}
//子函数,机器B,B类人。构造函数SubType,它的实例中有[[Prototype]]属性和自定义的subproperty属性
function SubType(){
this.subproperty=false;
}
//继承了SuperType (原型链)
SubType.prototype=new SuperType(); //机器B=a
//在SubType原型对象(模具B)中添加getSubValue方法
SubType.prototype.getSubValue=function(){
return tis.subproperty;
};
var insatance=new SubType();
alert(insatance.getSuperValue()); //true
问题:引用类型值会改变,因为实例共享属性,这与原型模式中的问题相同。
借用构造函数模式
解决方案:经典继承(借用构造函数):其实就是把模具A设计到机器B中,但它已不再是模板。机器B会给生产的b们添加这些A中的属性和方法,但可以人为控制,女娲又命令机器B根据传递不同的命令生产不同的b。
在子类构造函数的内部调用超类构造函数。
相当于把父类的属性实例化到子类中?Java中的super()存在疑问。
function SuperType(){
this.colors=['red','blue','green'];
}
function SubType(){
//继承了SuperTYpe
SuperType.call(this);
}
var insatance1=new SubType();
insatance1.colors.push('black');
alert(insatance1.colors);// 'red,blue,green,black'
var insatance2=new SubType();
alert(insatance2.colors);//'red,blue,green'
1. 传递参数:
借用构造参数可以在子类型构造参数中向超类型构造参数传递参数。
function SuperType(name){
this.name=name;
}
function SubType(){
//继承了SuperTYpe,同时还传递了参数
SuperType.call(this,'赵云');
//实例属性
this.age=29;
}
var insatance=new SubType();
alert(insatance.name); //赵云
alert(insatance.age); //29
为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数之后,再添加应该在子类型中定义的属性。
问题:浪费劳动力,在机器中创建A具有的功能和属性,那么A模板就没用了,相当于回到工厂模式,都有打火机了,还要钻木取火吗……
组合继承模式
解决方案:组合继承。
在公司加班没事做,现在赶着下班,故事编不下去了,后面的继承模式搬之前的记录吧……
原型链和构造函数技术组合到一起,使用原型链实现对原型属性和方法的继承,借用构造函数来实现对实例属性的继承。这样通过在原型上定义方法实现了函数的复用,同时保证每个实例都有它自己的属性。
原型继承:方法可以,实例属性无法继承;借用构造函数:实例属性可以,方法不行。一起用,完美。
function SuperType(name){
this.name=name;
thi.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.sayAge=function(){
alert(this.age);
}
var instance1=new SubType('zhaoyun',29);
instance1.colors.push('black');
alert(instance1.colors); //'red,blue,green,black'
instance1.sayName();//zhaoyun
instance1.sayAge();//29
var insatance2=new SubType('诸葛瑾',25);
alert(instance2.colrs);'red,blue,green'
instance22.sayName();//诸葛瑾
instance2.sayAge();//25
以上就是小编为大家带来的浅谈js对象的创建和对6种继承模式的理解和遐想全部内容了,希望大家多多支持本站~