在另一个问题中,一个用户指出使用new关键字是危险的,并提出了一个不使用new的对象创建解决方案。我不相信这是真的,主要是因为我使用过Prototype、Script.aculo.us和其他优秀的JavaScript库,它们都使用new关键字。

尽管如此,昨天我在YUI剧院看了Douglas Crockford的演讲,他说了完全相同的事情,他不再在他的代码中使用new关键字(Crockford谈JavaScript -第三幕:函数终极- 50:23分钟)。

使用new关键字“不好”吗?使用它的优点和缺点是什么?


当前回答

我同意PEZ和一些人的观点。

在我看来,“新”显然是自我描述的对象创建,而Greg Dean所描述的YUI模式完全被模糊了。

有人可能会写var bar = foo;或者var bar = baz();在baz不是对象的地方,创建方法似乎要危险得多。

其他回答

我是JavaScript的新手,所以可能我只是没有太多的经验来提供一个好的观点。然而,我想分享一下我对这个“新”事物的看法。

我来自c#世界,在那里使用关键字“new”是如此自然,以至于工厂设计模式对我来说看起来很奇怪。

当我第一次用JavaScript编码时,我没有意识到有“new”关键字和像YUI模式那样的代码,很快我就陷入了灾难。当回顾我所编写的代码时,我不知道某一行应该做什么。更混乱的是,当我“干运行”代码时,我的思维不能真正地在对象实例边界之间转换。

然后,我发现“新”关键字,对我来说,“分离”的东西。使用new关键字,它创建东西。如果没有new关键字,我知道我不会把它与创建东西混淆,除非我调用的函数提供了强有力的线索。

例如,用var bar=foo();我没有任何线索,什么酒吧可能是....它是一个返回值还是一个新创建的对象?但是用var bar = new foo();我确定bar是一个对象。

我同意PEZ和一些人的观点。

在我看来,“新”显然是自我描述的对象创建,而Greg Dean所描述的YUI模式完全被模糊了。

有人可能会写var bar = foo;或者var bar = baz();在baz不是对象的地方,创建方法似乎要危险得多。

以下是我对支持和反对使用新运算符的两个最有力的论点所做的最简短的总结:

反对新建的理由

Functions designed to be instantiated as objects using the new operator can have disastrous effects if they are incorrectly invoked as normal functions. A function's code in such a case will be executed in the scope where the function is called, instead of in the scope of a local object as intended. This can cause global variables and properties to get overwritten with disastrous consequences. Finally, writing function Func(), and then calling Func.prototype and adding stuff to it so that you can call new Func() to construct your object seems ugly to some programmers, who would rather use another style of object inheritance for architectural and stylistic reasons.

关于这个争论的更多信息,请参阅Douglas Crockford的著作《JavaScript: The Good Parts》。事实上,还是去看看吧。

赞成新

使用新的运算符 原型赋值很快。 那些关于意外的东西 运行一个构造函数 全局名称空间中的代码可以 很容易预防,如果你总是 包含一些代码在您的 要检查的构造函数 看看它们是否被调用 正确,而且,在这种情况下 他们没有,接听电话 如你所愿。

请参阅John Resig的文章,以获得对这种技术的简单解释,以及对他所倡导的继承模型的一般更深入的解释。

Crockford为推广优秀的JavaScript技术做了很多工作。他对语言关键要素的固执立场引发了许多有益的讨论。也就是说,有太多的人把每一个“坏”或“有害”的宣言都当作福音,拒绝超越一个人的观点。有时会让人有点沮丧。

使用new关键字提供的功能比从头构建每个对象有几个优点:

Prototype inheritance. While often looked at with a mix of suspicion and derision by those accustomed to class-based OO languages, JavaScript's native inheritance technique is a simple and surprisingly effective means of code re-use. And the new keyword is the canonical (and only available cross-platform) means of using it. Performance. This is a side-effect of #1: if I want to add 10 methods to every object I create, I could just write a creation function that manually assigns each method to each new object... Or, I could assign them to the creation function's prototype and use new to stamp out new objects. Not only is this faster (no code needed for each and every method on the prototype), it avoids ballooning each object with separate properties for each method. On slower machines (or especially, slower JS interpreters) when many objects are being created this can mean a significant savings in time and memory.

是的,new有一个关键的缺点,其他答案巧妙地描述了它:如果你忘记使用它,你的代码会毫无警告地崩溃。幸运的是,这个缺点很容易减轻——只需在函数本身添加一些代码:

function foo()
{
   // if user accidentally omits the new keyword, this will 
   // silently correct the problem...
   if ( !(this instanceof foo) )
      return new foo();
   
   // constructor logic follows...
}

现在你可以拥有new的优势,而不必担心误用造成的问题。

John Resig在他的“简单类”实例化文章中详细介绍了这种技术,以及在默认情况下将这种行为构建到“类”中的方法。绝对值得一读……他即将出版的新书《JavaScript忍者的秘密》(Secrets of the JavaScript Ninja)也是如此,这本书发现了JavaScript语言中隐藏的黄金和许多其他“有害”特性(关于with的章节对我们这些最初将这个备受诟病的特性视为噱头而不屑一顾的人特别有启发)。

通用的健全性检查

您甚至可以在检查中添加一个断言,如果您不愿意让坏掉的代码默默地工作的话。或者,正如一些人评论的那样,使用check来引入一个运行时异常:

if ( !(this instanceof arguments.callee) ) 
   throw new Error("Constructor called as a function");

请注意,这个代码片段能够避免硬编码构造函数名称,因为与前面的示例不同,它不需要实际实例化对象——因此,可以将它复制到每个目标函数中而无需修改。

ES5逃走

正如Sean McMillan, stephenbez和jrh所指出的,论点的使用。callee在ES5的严格模式下无效。因此,如果在该上下文中使用上述模式,它将抛出一个错误。

ES6和一个完全无害的新

ES6向JavaScript引入了类——不,不是像老派Crockford那样怪异地模仿java,而是在精神上更像他(和其他人)后来采用的轻量级方式,采用原型继承的最佳部分,并将公共模式烘焙到语言本身中。

...其中包括一个安全的新:

class foo
{
  constructor()
  {
    // constructor logic that will ONLY be hit 
    // if properly constructed via new
  } 
}

// bad invocation
foo(); // throws, 
// Uncaught TypeError: class constructors must be invoked with 'new'

但如果你不想用这种新糖怎么办?如果您只是想用上面所示的那种安全检查来更新您完美无缺的旧式原型代码,以便它们在严格模式下继续工作,该怎么办呢?

正如Nick Parsons指出的那样,ES6也提供了一个方便的检查,以new.target的形式:

function foo()
{
  if ( !(new.target) ) 
     throw new Error("Constructor called as a function");
   
  // constructor logic follows...
}

所以无论你选择哪种方法,只要稍加思考和保持卫生,你就可以无害地使用新产品。

JavaScript是一种动态语言,有无数种方法可以把其他语言阻止你的事情搞砸。

避免使用像new这样的基本语言特性,因为你可能会搞砸,这有点像在穿过雷区之前脱掉你闪闪发光的新鞋,以防你的鞋被弄脏。

我使用了一种约定,函数名以小写字母开头,而实际上是类定义的“函数”以大写字母开头。结果是一个非常引人注目的视觉线索,“语法”是错误的:

var o = MyClass();  // This is clearly wrong.

最重要的是,良好的命名习惯会有所帮助。毕竟,函数是做事情的,因此它的名字中应该有一个动词,而类代表对象,是没有任何动词的名词和形容词。

var o = chair() // Executing chair is daft.
var o = createChair() // Makes sense.

Stack Overflow的语法着色是如何解释上面的代码的,这很有趣。