1. 工厂模式
ECMAScript中无法创建类,因此用函数封装以特定接口创建对象的细节。
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("Mary",25,"Software Engineer");
var person2 = createPerson("Mike",26,"Doctor");
工厂模式解决了创建多个相似对象的问题;
缺点:但没有解决对象识别的问题,即无法知道一个对象的类型。
2. 构造函数模式
ECMAScript中的构造函数可用来创建特定的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中;也可以创建自定义的构造函数(名称首字母大写),定义自定义对象类型的属性和方法,如下:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; }
var person1 = new Person("Mary",25,"Software Engineer");
var person2 = new Person("Mike",26,"Doctor");
优点:创建自定义的构造函数使我们将来可以将它的实例标识为一种特定的类型,即可以进行对象识别;
缺点:每个方法都要在每个实例上重新创建一遍,而创建多个同样任务的Function实例是不必要的。
如上例中,person1和person2都有一个名为sayName()的方法,但这两个方法不是同一个Function的实例,即不同实例上的同名函数的不相等的。
一种不好的解决方案:this对象的存在使我们不必在执行代码前把函数绑定到特定的对象,可以把函数定义转移到构造函数外部。(但这么做会把该对象的函数作为全局函数,如果对象有很多函数,就会产生很多全局函数,这个自定义的引用类型就没有封装性可言了)。
构造函数模式和工厂模式的不同之处:
1)没有显式创建对象;
2) 直接将属性和方法赋给了this对象;
3)没有return语句。
要创建Person的新实例,必须使用new操作符(任何函数通过new操作符来调用,均可以作为构造函数)。以这种方式调用构造函数实际上会经历4个步骤:
1)创建一个新对象;
2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
3)执行构造函数中的代码(为这个新对象添加属性);
4)返回新对象。
注意:以这种方式定义的构造函数是定义在Global对象(浏览器中是window对象)中的。
全局作用域中调用一个函数时,this对象总是指向Global对象的(浏览器中是window对象)。
3. 原型模式
每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
function Person(){ } Person.prototype.name = "Mary"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Mary" var person2 = new Person(); person2.sayName(); //"Mary" alert(person1.sayName == person2.sayName); //ture
优点:可以让所有对象实例共享它所包含的属性和方法。原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。
缺点:,它省略了为构造函数传递初始化参数这一环节,结果所有实例在 默认情况下都将取得相同的属性值(但这不是最大缺点);原型模式的大问题是由其共享的本性所导致的,特别是对于包含引用类型值的属性。如下例:
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
更简单的原型语法
function Person(){ } Person.prototype = { name:"Mary", age:29, job:"Software Engineer", sayName:function(){ alert(this.name); } };
但是要注意,使用该原型语法后,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 == Object); //true alert(friend.constructor == Person); //false
如果 constructor 的值真的很重要,可以像下面这样特意将它设 置回适当的值。
function Person(){ } Person.prototype = { constructor:Person, name:"Mary", age:29, job:"Software Engineer", sayName:function(){ alert(this.name); } };
注意:js中,为实例与原型 之间的连接只不过是一个指针,而非一个副本,调用构造函数时会为实例添加一个指向初原型的 [[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与初原型之间的联系。所以以上定义的原型,实例访问不到。
以上述方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。默认 情况下,原生的 constructor 属性是不可枚举的。使用兼容 ECMAScript 5的 JavaScript引 擎,可以用以下方式解决。
Object.defineProperty(Person.prototype,"constructor",{ enumerable:false, value:Person });
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,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
优点:每个实例都会有自己的一份实例属性的副本,但又共享着对方法的引用,最大限度的节省了内存;另外这种混合模式还支持向构造函数传递参数。(创建对象常用的方式)
缺点:有其他 OO语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。
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();
优点:解决了构造函数和原型的分离,采用这种模式创建的对象,还可以使 用 instanceof 操作符确定它的类型。
注意:使用动态原型模式时,不能使用对象字面量重写原型,因为在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
6. 寄生构造函数模式
通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式 的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;
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(); //"Nicholas"
除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实 是一模一样的。
构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值。
应用场景:在特殊的情况下用来为对象创建构造函数。如:创建一个具有额外方法的特殊 数组。由于不能直接修改 Array 构造函数,因此可以使用这个模式。
function SpecialArray(){ //创建数组 var values = new Array(); //添加值 values.push.apply(values, arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); }; //返回数组 return values;
} var colors = new SpecialArray("red", "blue", "green"); alert(colors.toPipedString()); //"red|blue|green"
注意:寄生构造函数模式中,返回的对象与构造函数或者与构造函数的原型属 性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。不能依赖 instanceof 操作符来确定对象类型。
由于存在上述问题,我们建议在可以使用其他模式的情 况下,不要使用这种模式。
7. 稳妥构造函数模式
稳妥对象(durable objects):指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象适合在 一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的 实例方法不引用 this;二是不使用 new 操作符调用构造函数。
function Person(name, age, job){ //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ alert(name); }; //返回对象 return o; }
//使用稳妥的 Person 构造函数 var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
变量 friend 中保存的是一个稳妥对象,而除了调用 sayName()方法外,没有别的方式可 以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传 入到构造函数中的原始数据。
注意:,使用稳妥构造函数模式创建的对象与构造函数之间也 没有什么关系,因此 instanceof 操作符对这种对象也没有意义。
javascript高级程序设计-创建对象-总结