JavaScript的“ new”关键字是否被认为有害?[关闭]


568

在另一个问题中,用户指出该new关键字使用危险,并提出了不使用该对象创建对象的解决方案new。我不相信这是真的,主要是因为我使用了Prototype,Scriptaculous和其他出色的JavaScript库,并且每个人都使用了new关键字。

尽管如此,昨天我还是在Douglas Crockford在YUI剧院观看了演讲,他说的一模一样,他new不再在代码中使用关键字(JavaScript上的Crockford-Act III:终极功能-50:23分钟)。

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


90
使用new关键字并不坏。但是,如果忘记了它,您将把对象构造函数作为常规函数调用。如果您的构造函数没有检查其执行上下文,那么它将不会注意到'this'指向不同的对象(通常是全局对象),而不是新的实例。因此,构造函数将向全局对象(窗口)添加属性和方法。如果您始终在对象函数中检查“这”是对象的实例,那么您将永远不会遇到这个问题。
克里斯蒂安·B

5
我不明白 一方面,道格不鼓励使用new。但是,当您查看YUI库时。您必须在new各处使用。如var myDataSource = new Y.DataSource.IO({source:"./myScript.php"});
aditya_gaur 2011年

2
@aditya_gaur这是因为,如果您需要对某个对象进行一些初始化,init那么在使用该Object.create方法时,您必须破解一个方法,然后再调用该方法。使用new这两个功能,设置原型链并调用一些初始化程序要容易得多。
Juan Mendes 2012年

67
我不认为这应该已经结束。是的,这很可能会激发一些克罗福德反扇毒的气息,但我们正在谈论的流行建议是基本上避免此处出现主要的语言功能。使每个JQuery对象几乎什么都不称重(涉及内存)的机制涉及使用'new'关键字。首选工厂方法来不断调用新方法,但不要仅通过使用对象常量来大幅度减少体系结构的选择和性能潜力。他们有自己的位置,构造函数也有自己的位置。这是过时的和糟糕的建议。
Erik Reppen

2
TLDR:使用new并不危险。遗漏new是危险的,因此不好。但是在ES5中,您可以使用“ 严格模式”,该模式可以保护您免受这种危险以及其他危险。
jkdev '16

Answers:


603

Crockford在推广良好的JavaScript技术方面做了很多工作。他对语言关键要素的独到见解引发了许多有用的讨论。就是说,太多的人把每一个“坏”或“有害”的宣告当作福音,拒绝超越一个人的视线。有时可能会令人沮丧。

new与从头开始构建每个对象相比,使用关键字提供的功能具有多个优点:

  1. 原型继承。尽管基于类的OO语言的人们经常将怀疑和嘲笑混为一谈,但是JavaScript的本机继承技术是一种简单且令人惊讶的有效代码重用方法。并且new关键字是使用它的规范方法(并且只有可用的跨平台方法)。
  2. 性能。这是#1的副作用:如果我想向我创建的每个对象添加10个方法,我可以编写一个创建函数,将每个方法手动分配给每个新对象...或者,我可以将它们分配给创建功能,prototype并用于new标记新对象。这样不仅速度更快(原型上的每个方法不需要代码),而且还避免了为每个方法使用单独的属性来膨胀每个对象。在较慢的机器(尤其是较慢的JS解释器)上,当创建许多对象时,这可能意味着大量节省时间和内存。

是的,它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不必担心因意外使用而引起的问题。如果断码的想法静默地起作用,您甚至可以在断言中添加一个断言。或者,如某些评论所述,使用检查来引入运行时异常:

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

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

John Resig在其简单的“类”实例化文章中详细介绍了此技术,并包括一种默认情况下将此行为构建到“类”中的方法。绝对值得一读...正如他即将出版的书《 JavaScript忍者的秘密》一样,该书在JavaScript语言的此功能和许多其他“有害”功能中发现了隐藏的金子(该with那些最初被解雇的人特别有启发性这个much谐的功能(as头)。


96
if(!(this instanceof arguments.callee))throw Error(“构造函数称为函数”); //更通用,不需要了解构造函数名称,让用户修复代码。
一些

5
如果您担心性能-确实有理由要这样做-则根本不要进行检查。记住使用new或使用包装函数为您记住它。我怀疑如果您正要
紧要关头

65
使用arguments.callee检查您是否被打过电话new可能不太好,因为arguments.callee在严格模式下不可用。最好使用函数名称。
肖恩·麦克米兰

69
如果我拼错标识符,我的代码就会中断。我不应该使用标识符吗?不使用new是因为您可能忘记在代码中编写它,就像我的示例一样冗长。
Thomas Eding

16
如果打开严格模式下,如果你忘记了,当您尝试使用此使用新的,你会得到一个异常:yuiblog.com/blog/2010/12/14/strict-mode-is-coming-to-town也就是说比向每个构造函数添加check实例更容易。
stephenbez

182

我刚刚阅读了他在克罗克福德(Crockfords)的书“ Javascript:好的部分”中的某些部分。我感觉到他认为一切刺痛他的东西都是有害的:

关于开关掉线:

我绝不允许切换案例陷入下一个案例。我曾在激烈的演讲中说过为什么有时有用,然后立即由于意外的失败导致代码错误。(第97页,ISBN 978-0-596-51774-8)

关于++和-

众所周知,++(递增)和-(递减)运算符会通过鼓励复杂性而导致不良代码。在启用病毒和其他安全威胁方面,它们仅次于错误的体系结构。(第122页)

关于新:

如果你忘记了包含新的 调用构造函数时前缀,那么将不会被绑定到新的对象。可悲的是, 将绑定到全局对象,因此您将不破坏全局变量,而不是增加新对象。那真是太糟糕了。没有编译警告,也没有运行时警告。(第49页)

还有更多,但我希望您能明白。

我对您的问题的回答:不,这不是有害的。但是如果忘记了使用它的时间,可能会遇到一些问题。如果您在良好的环境中进行开发,则会注意到这一点。

更新资料

编写此答案大约一年后,发布了ECMAScript的第五版,并支持严格模式。在严格模式下,this不再绑定到全局对象,而是绑定到undefined


3
我完全同意。解决方案:始终记录用户如何实例化您的对象。举个例子,用户可能会剪切/粘贴。每种语言都有一些构造/功能,这些构造/功能可能会被滥用,从而导致异常/意外行为。这不会使它们有害。
nicerobot

45
有一个约定,总是以大写字母开头的构造函数,而所有其他函数都以小写字母开头。
一些

61
我刚刚意识到克罗克福德不考虑WHILE有害...我不知道有多少时间,我已经创建了一个不定式循环,因为我已经忘了增加一个变量...
一些

5
++--也一样。他们用最清晰的语言准确表达了我的意图。我爱他们!切换失败可能对某些人来说很清楚,但是我对此感到厌倦。当它们明显更清晰时,我会使用它们(“请双关语,无法抗拒)。
PEZ

30
我对克罗克福德的回答:编程很难,可以逛街。嘘!
杰森·杰克逊

91

Javascript是一种动态语言,有数不胜数的方法可以弄乱另一种语言会阻止您的位置。

避免使用基本的语言功能(例如new,可能会造成混乱)就像走在雷区前要脱掉闪亮的新鞋,以防万一您的鞋子浑浊。

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

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

除此以外,良好的命名习惯也有帮助。在所有函数完成工作之后,因此名称中应该有一个动词,而类表示对象,是不带动词的名词和形容词。

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

有趣的是,SO的语法着色如何解释了上面的代码。


8
是的,我只是在考虑语法着色的相同问题。
BobbyShaftoe

4
有一次我忘记键入“功能”一词。应不惜一切代价避免使用该词。还有一次我没有在多行if / then语句中使用花括号。因此,现在我不使用花括号,而只编写单行条件语句。
Hal50000 2015年

“这显然是错误的”。为什么?对于我的类(只需这样做if (! this instanceof MyClass) return new MyClass()),我实际上更喜欢new-less语法。为什么?因为函数构造函数更通用。通过省去,new可以轻松地将构造函数更改为常规函数...或反之。添加new代码可使代码更加具体。因此灵活性较差。与Java相比,建议您接受List而不是Java,ArrayList以便调用者可以选择实现。
Stijn de Witt

41

我是Java语言的新手,所以也许我只是不太有经验,无法对此提供良好的观点。但是,我想分享我对这一“新”事物的看法。

我来自C#世界,在这里使用关键字“ new”很自然,以至于工厂设计模式对我来说很奇怪。

当我第一次使用Javascript编写代码时,我没有意识到有像“ YUI”模式中那样的“ new”关键字和代码,并且花了很长时间我才陷入灾难。在回顾我编写的代码时,我无法知道特定行应该做什么。更混乱的是,当我“空运行”代码时,我的思想无法真正在对象实例边界之间转换。

然后,我找到了“新”关键字,它对我来说是“分离”的东西。使用new关键字,它可以创建事物。如果没有new关键字,我知道我不会将其与创建事物混淆,除非我要调用的函数为我提供了强有力的线索。

例如,由于var bar=foo();我没有任何线索,可能是什么条形...。它是返回值还是新创建的对象?但是,var bar = new foo();我肯定知道bar是一个对象。


3
同意,我相信工厂模式应遵循诸如makeFoo()的命名约定
pluckyglen,2009年

3
+1-“新”的出现可以更清楚地表明其意图。
belugabob

4
当JS中的几乎所有对象都是对象时,这种响应有点奇怪。当所有函数都是对象时,为什么要确定一个函数是对象呢?
约书亚·拉米雷斯

@JoshuaRamirez重点不是typeof new foo() == "object"。它new返回的实例foo,您知道可以调用foo.bar()foo.bang()。但是,通过使用JsDoc的@return可以轻松缓解这一点。不是我提倡程序代码(避免使用单词new
Juan Mendes

1
@JuanMendes Hmm ...在我看来,您的帖子似乎喜欢new,因为它在代码库中带有显式关键字。我可以挖掘这一点。这就是为什么我使用模块模式。我将有一个名为createFoo或NewFoo或MakeFoo的函数,只要它是显式的都无所谓。在其中,我声明了变量,这些变量用作从函数返回的对象文字内部的闭包。该对象文字最终成为您的对象,而该函数只是一个构造函数。
约书亚·拉米雷斯

39

另一种情况新就是我所说的维尼编码。小熊维尼跟随他的肚子。我说去你正在使用,而不是语言反对它。

该语言的维护者很可能会针对他们尝试鼓励的习惯用法优化该语言。如果他们在语言中添加了新的关键字,他们可能会认为创建新实例时清楚一点是有意义的。

遵循该语言意图编写的代码将在每个发行版中提高效率。避免使用该语言的关键结构的代码将随着时间而受苦。

编辑:这远远超出了性能。我不能指望我听说的时候(或者说)“到底为什么他们做?” 找到奇怪的代码时。经常发现,在编写代码时,有一些“好的”理由。遵循这种语言的道理是您最好的保证,因为从现在开始几年后都不会嘲笑您的代码。


3
为Pooh Coding链接+1-现在我需要找借口将其用于我的对话中……
Shog9,

大声笑!我在特定领域有多年的经验,可以向您保证,您不会遇到任何困难。他们经常在robowiki.net上叫我Pooh。=)
PEZ

1
不知道您是否会看到此评论,但Pooh Coding链接已死。
加尔文

感谢您的注意。上周,我们将robowiki.net迁移到了新的Wiki,目前旧内容无法访问。我会赶紧使那些旧链接起作用。
PEZ'Apr 30'09

24

我写了一篇有关如何减轻不使用new关键字调用构造函数的问题的文章。
这主要是讲解性的,但是它说明了如何创建可以使用或不使用的构造函数,而不new需要您添加样板代码this在每个构造函数中进行测试。

http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html

这是该技术的要点:

/**
 * Wraps the passed in constructor so it works with
 * or without the new keyword
 * @param {Function} realCtor The constructor function.
 *    Note that this is going to be wrapped
 *    and should not be used directly 
 */
function ctor(realCtor){
  // This is going to be the actual constructor
  return function wrapperCtor(){
    var obj; // object that will be created
    if (this instanceof wrapperCtor) {
      // Called with new
      obj = this;
    } else {
      // Called without new. Create an empty object of the
      // correct type without running that constructor
      surrogateCtor.prototype = wrapperCtor.prototype;
      obj = new surrogateCtor();
    }
    // Call the real constructor function
    realCtor.apply(obj, arguments);
    return obj;
  }

  function surrogateCtor() {}
}

使用方法如下:

// Create our point constructor
Point = ctor(function(x,y){
  this.x = x;
  this.y = y;
});

// This is good
var pt = new Point(20,30);
// This is OK also
var pt2 = Point(20,30);

6
避免arguments.callee真棒!
格雷格·林德

2
surrogateConstructor应该是surrogateCtor(反之亦然)。
戴维·康拉德

我维护了一组由Crockford启发的类似实用程序,并且发现当我开始一个新项目时,总是要我去寻找实现。这包含了编程的基本部分:对象实例化。所有这些工作,仅仅是为了启用随便实例化代码?老实说,我很欣赏这个包装程序的独创性,但是它似乎造成了粗心的编码。
Joe Coder

1
@joecoder我确实提到这只是出于教学目的,我不赞成这种偏执的编码风格。但是,如果我正在编写一个类库,那么我会以对调用者透明的方式添加此功能
Juan Mendes

22

不使用new关键字的原理很简单:

通过完全不使用它,可以避免因意外遗漏而带来的陷阱。YUI使用的构造模式是如何完全避免使用新关键字的示例”

var foo = function () {
    var pub= { };
    return pub;
}
var bar = foo();

或者,您可以这样:

function foo() { }
var bar = new foo();

但是这样做会招致有人忘记使用 的关键字,而运算符全是富巴的风险。AFAIK没有这样做的优势(除了您已经习惯了)。

在一天结束时:这是关于防御。 您可以使用新的陈述吗?是。这会使您的代码更危险吗?是。

如果您曾经写过C ++,则类似于删除指针后将其设置为NULL。


6
否。使用“ new foo()”可以在返回的对象上设置一些属性,例如构造函数。
一些

34
因此,为了澄清这一点:您不应该使用“新”字,因为您可能会忘记它吗?你在开玩笑吧?
孟买

16
@Bombe:在其他语言中,忘记“ new”将导​​致错误。在Javascript中,它只是在继续运输。您可能会忘记,永远不会实现。而且,简单地查看错误代码根本不会出什么问题。
肯特·弗雷德里克

3
@Greg:我不明白第一种技术如何允许使用原型链-对象字面量很棒,但是出于恐惧而放弃原型提供的性能和其他优势似乎有点愚蠢。
Shog9

5
@Bombe-您应该使用“ new”,因为您(以及使用您代码的任何人)都不会犯错误?你在开玩笑吧?
格雷格·迪恩

20

我认为“新”可增加代码的清晰度。清晰是值得的。很高兴知道有陷阱,但是通过避免清晰度来避免陷阱似乎对我而言并不可行。


16

情况1:new不是必需的,应该避免

var str = new String('asd');  // type: object
var str = String('asd');      // type: string

var num = new Number(12);     // type: object
var num = Number(12);         // type: number

情况2:new是必填项,否则会出现错误

new Date().getFullYear();     // correct, returns the current year, i.e. 2010
Date().getFullYear();         // invalid, returns an error

5
应该注意的是,在情况1中,仅当您打算进行类型转换时才将构造函数作为函数调用(并且不知道hull对象是否具有转换方法,例如toString())。在所有其他情况下,请使用文字。 不是 String('asd'),而是简单'asd'不是 Number(12),而是简单12
PointedEars 2012年

1
@PointedEars此规则的一个例外是ArrayArray(5).fill(0) 显然比[undefined, undefined, undefined, undefined, undefined].fill(0)和更具可读性(var arr = []).length = 5; arr.fill(0);
yyny 2015年

@YoYoYonnY我的陈述是在答案的情况1的情况下作出的,该情况是指new对对象类型对应的with构造函数的使用原始类型。您建议的文字等同于Array调用(try 0 in …),其余的则是语法错误:)否则,您是正确的,尽管也应注意,Array(5)如果您考虑不符合标准的实现,它可能会模棱两可。因此是模拟的 Array.prototype.fill(…))。
PointedEars 2015年

12

这是我对使用和反对使用new运算符的两个最强论点的最简短的总结:

反对 new

  1. 设计为使用对象实例化为对象的函数 new操作符函数被错误地调用为正常函数,则可能会造成灾难性的后果。在这种情况下,函数的代码将在调用函数的范围内执行,而不是按预期在本地对象的范围内执行。这可能导致全局变量和属性被覆盖,从而造成灾难性的后果。
  2. 最后,对某些程序员来说,编写function Func(),然后调用Func.prototype 并添加一些东西,以便您可以调用new Func()来构造对象似乎是丑陋的,他们出于架构和风格方面的原因宁愿使用另一种对象继承样式。

有关此论点的更多信息,请查看道格拉斯·克罗克福德(Douglas Crockford)伟大而简洁的书《 Javascript:The Good Parts》。实际上无论如何都要检查一下。

赞成的论点 new

  1. new与原型分配一起使用运算符很快。
  2. 如果您始终在构造函数中包含一些代码以检查它们是否被正确调用,以及在未正确调用它们的情况下,可以很容易地避免在全局名称空间中意外运行构造函数的代码的问题。 ,根据需要适当处理呼叫。

有关此技术的简单说明,以及他主张的继承模型的一般性更深入的说明,请参见John Resig的文章


对参数的更新:#1可以通过使用'use strict';模式(或链接中的方法)来缓解#2在ES6中具有合成糖,但是其行为与之前相同。
ninMonkey 2015年

9

我同意佩斯和这里的一些观点。

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

有人可能写的可能性var bar = foo;var bar = baz();baz不是对象创建方法的可能性似乎危险得多


8

我认为new是邪恶的,不是因为如果您忘记错误使用它可能会引起问题,而是因为它会破坏继承链,从而使语言难以理解。

JavaScript是基于原型的面向对象。因此,每个对象都必须像这样从另一个对象创建var newObj=Object.create(oldObj)。这里oldObj称为原型的newObj(因此“基于原型”)。这意味着,如果没有找到一个属性newObj那么它将在搜索oldObj因此,默认情况下,newObj将是一个空对象,但是由于其原型链,它似乎具有oldObj的所有值。

在如果你做的另一方面var newObj=new oldObj(),原型newObjoldObj.prototype,这是不必要的难以理解。

诀窍是使用

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

在此函数内部,仅在此处应使用new。之后,只需使用Object.create()方法。该方法解决了原型问题。


7
老实说,我对此技术并不陌生-它不会添加任何新内容,并且正如您的回答所显示的那样,它甚至可能最终成为拐杖。恕我直言,了解原型链和对象实例化对于理解JavaScript至关重要。但是,如果让您不方便直接使用它们,那么只要您记得自己在做什么,使用助手函数来处理一些细节就可以了。在做。FWIW:ECMAScript第5版的内容(对此有所帮助)有所变化。std,并且在某些浏览器中已经可用- 因此请注意不要盲目地重新定义它!
Shog9年

顺便说一句:我不确定您为什么要执行此CW,但是如果您想重新发布我所做的格式更正,请小心避免使用该复选框...
Shog9 2010年

2
不必要的难以理解?var c = new Car()和做的一样var c = Object.create(Car.prototype); Car.call(c)
Juan Mendes
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.