虽然Object构造函数或对象字面量均可以用来建立单个对象,但这种方式有个弊端:使用同一个接口建立不少对象,会产生大量的重复代码。为了解决这个问题,因而百家争鸣,各类工厂模式的变体应运而生。程序员
1.工厂模式设计模式
这种模式是软件工厂领域的广为人知的设计模式,它抽象了建立具体对象的过程,用函数来封装以特定接口建立对象的细节,举个栗子:数组
1 function createPerson(name,age,job){
2 var o=new Object(); 3 o.name=name; 4 o.age=age; 5 o.job=job; 6 o.sayname = function(){ 7 alert(this.name); 8 }; 9 return o; 10 } 11 12 var person1=createPerson("Sleipnir",23,"Software"); 13 var person2=createPerson("xiaoxiao",24,"Student");
这个模式虽然能够无数次调用,解决了建立多个类似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的类型)函数
2.构造函数模式this
咱们先用构造函数模式把工厂模式的例子重写一遍:spa
1 function Person(name,age,job){
2 this.name=name; 3 this.age=age; 4 this.job=job; 5 this.sayname = function(){ 6 alert(this.name); 7 }; 8 } 9 10 var person1=new Person("Sleipnir",23,"Software"); 11 var person2=new Person("xiaoxiao",24,"Student");
跟以前的工厂模式,咱们能够看出区别:1.没有在函数内再建立对象prototype
2.直接将属性和方法赋给了this对象设计
3.没有return语句指针
在建立Person的实例时,必须用到new操做符。以这种方式调用构造函数会经历如下4个步骤:1.建立一个新对象 2.将构造函数的做用域赋值给新对象(所以this就指向了这个新对象) 3.执行构造函数中的代码(为新对象添加属性和方法) 4.返回新对象code
刚才说了,构造函数胜于工厂模式的大方在于,它能解决实例的对象类型问题,未来能够将它的实例标识为一种特定类型:
1 console.log(person1 instanceof Person); //true 2 console.log(person1 instanceof Object]); //true 3 console.log(person2 instanceof Person); //true 4 console.log(person2 instanceof Object); //true 5 console.log(Person instanceof Object); //true
person1和person2之因此同时是Object的实例,是由于全部对象均继承自Object(下面会讲到继承)
2.1 调用构造函数的方式
上面已经写了Person的构造函数,咱们来用几种不一样的方式调用:
1 //当作构造函数使用
2 var person= new Person("Sleipnir",23,"Software");
3 person.sayname(); //"Sleipnir"
4
5 //做为普通函数使用
6 Person("xiaoxiao",25,"Student"); //添加到全局window
7 window.sayname(); //"xiaoxiao"
8
9 //在另外一个对象的做用域中调用
10 var o=new Object(); 11 person.call(o,"xiaoxiao",25,"Student"); 12 o.sayname(); //"xiaoxiao"
第一种是当作构造函数使用,以前已经提过,;第二种是做为普通函数调用,由于this对象都是指向全局对象,因此属性和方法都被添加了window对象;第三种是在o对象的做用域中使用call()方法来调用Perso函数
2.2 构造函数的问题
构造函数虽然不错,但也有瑕疵。主要问题就是每一个方法须要在每一个实例上从新建立一遍,可是,若是在构造函数里面建立Function实例或者在构造函数外部定义函数来供构造函数建立的实例调用的话,那咱们所谓的构造函数的全局做用域就名存实亡,自定义的引用类型包括定义的方法也就没有封装性可言了。
好在程序员们都是不服输的人,历史的车轮老是向前滚动,因而原型模式就登上历史舞台了。
3.原型模式
概念先不说,举个栗子再解释:
1 function Person(){
2 } 3 Person.prototype.name="Sleipnir"; 4 Person.prototype.age=23; 5 Person.prototype.job="Software"; 6 Person.prototype.sayname=function(){ 7 alert(this.name); 8 }; 9 10 var person1=new Person(); 11 person1.sayname(); //"Sleipnir" 12 var person2=new Person(); 13 person2.sayname(); //"Sleipnir" 14 15 alert(person1.sayname==person2.sayname);
咱们建立的每个函数都有一个prototype(原型)属性,这个属性是个指针,指向一个对象。因此,prototype就是经过调用构造函数而建立的那个对象实例的原型对象。上面例子中的person1和person2所拥有的属性和方法都是来源于构造函数的原型对象中的
想要理解原型模式,就要理解【构造函数】【原型】【实例】这三种之间的关系,抽象出来简单来讲,原型是构造函数的属性,而实例是经过构造函数的原型而建立的,实例和构造函数没有关系,原型里还有一个constructor是指向构造函数的,constructor就是相似于指针的存在,构造函数经过constructor的指针做用,就把原型和实例链接起来了。这也是最简单的原型继承关系。
3.1 访问实例/原型的属性
有时候,咱们根据原型建立的实例,这个实例里有一部分是原型的属性,有一部分也是实例本身的属性,因而就要判断哪些属性属于原型,哪些属性属于实例本身
有两个方法:hasOwnProperty() 和 in操做符
先看hasOwnProperty():
1 function Person(){
2 } 3 Person.prototype.name="Sleipnir"; 4 Person.prototype.age=23; 5 Person.prototype.job="Software"; 6 Person.prototype.sayname=function(){ 7 alert(this.name); 8 }; 9 10 var person1=new Person(); 11 var person2=new Person(); 12 13 console.log(person1.hasOwnProperty("name")); //false 14 15 person1.name="xiaoxiao"; 16 console.log(person1.name); //"xiaoxiao" 17 console.log(person1.hasOwnProperty("name")); //true 18 19 console.log(person2.name); //"Sleipnir" 20 console.log(person2.hasOwnProperty("name")); //false 21 22 delete person1.name; 23 console.log(person1.name); //"Sleipnir" 24 console.log(person1.hasOwnProperty("name")); //false
从代码结果能够总结出来,hasOwnProperty()检测的是实例属性,若是是属于实例的,返回true,不然返回false
in操做符:
function Person(){
}
Person.prototype.name="Sleipnir"; Person.prototype.age=23; Person.prototype.job="Software"; Person.prototype.sayname=function(){ alert(this.name); }; var person1=new Person(); var person2=new Person(); console.log(person1.hasOwnProperty("name")); //false console.log("name" in person1); //true person1.name="xiaoxiao"; console.log(person1.name); //"xiaoxiao" console.log(person1.hasOwnProperty("name")); //true console.log("name" in person1); //true console.log(person2.name); //"Sleipnir" console.log(person2.hasOwnProperty("name")); //false console.log("name" in person2); //true delete person1.name; console.log(person1.name); //"Sleipnir" console.log(person1.hasOwnProperty("name")); //false console.log("name" in person1); //true
经过互相比较,咱们能看出来,直接使用in操做符是,不管属性是实例本身的仍是原型的,都会返回true
咱们来定义一个函数,组合使用hasOwnProperty()和in操做符:
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object);
}
1 function Person(){
2 } 3 Person.prototype.name="Sleipnir"; 4 Person.prototype.age=23; 5 Person.prototype.job="Software"; 6 Person.prototype.sayname=function(){ 7 alert(this.name); 8 }; 9 function hasPrototypeProperty(object,name){ 10 return !object.hasOwnProperty(name)&&(name in object); 11 } 12 var person=new Person(); 13 console.log(hasPrototypeProperty(person,"name")); //true 14 15 person.name="xiaoxiao"; 16 console.log(hasPrototypeProperty(person,"name")); //false
3.2 更简单的原型语法
使用Person.prototype一个个定义属性太繁琐,因而咱们能够用对象字面量给原型定义属性:
1 function Person(){
2 } 3 Person.prototype={ 4 constructor:Person, 5 name:"Sleipnir", 6 age:23, 7 job:"Software", 8 sayname: function(){ 9 alert(this.name); 10 } 11 };
这里要注意,constructor要设置为Person,否则就会切断原型与实例之间的关系了
3.3 原型模式的问题
原型模式省略了为构造函数传递初始化参数这一环节,结果全部实例在默认状况下都取得了相同的属性值。
原型模式的问题在于其共享的本质,咱们来看一个因为这个缘由而引发问题的栗子:
function Person(){
}
Person.prototype={ constructor:Person, name:"Sleipnir", age:23, job:"Software", friends:["zhangsan","lisi"], sayname: function(){ alert(this.name); } }; var person1=new Person(); var person2=new Person(); person1.friends.push("van"); console.log(person1.friends); //"zhangsan,lisi,van" console.log(person2.friends); //"zhangsan,lisi,van" console.log(person1.friends===person2.friends); //true
这个例子中,friends是个引用类型的数组,因为在原型中已经定义了,因此在实例中作修改时,修改的值也会映射到其原型,而后原型又会同时将数据更新加载到其余实例中,这也体现了原型的动态性。但这种效果并非咱们想要的。
实际需求是,原型帮咱们定义一部分公共属性和方法,而后实例本身也有独立的属性和方法,基于这种状况,便有了下面的模式。
4.组合使用构造函数模式和原型模式
在这种模式下,构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每一个实例都有了本身的一份实例属性的副本,同时也共享着对方法的引用,最大限度地节省内存,同时这种模式还支持向构造函数传递参数,可谓集两种模式之长,咱们来看看用这个方式重写的上面的例子:
1 function Person(name,age,job){
2 this.name=name; 3 this.age=age; 4 this.job=job; 5 this.friends=["zhangsan","lisi"]; 6 } 7 Person.prototype={ 8 constructor:Person, 9 sayname: function(){ 10 alert(this.name); 11 } 12 } 13 14 var person1=new Person("Sleipnir",23,"Software"); 15 var person2=new Person("xiaoxiao",25,"Student"); 16 17 person1.friends.push("van"); 18 console.log(person1.friends); //"zhangsan,lisi,van" 19 console.log(person2.friends); //"zhangsan,lisi" 20 console.log(person1.fgriends===person2.friends); //false 21 console.log(person1.sayname===person2.sayname); //true
这种构造模式和原型混合的模式,是目前使用最多的一种方法,能够说,这是用来定义引用类型的一种默认模式。
5.动态原型模式
这种模式是解决了在构造器中查看并初始化原型的问题,它的动态性在于,在构造器中检查一下原型中是否存在某个方法,若是不存在,就创建下,创建一次就好了,一劳永逸,这就是其方便之处,咱们一样来看个例子:
1 function Person(name,age,job){
2 this.name=name; 3 this.age=age; 4 this.job=job; 5 6 //检查原型中是否有方法 7 if(typeof this.sayname!="function"){ 8 Person.prototype.sayname=function(){ 9 alert(this.name); 10 }; 11 } 12 }