JavaScript 中的构造函数和其它语言中的构造函数是不同的。 通过 new
关键字方式调用的函数都被认为是构造函数。
在构造函数内部 - 也就是被调用的函数内 - this
指向新创建的对象 Object
。 这个新创建的对象的 prototype
被指向到构造函数的 prototype
。
如果被调用的函数没有显式的 return
表达式,则隐式的会返回 this
对象 - 也就是新创建的对象。
1 | function Foo() { |
上面代码把 Foo
作为构造函数调用,并设置新创建对象的 prototype
为 Foo.prototype
。
显式的 return
表达式将会影响返回结果,但仅限于返回的是一个对象。
1 | function Bar() { |
new Bar()
返回的是新创建的对象,而不是数字的字面值 2。 因此 new Bar().constructor === Bar
,但是如果返回的是数字对象,结果就不同了,如下所示
1 | function Bar() { |
**这里得到的 new Test()
是函数返回的对象,而不是通过new
关键字新创建的对象,因此:
1 | (new Test()).value === undefined |
如果 new
被遗漏了,则函数不会返回新创建的对象。
1 | function Foo() { |
虽然上例在有些情况下也能正常运行,但是由于 JavaScript 中 this
的工作原理, 这里的 this
指向全局对象。
工厂模式
为了不使用 new
关键字,构造函数必须显式的返回一个值。
1 | function Bar() { |
上面两种对 Bar
函数的调用返回的值完全相同,一个新创建的拥有 method
属性的对象被返回, 其实这里创建了一个闭包。
还需要注意, new Bar()
并不会改变返回对象的原型(也就是返回对象的原型不会指向 Bar.prototype
)。 因为构造函数的原型会被指向到刚刚创建的新对象,而这里的 Bar
没有把这个新对象返回:而是返回了一个包含 method
属性的自定义对象)。
在上面的例子中,使用或者不使用 new
关键字没有功能性的区别。
上面两种方式创建的对象不能访问 Bar
原型链上的属性,如下所示:
1 | var bar1 = new Bar(); |
通过工厂模式创建新对象
我们常听到的一条忠告是不要使用 new
关键字来调用函数,因为如果忘记使用它就会导致错误。
为了创建新对象,我们可以创建一个工厂方法,并且在方法内构造一个新对象。
1 | function Foo() { |
虽然上面的方式比起 new
的调用方式不容易出错,并且可以充分利用私有变量
带来的便利, 但是随之而来的是一些不好的地方。
- 会占用更多的内存,因为新创建的对象不能共享原型上的方法。
- 为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。
- 放弃原型链仅仅是因为防止遗漏
new
带来的问题,这似乎和语言本身的思想相违背。
总结
虽然遗漏 new
关键字可能会导致问题,但这并不是放弃使用原型链的借口。 最终使用哪种方式取决于应用程序的需求,选择一种代码书写风格并坚持下去才是最重要的。