当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《JavaScript的"新"关键字被视为有害吗?》经验,为你挑选了12个好方法。

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

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

使用new关键字"不好" 吗?使用它有哪些优缺点?



1> Shog9..:

Crockford已经做了很多工作来推广优秀的JavaScript技术.他对语言关键要素的看法立场引发了许多有益的讨论.也就是说,有太多的人把每一个"坏"或"有害"的宣言当作福音,拒绝超越一个人的意见.有时可能有点令人沮丧.

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

    原型继承.虽然习惯于基于类的OO语言的人经常看到混合的怀疑和嘲笑,但JavaScript的本机继承技术是一种简单且令人惊讶的有效的代码重用方法.新关键字是使用它的规范(并且只有可用的跨平台)方式.

    性能.这是#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是对我们这些谁最初否认有启发这个备受诟病的功能作为噱头).


if(!(this instanceof arguments.callee))throw Error("构造函数称为函数"); //更通用,不需要构造函数名称的知识,让用户修复代码.
如果我拼错了标识符,我的代码会中断.我不应该使用标识符吗?不使用`new`因为你忘记在你的代码中写它就像我的例子一样荒谬.
使用`arguments.callee`检查你是否被`new`调用可能不太好,因为`arguments.callee`在严格模式下不可用.最好使用函数名称.
如果您打开严格模式,如果您忘记使用新模式,则在尝试使用此模式时会出现异常:http://www.yuiblog.com/blog/2010/12/14/strict-mode-is-coming -to-town /这比为每个构造函数添加check实例更容易.
如果你担心表现 - 实际上有理由 - 那就不要做检查了.要么记得使用`new`,要么使用包装函数为你记住它.我怀疑*如果你处于重要的地步,你已经用尽其他优化,例如记忆,所以你的创作电话无论如何都会被本地化......
我使用的一个有用的约定(我认为它来自Crockford本人):如果它需要"new",则将其大写为类名.`x = new Foo();`vs常规函数`x = foo();`
@bzlm:哈哈!从未见过那个人来过!但是:ECMA-262,第5版,15.11.1错误构造函数被称为函数:当Error作为函数而不是构造函数调用时,它会创建并初始化一个新的Error对象.因此,函数调用Error(...)等效于具有相同参数的对象创建表达式new Error(...).
@meandmycode不,你不能100%认为它将是`window`.如果在服务器端使用它,它将不是窗口.你的函数可以使用`call`和`apply`作为`this`调用,所以正确的测试是看它是否是你构造函数的一个实例
@bzlm:有趣的是,它实际上并不重要 - 都是`Error()`和`new Error()`返回`Error`的实例.但是,考虑到背景......这很有趣.

2> some..:

我刚刚阅读了他的Crockfords书中的一些部分"Javascript:The Good Parts".我觉得他认为所有曾经把他咬伤的东西都是有害的:

关于开关坠落:

我从不允许开关案件落到下一个案例.我曾经发现我的代码中的一个错误是由于在发生了一个有力的演讲之后发生了一次无意识的堕落.(第97页,ISBN 978-0-596-51774-8)

关于++和 -

众所周知,++(增量)和 - (减量)运算符通过鼓励过度的诡计来导致错误的代码.它们仅次于故障架构,可以实现病毒和其他安全威胁.(第122页)

关于新的:

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

还有更多,但我希望你能得到这张照片.

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

更新

写完这篇答案大约一年后,第五版ECMAScript发布,支持严格模式.在严格模式下,this不再绑定到全局对象而是绑定到undefined.


我刚才意识到Crockford不认为WHILE有害...我不知道有多少次我创建了一个不定式循环因为我忘了增加一个变量...
有一个约定总是启动带有大写字母的构造函数,以及带有小写字母的所有其他函数.
我对Crockford的回答:编程很难,我们去购物吧.啧!
++, - 同样如此.他们用最清晰的语言表达了我的意图.我爱你!对于某些人来说,切换失误可能是显而易见的,但我对这些感到厌倦.当它们显然更清晰时,我会使用它们('cuse the pun,无法抗拒).
我完全同意.解决方案:始终记录用户如何实例化对象.使用示例,用户可能会剪切/粘贴.每种语言都有可能被滥用的构造/特征,导致异常/意外行为.它不会使它们有害.
很好,先生说,很好说.克罗克福德的天空下降方法并没有帮助JS.
我没看过他的书.但我认为你应该从错误中吸取教训.如果++咬他,那么对他来说,避免++**可能是对的.当然,我全都是为了丰田"五个为什么",所以我可能会深入挖掘并找到为什么它真的**咬我.
for(p = src,q = dest;!*p; p ++,q ++)*q =*p; "他们还鼓励编程风格,事实证明,它是鲁莽的.大多数缓冲区溢出的错误导致了可怕的安全漏洞,这是由于这样的代码."
我不同意Crockfords所说的一些部分,并同意其他一些部分,比如eval.

3> AnthonyWJone..:

Javascript是动态语言,有很多方法可以搞砸另一种语言会阻止你.

避免一个基本的语言功能,例如new你可能会搞砸是有点像在穿过雷区之前移除你的闪亮的新鞋,以防你可能让你的鞋子变得泥泞.

我使用一种约定,其中函数名称以小写字母开头,实际类别定义的"函数"以大写字母开头.结果是一个非常引人注目的视觉线索,'语法'是错误的: -

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

除此之外,良好的命名习惯也有帮助.在所有函数执行之后,因此其名称中应该有一个动词,而类代表对象,是没有动词的名词和形容词.

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

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


是的,我只是在思考语法着色问题.
一旦我忘记输入"功能"这个词.应该不惜一切代价避免这个词.另一次我没有在多行if/then语句中使用花括号.所以现在我不使用花括号而只是写一行条件.

4> 小智..:

我是Javascript的新手,所以也许我只是没有太多的经验,提供一个很好的观点.但我想分享我对这个"新"事物的看法.

我来自C#世界,使用关键字"new"是如此自然,以至于工厂设计模式对我来说看起来很奇怪.

当我第一次使用Javascript编写代码时,我没有意识到存在"新"关键字和代码就像YUI模式中那样,并且我不会花费很长时间来遇到灾难.在回顾我编写的代码时,我忘记了特定行应该做的事情.更混乱的是当我"干运行"代码时,我的思维无法真正在对象实例边界之间转换.

然后,我找到了"new"关键字给我,它"分开"了.使用new关键字,它会创建一些东西.没有new关键字,我知道我不会把它与创建事物混淆,除非我调用的函数给了我强有力的线索.

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


当JS中的几乎所有东西都是对象时,这种反应有点奇怪.当所有函数都是对象时,为什么需要确定函数是一个对象?
同意,我相信工厂模式应遵循命名惯例,如makeFoo()
+1 - 'new'的存在给出了更明确的意图陈述.

5> PEZ..:

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

有可能语言的维护者会优化他们试图鼓励的习语的语言.如果他们在语言中添加了新关键字,他们可能认为在创建新实例时要明确是有意义的.

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

编辑:这远远超出了性能.我不能指望我听过(或说过)"为什么他们这样做?" 找到奇怪的代码.事实证明,在编写代码时,有一些"好"的理由.遵循语言道是你最好的保险,因为几年后你的代码不会被嘲笑.


维尼编码链接+1 - 现在我需要找借口将这个用于我的对话......

6> Juan Mendes..:

我写了一篇关于如何缓解调用不带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);


避免``arguments.callee``太棒了!
`surrogateConstructor`应该是`surrogateCtor`(反之亦然).

7> Greg Dean..:

不使用new关键字背后的基本原理很简单:

通过不使用它,你可以避免意外省略它的陷阱.YUI使用的构造模式是一个如何完全避免使用新关键字的示例"

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

或者你可以这样:

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

但通过这样做,你冒着忘记使用new关键字的风险,而且这个操作符都是fubar.AFAIK这样做没有任何好处(除了你已经习惯了).

在今天结束时:这是关于防守. 你能用新的声明吗?是.它会使您的代码更危险吗?是.

如果您曾经编写过C++,那么删除它们之后就像将指针设置为NULL一样.


所以,只是为了明确这一点:你不应该使用"新",因为你可能会忘记它?你在开玩笑吧?
@Bombe:在其他语言中忘记"new"会导致错误.在Javascript中,它只是继续卡车运输.你可以忘记,也永远不会意识到.而只是看错误的代码*不会在所有错误中显而易见.
不.使用"new foo()",在返回的对象上设置一些属性,如构造函数.
@Bombe - 您应该使用"new",因为您(以及任何使用您代码的人)永远不会犯错误?你在开玩笑吧?
@Greg:我没有看到第一种技术如何允许使用原型链 - 对象文字很棒,但抛弃原型提供的性能和其他优点似乎有点傻.
@KentFredric这就像不使用`==`因为如果我们使用`=`它会导致无声错误.

8> PEZ..:

我认为"新"增加了代码的清晰度.清晰度值得一切.很高兴知道存在陷阱,但通过避免清晰度来避免这些陷阱似乎不适合我.



9> 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`.

10> alegscogs..:

以下是我对使用new运算符的两个最强参数的最简要总结:

争论 new

    设计为使用new运算符实例化为对象的函数 如果作为普通函数被错误地调用,则会产生灾难性的影响.在这种情况下,函数的代码将在调用函数的范围内执行,而不是在预期的本地对象范围内执行.这可能导致全局变量和属性被覆盖,带来灾难性后果.

    最后,编写function Func(),然后调用Func.prototype 并向其中添加内容,以便您可以调用new Func()构造对象,这对某些程序员来说似乎很难看,他们宁愿使用其他样式的对象继承来构建和风格.

有关此论点的更多信息,请查看Douglas Crockford的精彩简洁书籍Javascript:The Good Parts.实际上无论如何要检查它.

争论有利于 new

    使用new运算符和原型分配很快.

    如果你总是在构造函数中包含一些代码来检查它们是否被正确调用,那么可以很容易地防止在全局命名空间中意外运行构造函数的代码,以及在它们不是这样的情况下,根据需要适当地处理呼叫.

有关此技术的简单解释,请参阅John Resig的帖子,并对他提倡的继承模型进行更深入的解释.



11> annakata..:

我同意pez和一些人在这里.

对我来说很明显,"新"是自描述对象创建,其中GUI Dean描述的YUI模式完全被模糊.

可能有人会写var bar = foo;var bar = baz();其中巴兹不是一个对象创建方法似乎远远更加危险.



12> 小智..:

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

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,已经在某些浏览器中可用 - **所以你应该注意不要盲目地重新定义它!**
不必要地难以理解?`var c = new Car()`与`var c = Object.create(Car.prototype)相同; Car.call(C)`
推荐阅读
ERIK又
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有