工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。考虑到在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封闭以特定接口创建对象的细节,如下面的例子所示。
<script type="text/javascript"> 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"); </script>
函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。随着JavaScript的发展,又一个新模式出现了。
构造函数模式
前几章介绍过,ECMAScript中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如,可以使用构造函数模式将前面的例子重写如下。
<script type="text/javascript"> 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"); </script>
在这个例子中,Person()函数取代了createPerson()函数。我们注意到:Person()中的代码除了与createPerson()中相同的部分外,还存在以下不同之处:
此外,还应该注意到函数名Person使用的是大写字母P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他OO语言,主要是为了区别于ECMAScript中的其他函数,因为构造函数本身也是函数,只不过可以用来创建对象而已。要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
在前面例子的最后,person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示。
<script type="text/javascript"> alert(person1.constructor==Person); alert(person2.constructor==Person); </script>
对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符要更可靠一些。我们在这个例子中创建的所有对象既是Object的实例,同时也是Person的实例,这一点通过instanceof操作符可以得到验证。
<script type="text/javascript"> alert(person1 instanceof Object); alert(person1 instanceof Person); alert(person2 instanceof Object); alert(person2 instanceof Person); </script>
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,而这正是构造函数模式胜过工厂模式的地方。在这个例子中,person1和person2之所以同时是Object的实例,是因为所有对象均继承自Object。以这种方式定义的构造函数是定义在Global对象(在浏览器中是window对象)中的。