-
Notifications
You must be signed in to change notification settings - Fork 2
JS构造函数的一个问题以及三个解决方案
原文链接:http://raganwald.com/2014/07/09/javascript-constructor-problem.html 翻译:jnotnull
大家都知道,我们可以使用构造函数来创建对象,像这样:
function Fubar (foo, bar) {
this._foo = foo;
this._bar = bar;
}
var snafu = new Fubar("Situation Normal", "All Fsked Up");
当你使用new来调用构造函数时候,你得到了一个新分配的对象,同时构造函数在当前上下文中被新的对象调用。即使你没有显式的返回什么,但是你得到了一个新的对象作为结果。
因此,构造函数体是用来初始化新创建的对象的。另一个注意的事情是:新创建的对象会有一个prototype属性。protytype是什么呢?它是构造函数的原型属性。因此我们可以这样写:
Fubar.prototype.concatenated = function () {
return this._foo + " " + this._bar;
}
snafu.concatenated()
//=> 'Situation Normal All Fsked Up'
非常感谢JS的内部操作符instanceof,我们可以使用它来判断一个对象是否由指定的构造函数来创建。
snafu instanceof Fubar
//=> true
如果我们有时不带有new去调用构造函数的话会发生什么呢?
var fubar = Fubar("Fsked Up", "Beyond All Recognition");
fubar
//=> undefined
我们呢调用了函数,但是并没有获得返回的东东,因此subar是undefined。这个不是我们想要的。事实上,问题更加严重:
_foo
//=> 'Fsked Up'
当我们调用一个普通函数时候,JS会默认设置this到global环境中,因此我们错误的设置变量到global上下文中了。我们可以坐下如下动作去修复下:
function Fubar (foo, bar) {
"use strict"
this._foo = foo;
this._bar = bar;
}
Fubar("Situation Normal", "All Fsked Up");
//=> TypeError: Cannot set property '_foo' of undefined
虽然“use strict”可能已经在博客和书中被忽略,但是在生产环境中可能因为各种原因,它是近乎必须使用的了。但是无论如何,不适用new来调用构造函数都是一个潜在的风险。
那我们该如何做呢?
在Effective JavaScript中,David Herman描述了自动实例化。当我们使用new去调用构造函数的时候,this变量就指向了我们类的实例。我们可以使用this去验证我们的构造函数是否被new调用。
function Fubar (foo, bar) {
"use strict"
var obj,
ret;
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else return new Fubar(foo, bar);
}
Fubar("Situation Normal", "All Fsked Up");
//=>
{ _foo: 'Situation Normal',
_bar: 'All Fsked Up' }
为何要这么麻烦而不用new呢?因为这个方案解决了不能使用new Fubar(...)来构造对象的问题。
function logsArguments (fn) {
return function () {
console.log.apply(this, arguments);
return fn.apply(this, arguments)
}
}
function sum2 (a, b) {
return a + b;
}
var logsSum = logsArguments(sum2);
logsSum(2, 2)
//=>
2 2
4
logsArguments封装了一个方法,这个方法在输出参数后返回。让我们在原始Fubar上做下试验。
function Fubar (foo, bar) {
this._foo = foo;
this._bar = bar;
}
Fubar.prototype.concatenated = function () {
return this._foo + " " + this._bar;
}
var LoggingFubar = logsArguments(Fubar);
var snafu = new LoggingFubar("Situation Normal", "All Fsked Up");
//=> Situation Normal All Fsked Up
snafu.concatenated()
//=> TypeError: Object [object Object] has no method 'concatenated'
这个没有用,因为snafu事实上是LoggingFubar而不是Fubar的实例。但是,如果我们使用Fubar的自动初始化
function Fubar (foo, bar) {
"use strict"
var obj,
ret;
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else {
obj = new Fubar();
ret = Fubar.apply(obj, arguments);
return ret === undefined
? obj
: ret;
}
}
Fubar.prototype.concatenated = function () {
return this._foo + " " + this._bar;
}
var LoggingFubar = logsArguments(Fubar);
var snafu = new LoggingFubar("Situation Normal", "All Fsked Up");
//=> Situation Normal All Fsked Up
snafu.concatenated()
//=> 'Situation Normal All Fsked Up'
现在它可以实现了,但是显然snafu是Fubar的一个实例,而不是LoggingFubar的。那是你想要的吗?谁知道呢!这不是标准的模式,只能解释为他是一个有用的但是有漏洞的抽象化。
如果能有一个方法去判断对象的类型,那将非常的方便。如果我们赞成一个方法能做两个不同事情,我们就可以让我们的构造函数拥有自己的instanceof验证功能。
function Fubar (foo, bar) {
"use strict"
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else return arguments[0] instanceof Fubar;
}
var snafu = new Fubar("Situation Normal", "All Fsked Up");
snafu
//=>
{ _foo: 'Situation Normal',
_bar: 'All Fsked Up' }
Fubar({})
//=> false
Fubar(snafu)
//=> true
这就允许我们使用构造函数作为参数来进行消息发送,或者作为过滤器
var arrayOfSevereProblems = problems.filter(Fubar);
如果你不需要自动实例化和函数重载,那我们建议你不要直接不带new去调用构造函数。我们看到“use strict”有帮助,但是它不是万能的。如果我们不把变量设定到global环境中,它不会抛出错误。我们应该尽可能的在赋值之前做好事情。
也许我们最好自己掌控局势。Olivier Scherrer给出了下面的模式:
function Fubar (foo, bar) {
"use strict"
if (!(this instanceof Fubar)) {
throw new Error("Fubar needs to be called with the new keyword");
}
this._foo = foo;
this._bar = bar;
}
Fubar("Situation Normal", "All Fsked Up");
//=> Error: Fubar needs to be called with the new keyword
这个比仅仅依赖“use strict”更加简单安全。如果你喜欢这个简单的instance判断,你还可以把这个判断方法传递到构造函数中。
Fubar.is = function (obj) {
return obj instanceof Fubar;
}
var arrayOfSevereProblems = problems.filter(Fubar.is);
这里总结下:如果不适用new去调用构造函数都会有潜在的问题。这里有三个解决方法,分别是:自动实例化、重载构造函数、抛出错误。