1.工厂模式(Factory Pattern)
用函数来封装以特定接口创建对象的细节
function createPerson (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 person1 = createPerson("Nicholas",29,"Software Engineer");
var person2 = createPerson("Greg",27,"Doctor");
函数createPerson()能够根据接受的参数来构建一个包含所有信息的Person对象,可以无数次的调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题,(即怎样知道一个对象的类型)。
2.构造函数模式(Constructor Pattern)
创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job= job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");
Person()函数取代了createPerson()函数:
1. 没有显式的创建对象;
2. 直接将属性和方法赋给this对象;
3. 没有return语句。
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数有以下4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为新对象添加属性);
(4) 返回新对象。
person1和person2 分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person:
alert(person1.constructor===Person);//true
alert(person2.constructor===Person);//true
对象的constructor属性最初是用来标识对象类型的,但是,检测对象类型还是instanceof操作符更可靠一些,上面用Person()函数创建的对象既是Object的实例,也是Person的实例:
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
alert(person2 instanceof Object);//true
alert(person2 instanceof Person);//true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,而这正是构造函数模式胜过工厂模式的地方。
在这个例子中,person1和person2之所以同时是Object的实例,是因为所有对象均继承自Object;
2.1将构造函数当作函数
任何函数,只要通过new操作符来调用,就可以作为构造函数,任何函数,不通过new操作符来调用,那它跟普通函数也不会有什么两样。
//当作构造函数使用
var person = new Person("Nicholas",29,"Software Engineer");
person.sayName();//"Nicholas"
//作为普通函数调用
Person("Greg",27,"Doctor");//添加到window
window.sayName();//"Greg"
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"Wicle",25,"Teacher");
o.sayName();//"Wicle"
2.2构造函数的问题
构造函数的主要问题是:每个方法都要在实例上重新创建一遍,在前面的例子中,person1和person2都有一个名为sayName()的方法,但那两个方法不是同一个function的实例——ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。
从逻辑角度讲,此时的构造函数也可定义为:
function Person(name,age,job){
this.name=naeme;
this.age=age;
this.job=job;
this.sayName=new Function("alert(this.name)");
}
alert(person1.sayName == person2.sayName);//false
3.原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。即prototype就是通过调用构造函数而创建的那个对象实例的原型对象,原型对象可以让所有对象实例共享它所包含的属性和方法。即,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中去。
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName();//"Nicholas"
alert(person1.sayName==person2.sayName);//true
对象的属性和方法是由所有实例共享的。
3.1 原型对象
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。Person.prototype.constructor ->Person,通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法。
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值,如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那就在实例中创建该属性,该属性会屏蔽原型中的那个属性。
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
alert(this.name);
};
var person1=new Person();
var person2=new Person();
person1.name="Greg";
alert(person1.name);//"Greg"——来自实例
alert(person2.name);//"Nicholas"——来自原型
以上例子中,person1和person2的name都能正常返回值,即分别是”Greg“(来自对象实例)和”Nicholas“(来自原型)。当在alert()中访问person1.name时,需要读取它的值,因此就会在这个实例上搜索一个名为name的属性。这个属性确实存在,于是就返回它的值而不必再搜索原型了;当访问person2.name时,并没有在实例上发现该属性,因此就会继续搜索原型,结果在那里找到了name属性。
当为对象添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,即使将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。
不过,delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
alert(this.name);
};
var person1=new Person();
var person2=new Person();
person1.name="Greg";
alert(person1.name);//"Greg"——来自实例
alert(person2.name);//"Nicholas"——来自原型
delete person1.name;
alert(person1.name);//"Nicholas"——来自原型
3.2 原型与in操作符
3.3 更简单的原型语法
function Person(){
}
Person.prototype={
name:"NIcholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};
在上面的代码中,我们将Person.prototype设置为等于一个以对象字面量形式创建的新对象。最终结果相同。
但是,constructor属性不再指向Person。前面说,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性,而这里使用的语法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。因此,尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了
var friend=new Person();
alert(friend instanceof Object);//true
alert(friend instanceof Person);//true
alert(friend.constructor== Person);//false
alert(friend.constructor== Object);//true
//如果constructor的值真的很重要,可以特意设置回适当的值
function Person(){
}
Person.prototype={
constructor:Person,
name:"NIcholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};
注意:以这种方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true。默认情况下,原生的constructor属性是不可枚举的。
3.4原型的动态性
var friend=new Person();
Person.prototype.sayHi=function(){
alert("hi");
};
friend.sayHi();//"hi"(没有问题)
function Person(){
}
var friend=new Person();
Person.prototype={
constructor:Person,
name:"NIcholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};
friend.sayName();//Error
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。
3.5原生对象的原型
3.6原型对象的问题
function Person(){
}
Person.prototype={
constructor:Person,
name:"NIcholas",
age:29,
job:"Software Engineer",
friends:["Shelby","Court"],
sayName:function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court,Van"
alert(person1.friends === person2.friends);//true
4.组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。
构造函数模式用于定义实例属性,
原型模式用于定义方法和共享的属性。
结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。
这种模式还支持想构造函数传递参数。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["Shelby","Court"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1=new Person("Nicholas",29,"Software Engineer");
var person2=new Person("Greg",27,"doctor");
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court"
alert(person1.friends === person2.friends);//false
alert(person1.sayName===person2.sayName);//true