以前写过一篇关于JS创建对象的帖子,JS创建对象的几种方法,突然想温习一下,所以写了下面的小例子,用来回顾这七种create pattern。
每种pattern都有自己的特色。
1 工厂模式中,在构造函数内部用 o={}创建一个新对象,最后返回这个对象。当实例化时,不需要用new关键字,就像调用一般的方法一样。
我们可以把create函数设想成一个贴牌工厂,面对不同的需求,制作出内在相同,标签不同的产品。
工厂模式最大的问题是容易和普通函数混淆,方便归方便,但我们只能通过命名来确认它是一个构造函数。
/*1.factory pattern*/ function createPerson(name,age,job){ var o = {}; //new object o.name=name; o.age=age; o.job=job; o.friends=["ansel","suisa"]; o.sayName=function(){ alert("factory pattern:"+this.name); }; return o; } var tanya= createPerson("tanya","28","Software Engineer"); tanya.sayName(); /*tips: o={} inside the create function no "this" in the create function */
2 构造函数模式中,用new关键字来实例化对象,在构造函数内部使用this,无需返回任何对象。
构造函数相比工厂函数更加OO啦,并且可以将它的实例标识作为一种特定的类型。像String,Array一样的使用自定义的类型。
但是,它的问题是不断的拷贝拷贝,每new一次就造出一份副本,每个方法都要在每个实例上重新创建一遍,不同实例的同名函数是不相等的(工厂模式也有此问题),
都说未来世界是个资源共享,专业细分的社会,JS OO的世界中当然应该体现这种共享性,于是Eric发明了原型链。
/*2.constructor pattern */ function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert("constructor pattern:"+this.name); }; } var ansel = new Person("ansel","28","Software Engineer"); ansel.sayName(); /*tips: new in entities this in constructor */
3 原型模式中,最特色的当然是prototype。但是原型仅在需要share的时候好用,可是人不能是完全透明的,也需要一点点隐私。所以最为常用的模式一定是混合型的。
/*3.prototype pattern*/ function Tanya(){ } Tanya.prototype.name="tanya"; Tanya.prototype.age="28"; Tanya.prototype.sayName= function(){ alert("prototype pattern:"+this.name); } var person1= new Tanya(); person1.sayName(); /*tips: public share of all the entities if no return ,then need new. else you may use return. */
4 构造函数模式和原型模式的混合类型。特点就是想隐私,那么就用构造函数模式去copy封装在自己的包里,想要share的地方就用prototype。
/*4.hybrid constructor & prototype Pattern*/ function Student(name,sno){ this.name=name; this.sno=sno; this.sayName=function(){alert("hybrid constructor & prototype Pattern:"+this.name);} } //Student.prototype.teacher=["ansel","tanya"]; //Student.prototype.sayTeacher=function(){alert("hybrid constructor & prototype Pattern:"+this.teacher);}; Student.prototype={ constructor:Student, teacher:["ansel","tanya"], sayTeacher:function(){alert("hybrid constructor & prototype Pattern:"+this.teacher);} } var wang = new Student("wang","12"); var li = new Student("li","13"); wang.sayName(); li.sayName(); wang.sayTeacher(); li.sayTeacher(); /*tips: something you need to be public share,and something you want to be private. we often use this mode,because anything never goes in extrmely way. */
5 有些时候别人做过的事情我们不想再做,如果别人还没有做,那么我们就去做一遍,这种情况就是动态原型模式,无非就是if 没这个函数...do prototype。
/*5.dynamic prototype pattern*/ function Dogs(name){ this.name=name; this.showName=function(){ alert(this.name); } if (typeof this.shout != "function") { Dogs.prototype.shout=function(){ alert("dynamic prototype pattern:wangwang"); } } } var xixi=new Dogs("xixi"); var mimi=new Dogs("mimi"); xixi.showName(); xixi.shout(); mimi.showName(); mimi.shout(); /*tips: sometimes,function is already defined ,if it already defined ,you may not define again, else you should make prototype attributes functions dynamically,. */
6 寄生构造函数模式更像是工厂模式,几乎一模一样,不过在每次实例化对象的时候用了一个 new。这种模式的作用是在特殊情况下用来为对象创建构造函数。
但是就像它的名字非常的怪异一样,这不是一种被推崇的创建对象模式,因为不能依赖 instanceof 操作符来确定对象的类型,
红皮书上的解释是:构造函数返回的对象与在构造函数外部创建的对象没有什么不同。
那么我就姑且理解成这种模式对外看不到创建它的对象的类型,所有通过此模式创建的实例都像一个个找不到厂家的无牌商品,工厂模式也有这样的问题。
/*6.parasitic constrctor pattern (hybird factory)*/ function Person1(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person1("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas" /*tips: similar to factory pattern,but use "new" in entities */
7 稳妥构造函数模式,这种模式的设计初衷是绝对保守的,他的目的是安全,手段是不用this,不用new,和工厂模式、寄生构造函数模式一样instanceof失效,
保守的人永远成不了气候,所以这是一种方法,但不会是主流。
/*7.durable constructor pattern*/ function Person2(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(name); }; return o; } var friend = Person2("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas" /*tips: Be sure no this and new in your code except o=new Object(). */
红皮书下面的章节是关于对象继承的,个人觉得相比创建对象,对象继承显得更为重要,不过前者是基础。
很多OO都支持两种继承,分别是接口继承和实现继承,很遗憾ECMAScript无法实现接口继承,它的继承主要依靠原型链。
一 原型链继承
关于原型链,我以前写过一篇帖子 js原型链原理看图说话 。在这篇帖子中介绍了每个实例中都默认拥有属性prototype,在很多浏览器中与其对应的属性叫__proto__。
学过链表的人很好理解,如果我要认某个人当爸爸,我只要把指针指向他,由于每个实例的链表中都只有一个prototype属性,所以通过原型链只能认一个爸爸。
按这个逻辑,通过原型链应该无法实现多重继承!写个例子验证一下。
<!DOCTYPE html> <html> <head> <title>apply,call,bind</title> <script type="text/javascript"> /*I often confused with the three function, this example get a deep research of them*/ function baseType1(){ this.name="tanya"; } function baseType2(){ this.age="28"; } function subType(){ } subType.prototype=new baseType2(); subType.prototype=new baseType1(); var instance = new subType(); alert(instance.name); alert(instance.age); </script> </head> <body> </body> </html>
果然,后一个原型的赋值会覆盖前一个,也就是说通过原型链只能继承离实例化最近的一个原型对象。
原型链继承的本质就是一个单链表的深度搜索。
1 搜索实例
2 搜索subType.prototype
3 搜索baseType.prototype
一环一环,直到搜索在prototype属性等于null时,才会停下。
有几点需要记住的:
1 所有的对象都继承自Object对象,就像所有的DOM对象都继承自window对象一样,Object对象提供了很多我们喜欢的方法。
2 确定原型和实例的关系可以使用instanceof操作符和isPropertyOf()方法。
3 在原型链上重写方法会屏蔽原来的方法。
4 通过原型链条实现继承时,不能使用对象字面量创建原型方法。因为对象字面量可以理解成一个实实在在的对象,对象可以成为原型但是会切断原型链。
5 由于js中的引用类型实际上保存的是一个指针,所以在原型链上任意一个引用类型值的原型属性都会被所有实例共享。
下面的例子说明,如果是基本类型值不会被所有实例共享:
function baseType1(){ this.name="tanya"; } function subType(){ } subType.prototype=new baseType1(); var instance = new subType(); var instance2 = new subType(); instance2.name="ansel"; alert(instance.name); //tanya alert(instance2.name); //ansel
但若替换成引用类型,就相当于所有实例共享引用的属性啦
function baseType1(){ this.name=["tanya","ansel"]; } function baseType2(){ this.age="28"; } function subType(){ } subType.prototype=new baseType1(); var instance = new subType(); var instance2 = new subType(); instance.name.push("xixi"); alert(instance.name);//tanya,ansel.xixi alert(instance2.name);//tanya,ansel,xixi
因为原型链继承中引用类型 有这种不分你我 不分彼此 的状态,所以 constructor stealing技术被引入。
二 借用构造函数继承(constructor stealing)
constructor stealing技术的基本思想是:在子类型构造函数的内部调用基类的构造函数,即在subType中调用 baseType.call(this)来实现继承。
通过这种技术,可以在子类型的构造函数中向父类传递参数。并且在子类型内部复制引用类型,保证引用类型的不共享。
其本质就是在subType中调用 baseType.call(this) 在subType内部clone了baseType的所有的属性和方法。看看下面的例子:
function baseType(){ this.name=["tanya","ansel"]; } function subType(){ baseType.call(this); } subType.prototype=new baseType(); var instance1 = new subType(); var instance2 = new subType(); instance1.name.push("xixi"); alert(instance1.name);//tanya,ansel.xixi alert(instance2.name);//tanya,ansel
但是这种技术的问题在于在超类型的原型中定义的方法对于子类型是不可见的,这就意味着所有的类型都只能使用构造函数模式。
三 组合继承模式(原型链+借用构造函数)
最常用的办法肯定是折衷的产物,于是结合了原型链的优势和constructor stealing的优势的组合继承模式应运而生。
组合继承模式有一个约定,那就是,用原型链实现对原型属性和方法的继承,而使用call或者apply来实现对实例属性的继承。
四 原型式继承
原型继承的本质是:
fuction object(o){ function F(){} F.prototype=o; return new F(); }
原型式继承相当于复制父类型,但是如红皮书上所说,仅仅是浅复制。因为引用类型相当于复制了引用地址,和原型模式一样引用类型的属性会被共享。
在ES5中新增了Object.create()方法规范了原型式继承。Object.create()的本质就是上面的obect(o)函数。
五 寄生式继承
前文创建对象部分介绍了寄生式模式,其特点是和工厂模式几乎一样,但会用new创建对象,之所以说是寄生,因为那种创建对象的模式一般用于为已有的原生对象创建构造函数.
寄生式继承的思路和寄生构造函数和工厂模式是一样的,即创建一个仅用于封装继承过程的函数。
var tanya={ name:"tanya", age:28, friends:["ansel","sunny","funny"] } function createAnother(o){ var clone=o; o.sayHi=function(){alert("hello");} return clone; } var anotherPerson1=createAnother(tanya); var anotherPerson2=createAnother(tanya); anotherPerson1.friends.push("dire"); //anotherPerson2.friends.pop("funny"); anotherPerson1.sayHi(); alert(anotherPerson1.friends); alert(anotherPerson2.friends);