避免在JavaScript中使用新运算符-更好的方法


16

警告:这是一篇冗长的文章。

让我们保持简单。我想避免每次在JavaScript中调用构造函数时都必须给新运算符添加前缀。这是因为我倾向于忘记它,并且我的代码严重搞砸了。

解决这个问题的简单方法是...

function Make(x) {
  if ( !(this instanceof arguments.callee) )
  return new arguments.callee(x);

  // do your stuff...
}

但是,我需要接受变量号。这样的论点...

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

第一个直接的解决方案似乎是这样的“应用”方法...

function Make() {
  if ( !(this instanceof arguments.callee) )
    return new arguments.callee.apply(null, arguments);

  // do your stuff
}

但是,这是错误的-新对象将传递给apply方法,而不是传递给我们的构造函数arguments.callee

现在,我提出了三种解决方案。我的简单问题是:哪个似乎最好。或者,如果您有更好的方法,请告诉它。

首先 –用于eval()动态创建调用构造函数的JavaScript代码。

function Make(/* ... */) {
  if ( !(this instanceof arguments.callee) ) {
    // collect all the arguments
    var arr = [];
    for ( var i = 0; arguments[i]; i++ )
      arr.push( 'arguments[' + i + ']' );

    // create code
    var code = 'new arguments.callee(' + arr.join(',') + ');';

    // call it
    return eval( code );
  }

  // do your stuff with variable arguments...
}

第二 –每个对象都有__proto__属性,该属性是与其原型对象的“秘密”链接。幸运的是,该属性是可写的。

function Make(/* ... */) {
  var obj = {};

  // do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // now do the __proto__ magic
  // by 'mutating' obj to make it a different object

  obj.__proto__ = arguments.callee.prototype;

  // must return obj
  return obj;
}

第三 –这类似于第二种解决方案。

function Make(/* ... */) {
  // we'll set '_construct' outside
  var obj = new arguments.callee._construct();

  // now do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // you have to return obj
  return obj;
}

// now first set the _construct property to an empty function
Make._construct = function() {};

// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;

  • eval 解决方案似乎笨拙,并伴随着“邪恶评估”的所有问题。

  • __proto__ 解决方案是非标准的,并且“ mIsERY的出色浏览器”不兑现它。

  • 第三种解决方案似乎过于复杂。

但是使用以上三种解决方案,我们都可以做这样的事情,否则我们将无法避免……

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true

Make.prototype.fire = function() {
  // ...
};

m1.fire();
m2.fire();
m3.fire();

因此,上述解决方案有效地为我们提供了接受变量编号的“真实”构造函数。的论点,不需要new。您对此有何看法。

-更新-

有些人说“只是抛出一个错误”。我的回答是:我们正在使用10个以上的构造函数来制作一个繁重的应用程序,我认为如果每个构造函数都可以“聪明地”处理该错误而又不会在控制台上抛出错误消息,那将更加令人费解。


2
或在糟糕的情况下抛出错误,检查stacktrace,然后可以修复代码
棘手怪胎

2
我认为最好在Stack OverflowCode Review中问这个问题。这似乎是以代码为中心的,而不是概念性的问题。
亚当李尔

1
@greengit而不是使用jslint引发错误。如果您Make()new这样做,它将警告您,因为Make是大写的,因此它假定它是构造函数
Raynos 2011年

1
因此,等等-您是否正在寻找一种更好的方法来完成此任务,还是只是在寻找可以给您提供代码的人,以便无需创建变量参数对象即可new?因为如果是后者,则可能是您在错误的网站上提问。如果是前者,则可能不希望很快放弃使用新错误和检测错误的建议...如果您的应用程序确实“繁重”,那么您想要的最后一件事就是过度劳累的构造机制来减慢它的运行速度。new,尽管有很多漏洞,但速度非常快。
Shog9 2011年

5
具有讽刺意味的是,试图“聪明地”处理程序员的错误本身是JavaScript的许多“不良部分”的原因。
丹尼尔·普拉特

Answers:


19

首先arguments.callee,在ES5中严格不推荐使用,因此我们不使用它。真正的解决方案非常简单。

你根本不用new

var Make = function () {
  if (Object.getPrototypeOf(this) !== Make.prototype) {
    var o = Object.create(Make.prototype);
    o.constructor.apply(o, arguments);
    return o;
  }
  ...
}

这是对的屁股痛吗?

尝试 enhance

var Make = enhance(function () {
  ...
});

var enhance = function (constr) {
  return function () {
    if (Object.getPrototypeOf(this) !== constr.prototype) {
      var o = Object.create(constr.prototype);
      constr.apply(o, arguments);
      return o;
    }
    return constr.apply(this, arguments);
  }
}

现在当然需要ES5,但是每个人都使用ES5-shim对吗?

您可能也对替代js OO模式感兴趣

顺便说一句,您可以将选项二替换为

var Make = function () {
  var that = Object.create(Make.prototype);
  // use that 

  return that;
}

如果您想要自己的ES5 Object.create垫片,那真的很容易

Object.create = function (proto) {
  var dummy = function () {};
  dummy.prototype = proto;
  return new dummy;
};

是的,但是这里所有的魔术都是因为Object.create。那么ES5之前呢?ES5-Shim Object.create列为“ DUBIOUS”。
treecoder 2011年

@greengit如果您再次阅读它,ES5填充会指出Object.create的第二个参数是DUBIOUS。第一个很好。
雷诺斯

1
是的,我确实读过。而且我认为(IF)他们在__proto__那里使用某种东西,那么我们仍然处于同一点。由于ES5之前的版本没有简单的方法可以对原型进行突变。但是无论如何,您的解决方案似乎是最优雅和前瞻性的。+1。(达到我的投票限制)
treecoder

1
谢谢@psr。@Raynos您的Object.create垫片几乎是我的第三个解决方案,但当然比我的复杂度低,外观更好。
treecoder

37

让我们保持简单。我想避免每次在JavaScript中调用构造函数时都必须给新运算符添加前缀。这是因为我倾向于忘记它,并且我的代码严重搞砸了。

显而易见的答案是不要忘记new关键字。

您正在改变语言的结构和含义。

我认为,为了您的代码的未来维护者,这是一个可怕的想法。


9
+1将一种语言纠缠于一个人的不良编码习惯似乎很奇怪。当然,某些语法高亮显示/签入策略可以强制避免易错模式/可能的错字。
Stoive 2011年

如果我找到了一个所有构造函数都不关心是否使用过的库new,我会发现它更易于维护。
2015年

@Jack,但它会引入难度更大,更巧妙的方法来发现错误。如果您包含;to end语句,只需四处看看由javascript“不关心”引起的所有错误。(自动分号插入)
CaffGeek

@CaffGeek“ Javascript不关心分号”是不准确的-在某些情况下,使用分号和不使用分号有细微的不同,在某些情况下则不然。那就是问题所在。接受的答案中提出的解决方案恰恰相反- 在所有情况下使用new或不使用在语义上都是相同的。目前没有微妙的情况下,这种不变的被打破。这就是为什么它很好,为什么要使用它。
2015年

14

最简单的解决方案是只记住new并抛出一个错误,使其变得很明显被您忘记了。

if (Object.getPrototypeOf(this) !== Make.prototype) {
    throw new Error('Remember to call "new"!');
}

无论您做什么,都不要使用eval。我会回避使用非标准属性,例如__proto__专门因为它们是非标准属性并且其功能可能会发生变化。


坚决避免.__proto__它是魔鬼
雷诺斯2011年

3

我实际上写了一篇关于此的文章。http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html

function Ctor() {
    if (!(this instanceof Ctor) {
        return new Ctor(); 
    }
    // regular construction code
}

您甚至可以将其概括化,因此不必将其添加到每个构造函数的顶部。您可以通过访问我的帖子看到

免责声明我在我的代码中没有使用它,我只是出于教学目的发布了它。我发现忘记a new是一个容易发现的错误。像其他人一样,我认为大多数代码实际上都不需要这样做。除非您正在编写用于创建JS继承的库,否则在这种情况下,您可以在一个地方使用它,并且已经使用了与直接继承不同的方法。


潜在的潜在错误:如果我先var x = new Ctor();拥有x然后又拥有x this和do var y = Ctor();,这将不会表现出预期的效果。
luiscubal 2012年

@luiscubal不知道您在说什么“以后将x表示为this”,您是否可以发布jsfiddle来显示潜在问题?
Juan Mendes

您的代码比我最初想象的要健壮一些,但是我确实想出了一个(有点复杂但仍然有效的)示例:jsfiddle.net/JHNcR/1
luiscubal 2013年

@luiscubal我明白您的意思,但这确实是一个令人费解的问题。您假设可以致电Ctor.call(ctorInstance, 'value')。我看不到您正在做什么的有效方案。要构造对象,请使用var x = new Ctor(val)var y=Ctor(val)。即使存在有效的情况,我的主张是您可以拥有不使用的构造函数,而new Ctor不是可以拥有可以使用的构造函数。Ctor.call请参阅jsfiddle.net/JHNcR/2
Juan Mendes

0

这个怎么样?

/* thing maker! it makes things! */
function thing(){
    if (!(this instanceof thing)){
        /* call 'new' for the lazy dev who didn't */
        return new thing(arguments, "lazy");
    };

    /* figure out how to use the arguments object, based on the above 'lazy' flag */
    var args = (arguments.length > 0 && arguments[arguments.length - 1] === "lazy") ? arguments[0] : arguments;

    /* set properties as needed */
    this.prop1 = (args.length > 0) ? args[0] : "nope";
    this.prop2 = (args.length > 1) ? args[1] : "nope";
};

/* create 3 new things (mixed 'new' and 'lazy') */
var myThing1 = thing("woo", "hoo");
var myThing2 = new thing("foo", "bar");
var myThing3 = thing();

/* test your new things */
console.log(myThing1.prop1); /* outputs 'woo' */
console.log(myThing1.prop2); /* outputs 'hoo' */

console.log(myThing2.prop1); /* outputs 'foo' */
console.log(myThing2.prop2); /* outputs 'bar' */

console.log(myThing3.prop1); /* outputs 'nope' */
console.log(myThing3.prop2); /* outputs 'nope' */

编辑:我忘记添加:

“如果您的应用程序确实很“繁重”,那么您最后想要的就是某种过度使用的构建机制来降低它的速度”

我完全同意-在上面没有“ new”关键字的情况下创建“事物”时,它的速度要慢于/使用它。错误是您的朋友,因为错误会告诉您哪里出了问题。而且,他们会告诉您的开发人员他们在做什么错。


为缺少的构造函数参数发明值很容易出错。如果缺少它们,请保留属性未定义。如果它们是必不可少的,则丢失它们时将引发错误。如果prop1应该是bool怎么办?测试“ nope”的真实性将是错误的来源。
JBR威尔金森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.