JavaScript的“新”关键字被视为有害吗?

Is JavaScript's “new” keyword considered harmful?

在另一个问题中,一个用户指出,new关键字的使用是危险的,并提出了一个不使用new的对象创建解决方案。我不相信这是真的,主要是因为我使用了原型、脚本库和其他优秀的JavaScript库,而且每个库都使用了new关键字。

尽管如此,昨天我在Yui剧院看了道格拉斯·克罗克福德的演讲,他说的完全一样,他在他的代码中不再使用new关键字(crockford on javascript-Act III:Function t he Ultimate-50:23分钟)。

使用new关键字是否"坏"?使用它的优点和缺点是什么?


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

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

  • 原型继承。虽然经常被那些习惯于基于类的OO语言的人所怀疑和嘲笑,但javascript的本地继承技术是一种简单而有效的代码重用方法。新的关键字是使用它的规范(并且只有跨平台可用)方法。
  • 性能。这是1的副作用:如果我想为我创建的每个对象添加10个方法,我可以编写一个创建函数,手动将每个方法分配给每个新对象…或者,我可以将它们分配给创建函数的prototype,并使用new来消除新对象。这不仅速度更快(原型上的每个方法都不需要代码),而且避免了使用每个方法的单独属性对每个对象进行膨胀。在创建许多对象时,在速度较慢的计算机(尤其是速度较慢的JS解释器)上,这意味着可以显著节省时间和内存。
  • 是的,new有一个关键的缺点,可以用其他答案来描述:如果你忘记使用它,你的代码将在没有警告的情况下中断。幸运的是,这种缺点很容易减轻——只需向函数本身添加一点代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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的优点,而不必担心意外误用造成的问题。你甚至可以在检查中添加一个断言,如果一想到死掉的代码会让你感到不安的话。或者,正如一些人所说,使用检查引入运行时异常:

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

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

    JohnResig在他的简单的"类"实例化帖子中详细介绍了这一技术,并在默认情况下将这种行为构建到您的"类"中。绝对值得一读…正如他即将出版的书《Javascript忍者的秘密》一样,这本书在Javascript语言的这个和许多其他"有害"特性中找到了隐藏的黄金(关于EDOCX1的章节〔5〕对我们这些最初把这个备受非议的特性视为一种诡计的人特别有启发性)。


    我刚刚读了他的藏书《javascript:好的部分》的一些部分。我觉得他认为所有咬过他的东西都是有害的:

    关于开关坠落:

    I never allow switch cases to fall
    through to the next case. I once found
    a bug in my code caused by an
    unintended fall through immediately
    after having made a vigorous speech
    about why fall through was sometimes
    useful. (page 97, ISBN
    978-0-596-51774-8)

    关于++和

    The ++ (increment) and -- (decrement)
    operators have been known to
    contribute to bad code by encouraging
    exessive trickiness. They are second
    only to faulty architecture in
    enabling viruses and other security
    menaces. (page 122)

    关于新的:

    If you forget to include the new
    prefix when calling a constructor
    function, then this will not be
    bound to the new object. Sadly, this
    will be bound to the global object, so
    instead of augmenting your new object,
    you will be clobbering global
    variables. That is really bad. There
    is no compile warning, and there is no
    runtime warning. (page 49)

    还有很多,但我希望你能拍到照片。

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

    更新

    在这个答案写完一年后,第五版EcmaScript发布了,支持严格模式。在严格模式下,this不再与全局对象绑定,而是与undefined绑定。


    JavaScript是一种动态语言,有无数种方法可以让其他语言阻止你。

    避免一个基本的语言功能,如new的基础上,你可能会搞砸,这有点像在穿过雷区前脱下你闪亮的新鞋,以防你的鞋弄脏。

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

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

    除此之外,良好的命名习惯还有帮助。在所有函数都做了一些事情之后,它的名称中应该有一个动词,而类代表对象,是没有动词的名词和形容词。

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

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


    我是Javascript的新手,所以也许我在提供一个好的观点方面没有太多经验。然而,我想分享我对这个"新"事物的看法。

    我来自C世界,在那里使用关键字"new"是很自然的,以至于我觉得工厂设计模式很奇怪。

    当我第一次用javascript编写代码时,我没有意识到有"new"关键字和类似于yui模式的代码,我很快就会遇到灾难。当回顾我编写的代码时,我会忘记一行代码应该做什么。更混乱的是,当我"干运行"代码时,我的思想不能真正地在对象实例边界之间转换。

    然后,我找到了"new"关键字,对我来说,它是"separate"的东西。使用新的关键字,它可以创建一些东西。如果没有新的关键字,我知道我不会把它与创建东西混淆,除非我调用的函数给了我有力的线索。

    例如,对于var bar=foo();,我没有任何线索,因为什么条可能是……它是返回值还是新创建的对象?但对于var bar = new foo();,我确信酒吧是一个目标。


    另一个新的例子是我称之为pooh编码。小熊维尼跟着他的肚子。我说用你用的语言去做,而不是反对它。

    这种语言的维护者很有可能会优化语言,使之适合他们想要鼓励的习语。如果他们在语言中加入一个新的关键字,他们可能会认为在创建一个新的实例时要清楚是有意义的。

    遵循语言意图编写的代码在每次发布时都会提高效率。避免使用该语言的关键结构的代码将随着时间的推移而受到影响。

    编辑:这远远超出了性能。我数不清我听到(或说)"他们为什么要那样做?"当发现奇怪的代码时。通常情况下,在编写代码时,有一些"好"的理由。遵循语言之道是你最好的保险,因为几年后你的代码就不会被嘲笑了。


    我写了一篇关于如何减轻在没有新关键字的情况下调用构造函数的问题的文章。它主要是说教性的,但它展示了如何创建使用或不使用new的构造函数,并且不需要添加样板代码来测试每个构造函数中的this

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

    技术要点如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /**
     * 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() {}
    }

    以下是如何使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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);


    不使用新关键字的原因很简单:

    完全不使用它,可以避免意外忽略它带来的陷阱。Yui使用的构造模式是如何完全避免使用新关键字的一个例子。"

    1
    2
    3
    4
    5
    var foo = function () {
        var pub= { };
        return pub;
    }
    var bar = foo();

    或者你可以这样做:

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

    但是这样做的话,你就有可能有人忘记使用新的关键字,而这个操作符就是fubar。阿法克这样做没有好处(除了你已经习惯了)。

    在一天结束的时候:这是关于防御性的。你能用新的说法吗?对。这会使您的代码更危险吗?对.

    如果您曾经编写过C++,那么在删除指针后,将指针设置为null是类似的。


    我认为"新"增加了代码的清晰度。清晰是值得的。很高兴知道有陷阱,但通过避免清晰来避免它们对我来说似乎不是一种方式。


    案例1:new不是必需的,应该避免。

    1
    2
    3
    4
    5
    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

    案例二:需要new,否则会出错

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


    下面是我对支持和反对使用new运算符的两个最有力的论点所作的最简要的总结:

    反对new的论点

  • 功能设计为使用new运营商可能造成灾难性后果。不正确的影响作为普通函数调用。一在这种情况下,函数的代码将在以下范围内执行:调用函数,而不是本地对象的作用域有意的。这可能导致全球要获取的变量和属性被灾难覆盖后果。
  • 最后,写了function Func(),然后打电话给Func.prototype。在里面加入一些东西可以调用new Func()来构造你的东西对某些人来说很难看希望使用的程序员对象继承的另一种样式建筑和风格原因。
  • 关于这个论点的更多信息,请看道格拉斯·克罗克福德的伟大而简明的书《javascript:好的部分》。事实上,还是去看看吧。

    支持new的论点

  • 同时使用new运算符原型分配很快。
  • 关于意外的事情运行构造函数函数的全局命名空间中的代码可以如果你总是在您的要检查的构造函数函数看看是否有人打电话给他们正确,并且在以下情况下他们没有,处理电话根据需要适当。
  • 关于这种技术的简单解释,以及他所提倡的继承模型的一般更深入的解释,请参阅约翰·雷西格的文章。


    我同意佩兹和这里的一些人。

    在我看来,"新"显然是自我描述的对象创造,格雷格·迪恩描述的Yui模式被完全掩盖了。

    有人可能在baz不是对象创建方法的地方编写var bar = foo;var bar = baz();,这看起来危险得多。


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

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

    另一方面,如果您执行var newObj=new oldObj(),newobj的原型是oldobj.prototype,这是不必要的难以理解。

    诀窍是使用

    1
    2
    3
    4
    5
    6
    Object.create=function(proto){
      var F = function(){};
      F.prototype = proto;
      var instance = new F();
      return instance;
    };

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