ChatGPT解决这个技术问题 Extra ChatGPT

JavaScript 的“new”关键字被认为是有害的吗?

在另一个 question 中,一位用户指出 new 关键字使用起来很危险,并提出了一个不使用 new 的对象创建解决方案。我不相信这是真的,主要是因为我使用过 Prototype、Scriptaculous 和其他优秀的 JavaScript 库,而且每个人都使用 new 关键字。

尽管如此,昨天我在 YUI 剧院观看了 Douglas Crockford 的演讲,他说了同样的话,他没有在他的代码 (Crockford on JavaScript - Act III: Function the Ultimate - 50:23 minutes) 中使用 new 关键字。

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

使用 new 关键字并不是“坏事”。但是,如果您忘记了它,您将把对象构造函数作为常规函数来调用。如果您的构造函数不检查其执行上下文,那么它不会注意到“this”指向不同的对象(通常是全局对象)而不是新实例。因此,您的构造函数将向全局对象(窗口)添加属性和方法。如果你总是在对象函数中检查'this'是你的对象的一个实例,那么你就不会遇到这个问题。
我不明白这一点。一方面,doug 不鼓励使用 new。但是当您查看 YUI 库时。您必须在任何地方使用 new。如var myDataSource = new Y.DataSource.IO({source:"./myScript.php"});
@aditya_gaur 那是因为如果您需要对对象进行一些初始化,那么如果您使用 Object.create 方法并在之后调用它,则必须破解一个 init 方法。使用 new 更容易,它可以同时设置原型链并调用一些初始化代码。
我认为这不应该被关闭。是的,它可能会激发一些 Crockford 反粉丝的毒液,但我们正在谈论流行的建议,以基本上避免这里的主要语言功能。使每个 JQuery 对象几乎没有重量的机制(涉及内存)涉及使用“新”关键字。更喜欢工厂方法而不是不断调用新方法,但不要只使用对象字面量来大幅降低您的架构选项和性能潜力。他们有自己的位置,构造者也有自己的位置。这是毫无疑问的过时和糟糕的建议。
TLDR:使用 new 并不危险。省略 new 是危险的,因此是 bad。但在 ES5 中,您可以使用 Strict Mode,它可以保护您免受这种危险和许多其他危险。

S
Shog9

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

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

原型继承。尽管那些习惯于基于类的 OO 语言的人经常带着怀疑和嘲笑的态度看待,但 JavaScript 的本机继承技术是一种简单且令人惊讶的代码重用方法。 new 关键字是使用它的规范(也是唯一可用的跨平台)方法。表现。这是 #1 的副作用:如果我想为我创建的每个对象添加 10 个方法,我可以编写一个创建函数,手动将每个方法分配给每个新对象……或者,我可以将它们分配给创建函数的原型,并使用 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 的优势,而不必担心意外误用导致的问题。

John Resig 在他的 Simple "Class" Instantiation 帖子中详细介绍了这种技术,并在默认情况下将这种行为构建到您的“类”中。绝对值得一读...正如他即将出版的书 Secrets of the JavaScript Ninja,它在 JavaScript 语言的这个和许多其他“有害”特性中发现了隐藏的黄金(with 中的章节尤其对于我们这些最初将这个备受诟病的功能视为噱头的人来说很有启发性)。

通用的健全性检查

您甚至可以在检查中添加一个断言,如果您对损坏的代码无声工作的想法感到困扰。或者,正如 some 所评论的,使用检查来引入运行时异常:

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

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

ES5 带走

正如 Sean McMillanstephenbezjrh 所指出的,arguments.callee 的使用在 ES5 的 strict mode 中是无效的。因此,如果您在该上下文中使用上述模式,它将引发错误。

ES6 和一个完全无害的新

ES6 将 Classes 引入 JavaScript - 不,不是以老式 Crockford 所做的怪异的 Java 风格,而是在精神上更像他(和其他人)后来采用的轻量级方式,采用了 prototypal 的最佳部分继承和烘焙通用模式到语言本身。

...其中一部分包括一个安全的 new

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 notes 一样,ES6 也以 new.target 的形式提供了一个方便的检查:

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

因此,无论您选择哪种方法,只要稍加考虑并保持良好的卫生习惯,您都可以使用 new 而不会造成伤害。


if (!(this instanceof arguments.callee)) throw Error("Constructor called as a function");// 更通用,不需要知道构造函数名称,让用户修复代码。
如果您担心性能 - 并且实际上有理由担心 - 那么根本不要进行检查。要么记住使用 new,要么使用包装函数为您记住它。我怀疑如果你正处于重要的地步,你已经用尽了其他优化,比如记忆,所以你的创建调用无论如何都会被本地化......
使用 arguments.callee 检查您是否被 new 调用可能不太好,因为 arguments.callee 在严格模式下不可用。最好使用函数名。
如果我拼错了一个标识符,我的代码就会中断。我不应该使用标识符吗?不使用 new 因为您可能会忘记在代码中编写它,这与我的示例一样荒谬。
如果您打开严格模式,如果您忘记使用 new,您将在尝试使用 this 时遇到异常:yuiblog.com/blog/2010/12/14/strict-mode-is-coming-to-town 这比在每个构造函数中添加一个 check 实例更容易。
s
some

我刚刚阅读了他的 Crockfords 书“Javascript: The Good Parts”的部分内容。我觉得他认为曾经咬过他的一切都是有害的:

关于开关掉线:

我从不允许切换案例落入下一个案例。我曾经在我的代码中发现了一个错误,这是在我发表了关于为什么跌倒有时有用的激烈演讲后立即意外跌倒引起的。 (第 97 页,ISBN 978-0-596-51774-8)

关于 ++ 和 --

众所周知,++(递增)和 --(递减)运算符会通过鼓励过度的技巧来导致糟糕的代码。在启用病毒和其他安全威胁方面,它们仅次于错误的架构。 (第 122 页)

关于新:

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

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

我对你的问题的回答:不,它没有害处。但是如果你忘记在你应该使用它的时候使用它,你可能会遇到一些问题。如果您在良好的环境中发展,您会注意到这一点。

更新

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


我完全同意。解决方案:始终记录用户必须如何实例化您的对象。使用示例,用户可能会剪切/粘贴。每种语言中都有可能被滥用的结构/功能,从而导致异常/意外的行为。它不会使它们有害。
有一个约定,构造函数总是以大写字母开头,所有其他函数都以小写字母开头。
我刚刚意识到 Crockford 并不认为 WHILE 有害......我不知道我创建了多少次不定式循环,因为我忘记增加一个变量......
++,--也是如此。它们用最清晰的语言准确地表达了我的意图。我爱他们!某些人可能很清楚切换失败,但我对这些感到厌倦。当它们明显更清晰时,我会使用它们(因为双关语,无法抗拒)。
我对 Crockford 的回答:编程很难,我们去购物吧。嘘!
D
Donal Fellows

Javascript 是动态语言,有无数种方法可以搞砸另一种语言会阻止你的地方。

避免使用诸如 new 之类的基本语言功能可能会造成混乱,这有点像在穿过雷区之前脱掉闪亮的新鞋,以防你的鞋子弄脏。

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

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

除此之外,良好的命名习惯也会有所帮助。毕竟函数都是做事的,因此它的名称中应该有一个动词,而类代表对象并且是没有动词的名词和形容词。

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

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


是的,我只是对语法着色有同样的想法。
一旦我忘记输入“功能”这个词。应该不惜一切代价避免这个词。还有一次我没有在多行 if/then 语句中使用花括号。所以现在我不使用花括号,只写一行条件。
“这显然是错误的”。为什么?对于我的类(仅执行 if (! this instanceof MyClass) return new MyClass()),我实际上更喜欢无 new 的语法。为什么?因为函数构造函数更通用。省略 new 可以很容易地将构造函数更改为常规函数......或者反过来。添加 new 使代码比它需要的更具体。因此不太灵活。与建议接受 List 而不是 ArrayList 的 Java 进行比较,以便调用者可以选择实现。
a
alex

我是 Javascript 的新手,所以也许我在提供一个好的观点方面经验不足。然而,我想分享我对这个“新”事物的看法。

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

当我第一次用 Javascript 编写代码时,我没有意识到有“new”关键字和 YUI 模式中的代码,我很快就会遇到灾难。回顾我编写的代码时,我忘记了特定行应该做什么。更混乱的是,当我“空运行”代码时,我的思维无法真正在对象实例边界之间转换。

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

例如,对于 var bar=foo();,我不知道 bar 可能是什么......它是返回值还是新创建的对象?但是对于 var bar = new foo();,我确定 bar 是一个对象。


同意,我相信工厂模式应该遵循 makeFoo() 之类的命名约定
+1 - “新”的存在给出了更清晰的意图陈述。
当 JS 中的几乎所有东西都是对象时,这种响应有点奇怪。当所有函数都是对象时,为什么你需要确定一个函数是对象?
@JoshuaRamirez 重点不在于typeof new foo() == "object"。就是 new 返回一个 foo 的实例,并且您知道您可以调用 foo.bar()foo.bang()。但是,这可以通过使用 JsDoc's @return 轻松缓解,并不是说我提倡使用程序代码(避免使用 new 这个词)
@JuanMendes 嗯......你的帖子让我觉得你喜欢 new 因为它在你的代码库中有明确的关键字。我可以挖那个。这就是我使用模块模式的原因。我将有一个名为 createFoo 或 NewFoo 或 MakeFoo 的函数,只要它是明确的就没有关系。在其中,我声明了用作函数返回的对象文字内部的闭包的变量。该对象文字最终成为您的对象,而该函数只是一个构造函数。
P
PEZ

for new 的另一种情况是我所说的Pooh Coding。小熊维尼跟着他的肚子走。我说使用你正在使用的语言,而不是反对它。

语言的维护者很有可能会针对他们试图鼓励的习语优化语言。如果他们将新关键字放入语言中,他们可能认为在创建新实例时明确是有意义的。

按照语言的意图编写的代码将随着每个版本的发布而提高效率。避免语言关键结构的代码会随着时间的推移而受到影响。

编辑:这远远超出了性能。我数不清有多少次我听到(或说过)“他们到底为什么要那样做?”当发现奇怪的代码时。事实证明,在编写代码时,有一些“好的”理由。遵循语言之道是几年后您的代码不会被嘲笑的最佳保险。


为小熊维尼编码链接 +1 - 现在我需要找借口在我的对话中使用它......
哈哈!我在该特定领域拥有多年经验,我可以向您保证,您不会遇到任何困难。他们经常在 robowiki.net 上叫我小熊维尼。 =)
不知道你是否会看到这条评论,但那个小熊维尼编码链接已经死了。
感谢您的提醒。上周我们将 robowiki.net 迁移到了一个新的 wiki,而旧内容目前无法访问。我会尽快让这些旧链接正常工作。
J
Justin Morgan

我写了一篇关于如何缓解在不使用 new 关键字的情况下调用构造函数的问题的帖子。
这主要是说教,但它展示了如何创建使用或不使用 new 且不需要添加的构造函数boilerplate code 在每个构造函数中测试 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);

避免 arguments.callee 太棒了!
我维护了一组受 Crockford 启发的类似实用程序,并发现当我开始一个新项目时,它总是让我跑去寻找实现。这包含了编程的一个基本部分:对象实例化。所有这些工作,只是为了启用随意的实例化代码?老实说,我很欣赏这个包装器的独创性,但它似乎会导致粗心的编码。
@joecoder 我确实提到这只是为了教学目的,我不赞成这种偏执狂的编码风格。但是,如果我正在编写一个类库,那么是的,我会以对调用者透明的方式添加此功能
a
alex

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

通过根本不使用它,您可以避免意外忽略它所带来的陷阱。 YUI 使用的构造模式是如何完全避免使用 new 关键字的示例”

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

或者你可以这样:

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

但是这样做会冒着有人忘记使用 new 关键字的风险,并且 this 操作符全是 fubar。 AFAIK 这样做没有任何好处(除了你已经习惯了)。

在一天结束时:这是关于防守的。你能用新的说法吗?是的。它会让你的代码更危险吗?是的。

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


不。使用“new foo()”,在返回的对象上设置了一些属性,例如构造函数。
所以,为了明确一点:你不应该使用“new”,因为你可能会忘记它?你在开玩笑吧?
@Bombe:在其他语言中忘记“新”会导致错误。在 Javascript 中,它只是继续运输。你可以忘记,永远不会意识到。并且仅仅查看错误的代码根本不会很明显出了什么问题。
@Greg:我看不到第一种技术如何允许使用原型链-对象文字很棒,但是出于恐惧而放弃原型提供的性能和其他优势似乎有点愚蠢。
@Bombe - 你应该使用“新”,因为你(和任何使用你的代码的人)永远不会犯错?你在开玩笑吧?
P
PEZ

我认为“新”增加了代码的清晰度。清晰值得一切。很高兴知道存在陷阱,但通过避免清晰来避免它们对我来说似乎不是这样。


n
nyuszika7h

案例 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

需要注意的是,在情况 1 中,将构造函数作为函数调用仅在您打算进行类型转换时才有意义(并且不知道 hull 对象是否有转换方法,例如 toString())。在所有其他情况下,使用文字。 不是 String('asd'),而只是 'asd'不是 Number(12),而只是 12
@PointedEars 此规则的一个例外是 ArrayArray(5).fill(0) 显然比 [undefined, undefined, undefined, undefined, undefined].fill(0)(var arr = []).length = 5; arr.fill(0); 更具可读性
@YoYoYonnY 我的陈述是在答案的案例 1 的上下文中做出的,它指的是 new 与对应于 primitive 类型的对象类型的构造函数一起使用。您建议的文字 not 等同于 Array 调用(尝试 0 in …),其余的是语法错误:) 否则您是正确的,尽管也应注意如果您考虑不符合的实现(因此是模拟 Array.prototype.fill(…)),那么 Array(5) 可能会模棱两可。
请注意,Date 具有特殊规则来区分使用与未使用的 new。这是一种异常值。此外,所有这些构造函数都是最古老的语言规范的一部分。较新的语言功能表现得更好。这里的所有答案都需要更新或删除……
a
alegscogs

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

反对新的论据

设计为使用 new 运算符实例化为对象的函数如果被错误地调用为普通函数,可能会产生灾难性的影响。在这种情况下,函数的代码将在调用函数的范围内执行,而不是按预期在本地对象的范围内执行。这可能会导致全局变量和属性被覆盖并带来灾难性后果。最后,编写函数 Func(),然后调用 Func.prototype 并向其添加内容,以便您可以调用 new Func() 来构造对象,这对于一些程序员来说似乎很丑陋,他们宁愿使用另一种对象继承风格来进行架构和风格的原因。

有关此论点的更多信息,请查看 Douglas Crockford 的伟大而简洁的书 Javascript:The Good Parts。事实上,无论如何都要检查一下。

支持新的论据

使用 new 运算符和原型赋值很快。如果您总是在构造函数中包含一些代码以检查它们是否被正确调用,并且在它们没有被正确调用的情况下,那么在全局命名空间中意外运行构造函数代码的事情可以很容易地避免,根据需要适当地处理呼叫。

有关此技术的简单说明以及对他所倡导的继承模型的一般更深入的说明,请参见 John Resig's post


反对参数的更新:#1 可以通过使用 'use strict'; 模式(或链接中的方法)来缓解 #2 在 ES6 中具有语法糖,但行为与以前相同。
a
annakata

我同意佩斯和一些在这里。

对我来说,“新”似乎很明显是自我描述的对象创建,Greg Dean 描述的 YUI 模式完全被掩盖了。

有人可以在 baz 不是对象创建方法的情况下编写 var bar = foo;var bar = baz(); 的可能性似乎更危险。


2
2 revs, 2 users 62%

我认为 new 是邪恶的,不是因为如果你忘记错误地使用它可能会导致问题,而是因为它搞砸了继承链,使语言更难理解。

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

另一方面,如果您执行 var newObj=new oldObj(),则 newObj 的原型是 oldObj.prototype,这非常难以理解。

诀窍是使用

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

它在这个函数内部,只有在这里才应该使用 new 。在此之后只需使用 Object.create() 方法。该方法解决了原型问题。


老实说,我对这种技术并不感兴趣——它没有添加任何新东西,正如你的回答所表明的那样,它甚至可能最终成为一个拐杖。恕我直言,理解原型链和对象实例化对于理解 JavaScript 至关重要......但如果直接使用它们让你不舒服,那么使用辅助函数来处理一些细节就可以了,只要你记得你是什么正在做。 FWIW:ECMAScript 第 5 版的一个(更有用的)变体。 std,并且已经在某些浏览器中可用 - 所以你应该小心不要盲目地重新定义它!
顺便说一句:我不确定你为什么制作这个 CW,但如果你想重新发布它并使用我所做的格式更正,请小心避免使用该复选框......
不必要地难以理解? var c = new Car() 与执行 var c = Object.create(Car.prototype); Car.call(c) 相同
C
Chris Buck

IMNSHO“新”是 2021 JavaScript 中的一个有缺陷的概念。它在不需要的地方添加单词。它使函数/构造函数的返回值隐含并强制在函数/构造函数中使用 this。在代码中添加噪音从来都不是一件好事。

// With new
function Point(x, y) {
    this.x = x
    this.y = y
}
let point = new Point(0,0)

比。

// Without new
function Point(x, y) {
    return { x, y }
}
let point = Point(0,0)