我是谁,我来自哪,我是谁的谁
想必大家一定在学习或者开发过程常常被JS独有的原型继承拨过不少脑弦吧,为何不迎问题而上,直面解决这些谜团,今天就来总结一下Js的认亲问题...
为开发需求选择一种合适的继承模式可以提高代码的可读性和便于排错,从而提高我们的开发效率,因而今天就来总结一下ES5之前的继承模式,ES6之后再进行补充或者另外讨论
原型链模式-----------------------------------------------所有继承模式的基础
关键词: 构造函数 原型对象 实例对象 原型对象为另一类型的实例
function SuperType(){ //超类构造函数 this.property = true; } SuperType .prototype.getSuperValue = function(){ //定义超类的原型方法 return this.property; }; function SubType(){ //父类的构造函数 this.subproperty = false; } SubType.prototype = new SuperType(); //重写父类的原型对象,继承超类 SubType.prototype.getSubValue = function(){ //定义父类独有的原型方法 return this. subproperty; }; var instance = new SubType(); //实例化子类对象 alert(instance.getSuperValue()); //true //调用超类的原型方法 }
原型链的问题:
- 超类的实例属性变成了子类的原型属性,无法解决引用类型值被所有实例共享的问题.
- 创建子类型实例时无法向超类型的构造函数中传递参数
以上问题导致很少单独使用原型链.
借用构造函数-------------------------------------------借用超类构造函数
关键词:借用超类构造函数
function SuperType(){ this.name = name; } function SubType(){ SuperType.call(this,"LiAo"); this.age = 22; } var instance = new SubType(); alert(instance.name); //"Nicholas" alert(instance.age); // 22
借用构造函数的问题
- 与构造函数模式一样,无法做到函数复用.
- 另外此时没有使用原型链,所以子类无法调用超类的原型方法
组合继承----------------------------------------结合借用构造函数和原型链
关键词:
- 借用构造函数负责实现对实例属性的继承
- 原型链负责实现对原型属性和方法的继承(复用)
`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; //恢复constructor的指向
SubType.prototype.sayAge = function(){ //定义父类独有的原型方法
alert(this.age);
};
var instance = new SubType("LiAo",22);
`
组合继承模式的优点
- 主要融合了原型链和借用构造函数模式的优点
- 可以用instanceof 和isPrototypeOf()来识别创建的对象类型
组合继承模式的问题 - 无论在什么情况下,都会调用两次超类型构造函数,一次是在子类型实例化的时候,第二次是在借用构造函数的时候,导致父类的原型对象会出现超类的实例属性或方法,解决此问题的办法先留个悬念....
原型式继承-------------------------------------------------------敷衍式代孕
关键词:基于已有对象创建新对象 对象关联 生下就撤
工具函数:
function object(o){ function F(){}; //创建临时类型 F.prototype = o; //让临时类型的原型对象指向传入的对象(继承) return new F(); //返回这个临时类型的实例(返回后就撤!) }
其实仔细观察这个函数,会发现临时类型F是起着代孕的作用,受人委托替传入的妈妈对象生了个宝宝,生完后孩子还没睁眼就悄然离开,所以宝宝只认当前的妈妈,有着妈妈的各种特征.
用法:
var mom = { name = Lily, hairColor: "black", skin:"white" } var baby = object(mom); baby.name = "Jack"; alert(baby.name) ; //"Jack" alert(baby.skin); //"white"
ES5规范通过Object.create()方法规范化原型式继承
该函数接收两个参数:
①用作新对象原型的对象
②为新对象定义额外属性的对象(可选),格式与Object.defineProperties相同,每个属性都是通过自己的描述符定义的.
该函数的缺点:只能定义额外属性,不能定义额外方法
原型式继承的优点
- 抛弃了构造函数,而是把继承简化为对象之间的关联
- 适用于想让一个对象与另一个对象保持类似的情况下使用
原型式继承的缺点
- 通过原型式继承创建的对象是空对象,只不过是关联了另一个对象,但是在很多时候,我们需要对新对象进行加强,而这时候虽然可以在新对象增添特有的属性和方法,但是当我们需要很多个这样的对象的时候,一个个去增添,就显得很麻烦和难看,于是下面要将的寄生式继承可以帮到我们
寄生式继承----------------------------------------------------负责式代孕
关键词: 增强对象 生完培养一会再撤
function createBaby(mom){ var baby = object(mom); baby.sayHi = function(){ alert("hi!); } return baby; }
看完你大概懂了吧,寄生式继承类似原型式继承,只不过在内部对新对象进行增强,这样通过它产生的对象都是增强式的. 就好像一个负责的代孕妈妈,生完之后还要教会孩子一些技能,等到亲妈来了再离开,不过戏剧化的是,这个孩子还是只认亲妈(卧槽)
寄生式继承的优点
- 解决了上面所提到的问题,可以快速新建一批增强式的对象.
寄生组合式继承-----------------------------最完美的继承方式(非最优雅)
关键词:只要超类构造函数的原型对象,不要它本身
相信你还记得上面留下的悬念吧,组合式继承会二次调用超类构造函数,寄生组合式继承就是为了解决这个问题的,其配合一个工具函数使用:
function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); //创建一个新对象,关联超类构造函数的原型对象,不要构造函数 prototype.constructor = subType; //为新对象创建一个constructor指针并正确指向父类构造函数 subType.prototype = prototype; //重写父类构造函数的原型对象为新对象 } 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; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge = function(){ alert(this.age); };
虽然寄生组合式继承是引用类型最理想的继承范式,但是我还是觉得代码还是不够优雅,如果要数优雅程度的话,那就离不开ES6这块巨型语法糖了
上面介绍了的继承模式有:
原型链
借用构造函数
组合继承
原型式继承
寄生式继承
-
寄生组合式继承
其中原型链和借用构造函数模式都有自己比较大的问题,组合继承把两者的优点融合在一起了,因此组合继承比较常见,但是组合继承也有自己的问题,而这个问题在寄生组合式继承中得到解决,寄生组合式继承代码不是那么的优雅,但是是最理想的方式.
原型式继承和寄生式继承是一对儿,他们都抛弃了构造函数,而选择一种新的思维来进行继承------对象关联,这种方式代码最优雅.原型式新建立的对象是一个空对象,寄生式新建的对象则是一个增强的对象(用于大量生产).个人来说,原型式继承和寄生式继承是我比较顺眼的模式 其他模式感觉都是为了使js这么语言向传统的类继承靠近的 其实js的本质就是对象关联,而原型式继承和寄生式继承正体现了这一点 这两种继承各有各的适合情景,原型式继承适合生产少量的对象后自己再进行增强 寄生式继承适合生产大量的有类似功能的对象.
下面是我改写寄生式继承的一个工具函数
function createAndConnect(original,propertyObject){ var clone = Object.create(original); if(typeof propertyObject == "object"&&propertyObject!=null){ for(var key in propertyObject){ clone[key] = propertyObject[key]; } } return clone; }
多增添了一个用于加强对象的参数,就不用每次都得在内部加强了,而且使用方式显得相对优雅一点,如:
var chinese ={ country: "China", language:"Chinese", getLanguage: function(){ console.log("我的母语是"+this.language); }, skin: "yellow", aveHeight:1.71, face:["nose","eyes","mouse"] } var chineseStudent =createAndConnect(chinese,{ name:"LiAo", //独有属性 height:0.1, //独有属性 score: 59, //独有属性 secondLanguage:"English", //独有属性 skin:"a little yellow", //重写属性 getSecondLanguage:function(){ console.log("我的第二门语言是"+this.secondLanguage); } }) console.log(chineseStudent.name); // "LiAo" console.log(chineseStudent.skin); // "a little yellow" console.log(chineseStudent.face); //["nose","eyes","mouse"] chineseStudent.getLanguage(); //我的母语是Chinese chineseStudent.getSecondLanguage(); //我的第二门语言是English
最后的话
在createAndConnect函数(意思是创建和链接)的第二个参数中,你可以指定新对象自己的属性和方法,也可以"重写"原型对象的属性和方法,我认为这个才是js原型继承的精髓即对象关联.同时我也倡导大家用对象关联的思维去开发js应用!