自定义构造函数

除了对象字面量和内置构造函数之外,你也可以通过自定义的构造函数来创建对象实例,正如下面的代码所示:

  1. var adam = new Person("Adam");
  2. adam.say(); // "I am Adam"

这种写法非常像Java中用Person类创建了一个实例,两者的语法非常接近,但实际上JavaScript中没有类的概念,Person()是一个函数。

Person()构造函数是如何定义的呢?看下面的代码:

  1. var Person = function (name) {
  2. this.name = name;
  3. this.say = function () {
  4. return "I am " + this.name;
  5. };
  6. };

当你通过new来调用这个构造函数时,函数体内将发生这些事情:

  • 创建一个空对象,将它的引用赋给this,并继承函数的原型。
  • 通过this将属性和方法添加至这个对象。
  • 最后返回this指向的新对象(如果没有手动返回其他的对象)。

用代码表示这个过程如下:

  1. var Person = function (name) {
  2. // 使用对象字面量创建新对象
  3. // var this = {};
  4. // 添加属性和方法
  5. this.name = name;
  6. this.say = function () {
  7. return "I am " + this.name;
  8. };
  9. //return this;
  10. };

上例中,为简便起见,say()方法被添加至this中,结果就是不论何时调用new Person(),在内存中都会创建一个新函数(say()),显然这是效率很低的,因为所有实例的say()方法是一模一样的。最好的办法是将方法添加至Person()的原型中。

  1. Person.prototype.say = function () {
  2. return "I am " + this.name;
  3. };

我们将会在下一章里详细讨论原型和继承,现在只要记住将需要重用的成员放在原型里即可。

关于构造函数的内部工作机制也会在后续章节中有更细致的讨论。这里我们只做概要的介绍。刚才提到,构造函数执行的时候,首先创建一个新对象,并将它的引用赋给this

  1. // var this = {};

其实事实并不完全是这样,因为“空”对象并不是真的空,这个对象继承了Person的原型,看起来更像:

  1. // var this = Object.create(Person.prototype);

在后续章节会进一步讨论Object.create()

构造函数的返回值

当使用new调用的时候,构造函数总是会返回一个对象,默认情况下返回this所指向的对象。如果构造函数内没有给this赋任何属性,则返回一个“空”对象(除了继承构造函数的原型之外,没有自有属性)。

尽管在构造函数中没有return语句的情况下,也会隐式返回this。但事实上我们是可以返回任意指定的对象的,在下面的例子中就返回了新创建的that对象。

  1. var Objectmaker = function () {
  2. // name属性会被忽略,因为返回的是另一个对象
  3. this.name = "This is it";
  4. // 创建并返回一个新对象
  5. var that = {};
  6. that.name = "And that's that";
  7. return that;
  8. };
  9. // 测试
  10. var o = new Objectmaker();
  11. console.log(o.name); // "And that's that"

可以看到,构造函数中其实是可以返回任意对象的,只要你返回的东西是对象即可。如果返回值不是对象(字符串、数字或布尔值),程序不会报错,但这个返回值被忽略,最终还是返回this所指的对象。