首页 > JavaScript > JavaScript Creating Objects — The Constructor Pattern
2010四月15

JavaScript Creating Objects — The Constructor Pattern

使用工厂模式创建的JavaScript对象类,总是感觉在语义上有些不爽,不像那些使用构造函数方式,用new运算符创建对象那样正规,有点不走寻常路的感觉。

所以,今天我们就来谈一谈创建JavaScript类的另一个方法——The Constructor Pattern。

Simple To Understand

在ECMAScript构造函数用于创建特定类型的对象,像 Array 和 Object,他们属于本地构造函数(native constructors),与执行环境一起运行。同时,你也可以创建属于你的构造函数以创建自定义对象,并赋予其属性和方法。

我们可以用构造函数模式来重写在工厂模式中中的例子:

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");

在这段代码中,我们使用Person()方法来代替工厂模式中的createPerson()方法。在二者内部实现上,有如下不同:

  1. There is no object being created explicitly. 在Person中,我们并没有明确的创建对象
  2. The properties and method are assigned directly onto the this object. 属性和方法都是绑定在this对象上
  3. There is no return statement. 没有return语句

Different Between Constructor Pattern and Factory Pattern

方法命名:让我们先来看一下方法名,构造函数模式中我们定义方法名为Person,工厂模式中我们定义类名为createPerson。因为按照非官方强制标准,但是属于国际惯例,我们将构造函数的方法名首字母大写。

创建类实例:在构造函数模式中,我们使用new运算符,来构造Person()对象实例。关于new一个新对象的本质,大家可以看一下这里,在此处我再简单说一下。

  1. Create a new object.
  2. Assign the scope of the constructor to the new object (so this points to the new object).
  3. Execute the code inside the constructor (adds properties to the new object).
  4. Return the new object.

在上面例子的最后,person1和person2是Person对象的两个实例。每个实例化的对象都有一个指向其构造函数的constructor属性。

alert(person1.constructor == Person);  //true
alert(person2.constructor == Person);  //true

constructor属性是为了确定对象类型而使用的。然而,确定一个类型比较靠谱的方式是使用instanceof操作符。因为所以用户自定义对象都继承自Object对象,所以例子中的每个实例化对象,既是Object的实例,也是Person对象的实例:

alert(person1 instanceof Object);  //true
alert(person1 instanceof Person);  //true
alert(person2 instanceof Object);  //true
alert(person2 instanceof Person);  //true

使用构造函数模式创建的对象与工厂模式创建的对象相比,拥有明确的对象类型这是其一大优势。

Use Constructor Like General Function

除了使用new运算符,我们可以像使用其他普通函数一样来使用构造函数吗?答案是“可以”。因为构造函数也是函数。

在下例中,我们使用其他方式来使用Person()构造函数:

//use as a constructor
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 对象的实例化对象。从逻辑上,构造函数实际上是这样定义的:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name)");  //logical equivalent
}

我们可以使用下面的代码验证一下

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

这实在是很糟糕,因为我听说DDR2内存已经涨到350了,我们实在不想浪费这些内存,没有办法避免吗?我们可以将函数的定义放在构造函数外部以解决此问题:

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", 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/
版权所有 © 转载时必须以链接形式注明作者和原始出处!

3 Responses to “JavaScript Creating Objects — The Constructor Pattern”

  1. #1 掌柜的马甲 回复 | 引用 Post:2010-04-15 23:11

    我表示我是来坐沙发的…

    [回复]

  2. #2 andi 回复 | 引用 Post:2010-04-16 00:01

    我表示我已经阅读过了,期待继续发展…

    [回复]

  3. #3 simaopig 回复 | 引用 Post:2010-04-16 11:14

    @andi
    有点鄙视你的感觉。呵呵。。

    [回复]

发表评论

:wink: :twisted: :roll: :oops: :mrgreen: :lol: :idea: :evil: :cry: :arrow: :?: :-| :-x :-o :-P :-D :-? :) :( :!: 8-O 8)