创建对象的几种模式
1.工厂模式
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");
优点:解决了创建多个相似对象的问题。
缺点:没有解决对象识别问题(即怎样知道一个对象的类型)
2.构造函数模式
function Person(name,age,job){ this.name=name; othis.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");
与工厂模式相比,这里没有显式地创建对象;直接将属性和方法赋给了this对象;没有return语句。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数应该以一个小写字母开头。在这个例子中,person1和person2分别保存着Person的一个不同实例,这两个对象都有一个constructor(构造函数)属性。任何函数,只要通过new操作来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它和普通函数就没什么两样。
优点:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。
缺点:每个方法都要在每个实例上重新创建一遍。
改进:
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=sayName; } function sayName(){ alert(this.name); }; var person1=new Person("Nicholas",29,"Software Engineer"); var person2=new Person("Greg",27,"Doctor");
将sayName()函数的定义转移到了构造函数外部。而构造函数内部,我们将sayName属性设置成等于全局的sayName函数。这样一来,由于sayName包含的是一个指向函数的指针,因此person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。可是如果对象需要定义很多方法,那么就要定义很多全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言。
3.原型模式
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(); var person2=new Person(); person2.sayName();
alert(person1.sayName==person2.sayName); //true
我们创建的每一个函数都有一个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(); var person2=new Person(); person1.name="Greg"; alert(person1.name);//"Greg"——来自实例 alert(person2.name);//"Nicholas"——来自原型
使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。它是从object继承来的,只在给定属性存在于对象实例中时,才会返回TRUE。上面例子中person1重写了name属性,故调用person1.hasOwnProperty("name");时才会返回true;
大家应该注意到了,前面例子中每添加一个属性和方法就要敲一遍Person.prototype,为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的方法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function Person(){ } Person.prototype={ name:"Nicholas", age:29, job:"software engineer", sayName:function(){ alert(this.name); } };
但是上面的方法会使constructor属性不再指向Person了,为此可以在Person.prototype添加一句,constructor:Person,以确保通过该属性可以访问到适当的值。
优点:可以让所有对象实例共享它所包含的实例和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
缺点:它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。这种共享对于函数非常合适,对于那些包含基本值的属性也说得过去,但对于包含引用类型值的属性来说,问题就比较突出了。如下:
function Person(){ } Person.prototype={ constructor:Person, name:"Nicholas", age:29, job:"Software Engineer", friend:["one","two"], sayName:function(){ alert(this.name); } }; var person1=new Person(); var person2=new Person(); person1.friend.push("three"); alert(person1.friend); //"one,two,three" alert(person2.friend); //"one,two,three" alert(person1.friend===person2.friend);//true
这也是很少看到有人单独使用原型模式的原因所在。
4.组合使用构造函数模式和原型模式
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["one","two"]; } Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } } var person1=new Person("Nicholas",29,"softwarre Engineer"); var person2=new Person("Greg",27,"Doctor"); person1.friends.push("three"); alert(person1.friends); //"one,two,three" alert(person2.friends); //"one,two" alert(person1.friends==person2.friends); //false alert(person1.sayName==person2.sayName); //true
这种组合模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这种组合模式是在ECMAScript中使用最广泛,认同度最高的一种创建自定义类型的方法。
5.动态原型模式
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; if(typeof this.sayName !="function"){ Person.prototype.sayName=function(){ alert(this.name); } } } var friend=new Person("Nicholas",29,"Software Engineer"); friend.sayName();
这里只在sayName()方法不存在的情况下才会将它添加到原型中。这段代码只有在初次调用的时候才会执行。
6.寄生构造函数模式
function Person(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 Person("Nicholas",29,"Software Engineer"); friend.sayName();
通常在前面几种模式都不适用的情况下,可以使用寄生构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。关于寄生构造函数模式,有一点要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数的返回的对象与在构造函数外部创建的对象没有什么不同。为此不能依赖instanceof操作符来确定对象类型。
7.稳妥构造函数模式
function Person(name,age,job){ var o=new Object(); //可以在这里定义私有变量和函数 o.sayName=function(){ alert(name); }; return o; } var friend=Person("Nicholas",29,"Software Engineer"); friend.sayName();
Douglas Crockford发明了javascript中的稳妥对象这个概念。所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境会禁止使用this和new)时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例不引用this;二是不使用new操作符调用构造函数。在上面的例子中,除了sayName()方法外,没有别的方式可以访问其数据成员。