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"); console.log(person1.name); //Nicholas var person2 = createPerson("Sofia",27,"Docter"); person2.sayName(); //Sofia
2.构造函数模式
可通过创建自定义的构造函数,从而定义自定义对象类型的属性和方法。构造函数始终都要应该以一个大写字母开头;要创建一个构造函数的新实例,必须使用new操作符。
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",25,"Chemical Engineer"); var person2 = new Person("Sofia",28,"Executive Manager"); console.log(person1 instanceof Object); //true console.log(person1 instanceof Person); //true console.log(person1.sayName==person2.sayName); //false
使用构造函数模式可以确定对象的类型,但是上面像这样将对象方法写入构造函数内部会使得每个方法都要在每个实例上重新创建一遍。以这种方式创建的函数,会导致不同的作用域链和标识符来解析,因此不同实例上的同名函数是不相等的。
然而这样是完全没有必要的,创建任务相同的不同的Function实例。可将函数定义在构造函数外部,来解决这个问题。
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",25,"Chemical Engineer"); var person2 = new Person("Sofia",28,"Executive Manager"); console.log(person1.sayName()); //Nicholas console.log(person2.sayName()); //Sofia console.log(person1 instanceof Object); //true console.log(person1 instanceof Person); //true
但是如果对象需要定义很多方法,就需要定义很多全局函数,那么这个自定义的引用类型就毫无封装性可言了。
3. 原型模式
每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,也就是原型对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。这样可以通过将属性和方法直接添加到原型对象中来让所有的对象实例来共享。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Chemical Engieer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //Nicholas var person2 = new Person(); console.log(person2.age); //29 console.log(Person.prototype.isPrototypeOf(person1)); // true console.log(Object.getPrototypeOf(person1)==Person.prototype); //true person1.name = "Lisa"; console.log(person1.name); Lisa console.log(person1.hasOwnProperty("name")); //true
在默认情况下,所有原型对象都会自动获取一个constructor(构造函数)属性,这个属性是一个指向prototype属性 所在函数的指针。创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性。当调用构造函数创建一个新实例后,该实例的内部包含一个指针(内部属性),指向构造函数的原型对象,在ES5中叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但FireFox、Safari和Chrome在每个对象上都支持一个属性_proto_。
但是当重写整个原型对象时,即将Person.prototype设置为一个新对象时(如下),constructor属性也变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
Person.prototype={ name:"Nicholas", age: 29, job: "Software Engineer", sayName: function(){ alert(this.name); } }
如果constructor属性很重要,可以在新原型对象上加上constructor:Person, 但是这样constructor属性的[[Enumerable]]特性被设置为true。默认情况下是不可枚举的,false。
可以通过isPrototypeOf()方法确定某对象实例是否属于某对象原型。如果实例的[[Prototype]]指向调用isPrototypeOf()方法的对象,那么这个方法返回true。
ES5中,增添了一个新方法,Object.getPrototypeOf(),这个方法返回[[Prototype]]的值。
使用hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中。这个方法只有在给定属性存在于对象实例中时,才会返回true。
4.构造函数模式和原型模式组合使用
这种构造函数和原型混合的模式,目前子啊ECMAScript中使用最广泛的一种创建自定义类型的方法。每个实例都会有自己的一份实例属性的副本,同时也共享着方法的引用,极大程度低节省了内存。
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby","Court"]; }
Person.prototype={
construtor: Person,
sayName: function{
alert(this.name);
}
}
var person1=new Person("Nicholas",29,"Software Engineer");
var person2=new Person("Greg",27,"Chemical Engineer");
person1.friends.push("Sofia");
console.log(person1.friends); //["Shelby","Court","Sofia"]
console.log(person2.friends); //["Shelby","Court"]
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); } }
6.稳妥构造函数模式
稳妥对象,指的是没有公共属性,而且方法也不引用this的对象。稳妥对象最适合在一些安全的环境(环境中会禁止使用this和new)中,或者在防止数据被其他应用程序改动时使用。
function createPerson(name,age,job){ var o = new Object(); //定义私有变量和函数 //添加方法 o.sayName = function() { alert(name); }; //返回对象 return o; } var friend=Person("Sofia",25,"Chemical Engineer"); friend.sayName(); //"Sofia"
在上述的例子中,除了调用sayName()方法,没有别的方式可以访问到传入到构造函数中的原始数据成员。