JavaScript Creating Objects — The Constructor Pattern
使用工厂模式创建的JavaScript对象类,总是感觉在语义上有些不爽,不像那些使用构造函数方式,用new运算符创建对象那样正规,有点不走寻常路的感觉。
所以,今天我们就来谈一谈创建JavaScript类的另一个方法——The Constructor Pattern。
Simple To Understand
在ECMAScript构造函数用于创建特定类型的对象,像 Array 和 Object,他们属于本地构造函数(native constructors),与执行环境一起运行。同时,你也可以创建属于你的构造函数以创建自定义对象,并赋予其属性和方法。
我们可以用构造函数模式来重写在工厂模式中中的例子:
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");
在这段代码中,我们使用Person()方法来代替工厂模式中的createPerson()方法。在二者内部实现上,有如下不同:
- There is no object being created explicitly. 在Person中,我们并没有明确的创建对象
- The properties and method are assigned directly onto the this object. 属性和方法都是绑定在this对象上
- There is no return statement. 没有return语句
Different Between Constructor Pattern and Factory Pattern
方法命名:让我们先来看一下方法名,构造函数模式中我们定义方法名为Person,工厂模式中我们定义类名为createPerson。因为按照非官方强制标准,但是属于国际惯例,我们将构造函数的方法名首字母大写。
创建类实例:在构造函数模式中,我们使用new运算符,来构造Person()对象实例。关于new一个新对象的本质,大家可以看一下这里,在此处我再简单说一下。
- Create a new object.
- Assign the scope of the constructor to the new object (so this points to the new object).
- Execute the code inside the constructor (adds properties to the new object).
- Return the new object.
在上面例子的最后,person1和person2是Person对象的两个实例。每个实例化的对象都有一个指向其构造函数的constructor属性。
alert(person2.constructor == Person); //true
constructor属性是为了确定对象类型而使用的。然而,确定一个类型比较靠谱的方式是使用instanceof操作符。因为所以用户自定义对象都继承自Object对象,所以例子中的每个实例化对象,既是Object的实例,也是Person对象的实例:
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
使用构造函数模式创建的对象与工厂模式创建的对象相比,拥有明确的对象类型这是其一大优势。
Use Constructor Like General Function
除了使用new运算符,我们可以像使用其他普通函数一样来使用构造函数吗?答案是“可以”。因为构造函数也是函数。
在下例中,我们使用其他方式来使用Person()构造函数:
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
//call as a function
Person("Greg", 27, "Doctor"); //adds to window
window.sayName(); //"Greg"
//call in the scope of another object
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
第一种方式,使用new运算符来创建一个新的对象,这是构造函数的典型使用方法。第二种方式告诉我们,当不使用new运算符直接调用构造函数时,对象的属性和方法被绑定到window对象上了。这是因为this的问题,有兴趣的朋友可以参考我另一篇日志: The this keyword for JavaScript.同样,第三种方式告诉我们,使用call()或者apply()函数也可以用其他对象调用Person构造函数。
The Problem Of Constructor Pattern
人无完人,方法也一样,使用构造函数创建JavaScript对象也不是完美的。使用构造函数方式最大的问题是与工厂模式一样,其为每个实例化的对象都创建了一个方法。
所以person1和person2都拥有一个sayName()的方法副本。然而这两个副本并非是同一引用。我们要知道,在ECMAScript中,函数是对象。所以,无论任何时候,定义函数就是创建一个Function 对象的实例化对象。从逻辑上,构造函数实际上是这样定义的:
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("alert(this.name)"); //logical equivalent
}
我们可以使用下面的代码验证一下
这实在是很糟糕,因为我听说DDR2内存已经涨到350了,我们实在不想浪费这些内存,没有办法避免吗?我们可以将函数的定义放在构造函数外部以解决此问题:
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()方法被定义在构造函数外部,而构造函数内部,将其赋予sayNamer。现在sayName属性仅仅是指向外部函数的指针,person1和person2在外部作用域上共享sayName()函数。看似很完美,但是我们实在看不出来这个sayName()方法与构造函数有什么关系,这让人看着很郁闷。
也许你不在乎,不过别忘了,我们现在只定义了一个函数,如果有十个?百个?你还能感觉很舒服?好吧,我服了你了。
想要解决此问题,请继续关注我后面的日志(继续卖个关子),The Prototype Pattern。
文章作者:simaopig
本文地址:http://www.xiaoxiaozi.com/2010/04/15/1744/
版权所有 © 转载时必须以链接形式注明作者和原始出处!
我表示我是来坐沙发的…
[回复]
我表示我已经阅读过了,期待继续发展…
[回复]
@andi
有点鄙视你的感觉。呵呵。。
[回复]