组合构造函数+原型模式
这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。
1 //不共享的使用构造函数 2 function Box(name,age){ 3 this.name = name; 4 this.age = age; 5 this.family = ['父亲', '母亲', '妹妹']; 6 }; 7 8 //共享的使用原型模式 9 Box.prototype = { 10 constructor:Box, 11 run : function(){ 12 return this.age+" "+this.name+" "+this.family; 13 } 14 }
原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装到一起。
为了解决这个问题,我们可以使用动态原型模式。
1 function Box(name,age){ 2 this.name = name; 3 this.age = age; 4 this.family = ['父亲', '母亲', '妹妹']; 5 if(typeof this.run != "function"){ 6 Box.prototype.run = function(){ 7 return this.age+" "+this.name+" "+this.family; 8 } 9 } 10 };
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。
这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。
PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。
寄生构造函数
寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
1 function Box(name,age){ 2 var obj=new Object(); 3 obj.name=name; 4 obj.age=age; 5 obj.run=function(){ 6 return this.name+this.age+'运行中...'; 7 }; 8 return obj; 9 }
在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。
1 function myString(string){ 2 var str=new String(string); 3 str.addstring=function(){ 4 return this+',被添加了!'; 5 }; 6 return str; 7 } 8 var box=new myString('Lee'); //比直接在引用原型添加要繁琐好多 9 alert(box.addstring());
在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用this,这里的new是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。
1 function Box(name,age){ 2 var obj=new Object(); 3 obj.run=function(){ 4 return name+age+'运行中...'; //直接打印参数即可 5 }; 6 return obj; 7 } 8 var box=Box('Lee',100); //直接调用函数 9 alert(box.run());
PS:稳妥构造函数和寄生类似
继承
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。
而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
1 function Box(){ 2 this.name = "Lee"; 3 } 4 5 function Desk(){ 6 this.age = 30; 7 } 8 9 Desk.prototype=new Box(); // Desk继承了Box 通过原型链条 10 var desk = new Desk(); 11 console.info(desk.name+" "+desk.age); // Lee 30 12 13 function Table(){ 14 this.level = "j"; 15 } 16 Table.prototype = new Desk(); 17 var table = new Table(); 18 console.info(table.name); // Lee
原型链继承流程图
如果要实例化table,那么Desk实例中有age=100,原型中增加相同的属性age=200,最后结果是多少呢?
1 Desk.prototype.age=200; 2 console.info(table.age); // 30 --->先查找构造函数实例里的属性或方法 如果构造函数实例里没有,则去它的原型对象里找
PS:以上原型链继承还缺少一环,那就是Obejct,所有的构造函数都继承自Obejct。而继承Object是自动完成的,并不需要程序员手动继承。以下为经过继承后的实例,他们的从属关系
1 console.info(table instanceof Object); //true 2 console.info(desk instanceof Table); //false,desk是table的超类 3 console.info(table instanceof Desk); //true 4 console.info(table instanceof Box); //true
在JavaScript里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使
用引用类型的原型,并且子类型还无法给超类型传递参数。
伪造对象、经典继承
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者称为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
1 function Box(age) { 2 this.age = age; 3 this.name = ['哥哥','姐姐','妹妹']; //引用类型,放在构造里就不会被共享 4 } 5 6 function Desk(age) { 7 Box.call(this, age) //对象冒充,给超类型传参 8 } 9 10 var desk=new Desk(200); 11 console.info(desk.name); //["哥哥", "姐姐", "妹妹"] 12 console.info(desk.age); //200 13 desk.name.push("JJ"); 14 console.info(desk.name); //["哥哥", "姐姐", "妹妹", "JJ"] 15 16 var desk2 = new Desk(10); 17 console.info(desk2.name); ////["哥哥", "姐姐", "妹妹"]
1 function Box(name, age) { 2 this.name = name; 3 this.age = age; 4 this.family = ['哥哥','姐姐','妹妹']; //引用类型,放在构造里就不会被共享 5 } 6 7 function Desk(name, age) { 8 Box.call(this, name, age) //对象冒充,对象冒充只能继承构造里的信息 9 } 10 11 var desk = new Desk('Lee', 100); 12 console.info(desk.family); // ["哥哥", "姐姐", "妹妹"] 13 desk.family.push('弟弟'); // ["哥哥", "姐姐", "妹妹", "弟弟"] 14 console.info(desk.family); 15 16 var desk2 = new Desk('Lee', 100); 17 console.info(desk2.family); // ["哥哥", "姐姐", "妹妹"]
组合继承:
借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需要原型链+借用构造函数的模式,这种模式成为组合继承。
1 function Box(age){ 2 this.name=['Lee','Jack', 'Hello'] 3 this.age=age; 4 } 5 Box.prototype.run=function(){ 6 return this.name+this.age; 7 }; 8 function Desk(age){ 9 Box.call(this, age); // 对象冒充 10 } 11 Desk.prototype=new Box(); //原型链继承 12 var desk=new Desk(100); 13 console.info(desk.run());