本文深入解析JavaScript中构造函数的运行机制,阐述new关键字如何改变this指向并自动返回对象。通过对比显式返回对象值的工厂模式,分析两者在内存占用、原型链共享及私有变量实现上的差异,帮助开发者根据实际需求选择合适的设计模式。
JavaScript 构造函数与 new 关键字机制
JavaScript 中的构造函数机制与其他编程语言存在显著差异。在 JavaScript 中,只要函数是通过 new 关键字调用的,它即可被视为构造函数。在构造函数执行期间,this 关键字指向新创建的对象实例。若构造函数内部没有显式的 return 语句,则默认返回 this,即新创建的对象。
代码如下:
function Foo() {
this.bla = 1;
}
Foo.prototype.test = function() {
console.log(this.bla);
};
var test = new Foo();
上述代码将 Foo 作为构造函数调用,并将新建对象的原型(__proto__)指向了 Foo.prototype。如果在构造函数内部定义了 return 表达式,构造函数将返回该表达式的值,但该返回值必须是一个对象。
代码如下:
function Bar() {
return 2;
}
new Bar(); // a new object
function Test() {
this.value = 2;
return {
foo: 1
};
}
new Test(); // the returned object
如果省略 new 关键字,函数将无法返回新创建的对象。
代码如下:
function Foo() {
this.bla = 1; // gets set on the global object
}
Foo(); // undefined
上述示例在某些场景下可能看似运行正常,但由于 JavaScript 中 this 的工作机制,此时 this 将指向全局对象。
工厂模式与原型链的权衡
工厂模式
为了在不使用 new 关键字的情况下实现类似构造函数的功能,函数需要显式返回一个值。
代码如下:
function Bar() {
var value = 1;
return {
method: function() {
return value;
}
}
}
Bar.prototype = {
foo: function() {}
};
new Bar();
Bar();
在上述示例中,无论是否使用 new 调用函数 Bar,效果均相同:返回一个包含 method 方法的新对象,这实际上构成了一个闭包。需要注意的是,new Bar() 不会返回 Bar.prototype,而是返回 return 表达式中函数 method 的原型对象。在此示例中,使用 new 与否在功能上无差异。
通过工厂模式创建新的对象
我们常被提醒避免使用 new,因为一旦忘记使用可能导致错误。为了创建对象,我们更倾向于使用工厂模式,并在其中构造一个新对象。
代码如下:
function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
尽管上述代码比使用 new 时更不易出错,且在处理私有变量时更为便捷,但也存在一些不足之处:
由于无法共享原型对象,导致内存占用增加。为了实现继承,工厂模式需要拷贝另一个对象的所有方法,或将其作为新对象的原型。放弃原型链仅仅是为了避免使用 new,这似乎与 JavaScript 语言的精神相悖。
总结
尽管使用 new 可能更容易产生错误,但这并不能成为放弃使用原型链的理由。最终选择哪种方式,需根据应用的具体需求而定。最佳实践是选择一种风格并坚持使用。
简单来说,构造函数用于初始化实例对象,对象的 prototype 属性继承自一个实例对象。