您的位置:首页 > Web前端 > JavaScript

JS创建对象的模式及其优缺点详解

2017-09-05 07:59 459 查看

工厂模式 (Factory)

工厂模式是软件工程领域一种广为人知的设计模式, 它抽象了创建具体对象的过程。

代码演示:

function createPerson(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
alert(this.name);
};
return o;
}

var person1 = createPerson('Victor', 20);
var person2 = createPerson('Angela', 19);


上述代码中,createPerson函数能够根据传入的参数创建一个包含相关信息的Person对象,可以重复调用此函数。每次调用都可以返回含有两个属性以及一个方法的对象。

优点:解决了创建多个相似对象的问题;

缺点:没有解决对象识别的问题,也就是如何知道一个对象的类型的问题。

虽然它能创建多个相似的对象,但是不能识别这个对象属于哪个类型。

构造函数模式(Constructor)

为了弥补工厂模式中不能识别对象类型的缺陷,构造函数模式诞生了。构造函数模式与工厂模式相似。

代码演示:

function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
alert(this.name);
};
}
var person1 = new Person('Victor', 20);
var person2 = new Person('Angela', 19);


上述代码中,Person()函数替代了之前的createPerson()函数,我们还以观察到与工厂模式中的不同:

函数中没有显式地创建对象;

直接将属性和方法复制给了this对象;

没有return语句;

函数名的首字母大写,一般按照惯例,构造函数都是以大写字母开头。这种做法是参照其他面向对象语言,也是要区别ECMAScript的其他函数。构造函数本身也是函数,创建对象时,可以自动返回this对象;

采用new操作符调用构造函数,其实构造函数也可以当作普通函数调用,请看以下代码:

普通函数调用构造函数:

Person('Victor', 20);//添加到window,构造函数一般定义在global对象上,即window对象
window.sayName(); //'Victor'


上述new操作符调用构造函数例子中,person1和person2分别保存着Person的一个同的实例。这两个对象都共有一个constructor属性,该属性指向Person,如下所示:

console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true


对象的constructor属性最初是用来标识对象类型的,但是相比于检测对象类型,还是instanceof操作符比较好。我们创建的所有对象其实都是Object的实例。

console.log(person1 instanceof  Object); //true
console.log(person1 instanceof  Person); //true
console.log(person2 instanceof  Object); //true
console.log(person2 instanceof  Person); //true


优点:弥补工厂模式中不能识别对象类型的缺陷, 可以将构造函数的实例标识为某种特定的类型;

缺点:构造函数中的每个方法都要在实例上重新创建一遍,在上述的代码中,person1和person2都有一个sayName()的方法,但是它们不是同一个function的实例。因为EMCAScript中,每一个函数都是一个对象,也就是说,每定义一个函数就是实例化了一个对象。

...
this.sayName = new Function("alert(this.name)");//这是下面那种定义方法的是指,再new了一个对象

this.sayName = function() {
alert(this.name);
};
...
console.log(person1.sayName == person2.sayName); //false


由于创建两个完成同样任务的function实例没有必要,而且this对象也存在,根本不用在代码执行前就把函数绑定在特定对象上。所以可以把函数定义转移到构造函数外面就可以解决这个问题。

原型模式 (Prototype)

代码演示:

function Person() {
}
Person.prototype.name = 'victor';
Person.prototype.age = 20;
Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
person1.sayName(); //'victor'

var person2 = new Person();
person2.sayName(); //'victor'

alert(person1.sayName == person2.sayName); //true


优点:解决了构造函数中的每个方法都要在实例上重新创建一遍的问题

缺点:如果在原型中定义属性,将被所有实例共享,因为它在省略了尾构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。

function Person() {
}
Person.prototype.name = 'victor';
Person.prototype.age = 20;
Person.prototype.friends = ['Angela', 'Mike', 'Jack'];
Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();
person1.friends.push('Jecy');
alert(person1.friends);//'Angela, Mike, Jack, Jecy'
alert(person2.friends);//'Angela, Mike, Jack, Jecy'
alert(person1.friends === person2.friends); //true


组合使用构造函数模式和原型模式

该模式是创建自定义类型最常见的模式,构造函数用于定义属性,而原型模式用于定义方法和共享的属性。

代码演示:

function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Anglea', 'Mike', 'Jecy'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
}
var person1 = new Person('Victor', 20);
var person2 = new Person('Alex', 21);
person1.friends.push('Jack');
alert(person1.friends);//'Angela, Mike, Jecy, Jack'
alert(person2.friends);//'Angela, Mike, Jecy'
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true


上述代码中,实例属性都是在构造函数中定义的,而所有的实例共享属性constructor和方法sayName()都是在原型中定义的。我们可以看到,修改了person1.friends之后,person2.friends并不会受到影响,这时因为它们引用了不同的数组。

优点:每个实例都会有自己的一份实例属性的副本,但同时有共享着对方发的引用,最大限度地节省了内存。此外还可以向构造函数传递参数。这种模式是当前在EMCAScript中使用最为广泛、认同度最高的一种创建自定义类型的方法,也是一种用来定义引用类型的默认模式。

缺点:目前没有发现任何缺点

动态原型模式(Dynamic Prototype)

原型中如果没有定义方法,就可以在构造函数中,动态加入方法,原型一旦被修改,能够立即在实例中的大反映。

代码演示:

function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Anglea', 'Mike', 'Jecy'];
if(typeof this.sayName != 'function') {
person.prototype.sayName = function() {
alert(this.name);
};
}
var friend = new Person('Jack', 29);
friend.sayName();//'Jack'
}


优点:可以动态定义原型中的方法,不用if语句来检查其中的每个属性和方法,只要检查一个即可。这种模式创建的对象,可以用instanceof操作符来确定它的类型。

注意:使用动态原型模式的时候,不能使用对象字面量重写原型,不然就会切断现有实例与新原型之间的关系。

寄生构造函数模式(Parasitic)

创建一个函数,它的作用仅仅是封装创建对象的代码,然后再返回新创建的对象,类似构造函数。

代码演示:

function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person('Jack', 29);
friend.sayName();//'Jack'


优点:该模式下,返回的对象和构造函数或者其原型属性之间没有任何关系,也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。

缺点:不能依赖instanceof操作符确定对象类型。

稳妥构造函数模式(Durable Constructor)

稳妥对象:没有公共属性,而且其方法也不引用this的对象。它适合在一些安全的环境中该环境禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数模式与寄生构造函数模式类似,但有两点不同

1. 新创建对象的实例方法不引用this;

2. 不适用new操作符调用构造函数。

代码演示:

function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = Person('Jack', 29);
friend.sayName();//'Jack'


优点:安全性得到保障,适合在安全执行环境使用,例如ADsafe和Caja提供的环境。

缺点:除了调用其定义的方法外,没有别的方式可以访问其数据成员。

以上是具前所有的创建对象的模式以及其优缺点分析,一般常用的就是组合使用构造函数模式和原型模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息