在另一个问题中,用户指出该new
关键字使用起来很危险,并提出了一种不使用的对象创建解决方案new
.我不相信这是真的,主要是因为我使用了Prototype,Scriptaculous和其他优秀的JavaScript库,并且每个人都使用了new
关键字.
尽管如此,昨天我在YUI剧院观看道格拉斯·克罗克福德的演讲,他说的完全一样,他new
在代码中不再使用关键字(Crockford on JavaScript - Act III:Function the Ultimate - 50:23分钟).
使用new
关键字"不好" 吗?使用它有哪些优缺点?
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
是对我们这些谁最初否认有启发这个备受诟病的功能作为噱头).
我刚刚阅读了他的Crockfords书中的一些部分"Javascript:The Good Parts".我觉得他认为所有曾经把他咬伤的东西都是有害的:
关于开关坠落:
我从不允许开关案件落到下一个案例.我曾经发现我的代码中的一个错误是由于在发生了一个有力的演讲之后发生了一次无意识的堕落.(第97页,ISBN 978-0-596-51774-8)
关于++和 -
众所周知,++(增量)和 - (减量)运算符通过鼓励过度的诡计来导致错误的代码.它们仅次于故障架构,可以实现病毒和其他安全威胁.(第122页)
关于新的:
如果你忘记了包含新的 调用构造函数时前缀,那么这将不会被绑定到新的对象.遗憾的是,这 将绑定到全局对象,因此不会增加新对象,而是破坏全局变量.那真是太糟糕了.没有编译警告,也没有运行时警告.(第49页)
还有更多,但我希望你能得到这张照片.
我对你问题的回答:不,这没有害处.但如果你忘记使用它,你应该有一些问题.如果你在一个良好的环境中发展,你会注意到这一点.
更新
写完这篇答案大约一年后,第五版ECMAScript发布,支持严格模式.在严格模式下,this
不再绑定到全局对象而是绑定到undefined
.
Javascript是动态语言,有很多方法可以搞砸另一种语言会阻止你.
避免一个基本的语言功能,例如new
你可能会搞砸是有点像在穿过雷区之前移除你的闪亮的新鞋,以防你可能让你的鞋子变得泥泞.
我使用一种约定,其中函数名称以小写字母开头,实际类别定义的"函数"以大写字母开头.结果是一个非常引人注目的视觉线索,'语法'是错误的: -
var o = MyClass(); // this is clearly wrong.
除此之外,良好的命名习惯也有帮助.在所有函数执行之后,因此其名称中应该有一个动词,而类代表对象,是没有动词的名词和形容词.
var o = chair() // Executing chair is daft. var o = createChair() // makes sense.
有趣的是SO的语法着色如何解释上面的代码.
我是Javascript的新手,所以也许我只是没有太多的经验,提供一个很好的观点.但我想分享我对这个"新"事物的看法.
我来自C#世界,使用关键字"new"是如此自然,以至于工厂设计模式对我来说看起来很奇怪.
当我第一次使用Javascript编写代码时,我没有意识到存在"新"关键字和代码就像YUI模式中那样,并且我不会花费很长时间来遇到灾难.在回顾我编写的代码时,我忘记了特定行应该做的事情.更混乱的是当我"干运行"代码时,我的思维无法真正在对象实例边界之间转换.
然后,我找到了"new"关键字给我,它"分开"了.使用new关键字,它会创建一些东西.没有new关键字,我知道我不会把它与创建事物混淆,除非我调用的函数给了我强有力的线索.
例如,var bar=foo();
我没有任何线索,因为它可能是什么吧....它是一个返回值还是一个新创建的对象?但var bar = new foo();
我肯定知道酒吧是一个对象.
另一种情况为新就是我所说的维尼编码.小熊维尼跟着他的肚子.我说用你正在使用的语言,而不是反对它.
有可能语言的维护者会优化他们试图鼓励的习语的语言.如果他们在语言中添加了新关键字,他们可能认为在创建新实例时要明确是有意义的.
按照语言的意图编写的代码将在每次发布时提高效率.避免语言关键结构的代码会随着时间的推移而受到影响.
编辑:这远远超出了性能.我不能指望我听过(或说过)"为什么他们这样做了?" 找到奇怪的代码.事实证明,在编写代码时,有一些"好"的理由.遵循语言道是你最好的保险,因为几年后你的代码不会被嘲笑.
我写了一篇关于如何缓解调用不带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);
不使用new关键字背后的基本原理很简单:
通过不使用它,你可以避免意外省略它的陷阱.YUI使用的构造模式是一个如何完全避免使用新关键字的示例"
var foo = function () { var pub= { }; return pub; } var bar = foo();
或者你可以这样:
function foo() { } var bar = new foo();
但通过这样做,你冒着忘记使用new关键字的风险,而且这个操作符都是fubar.AFAIK这样做没有任何好处(除了你已经习惯了).
在今天结束时:这是关于防守. 你能用新的声明吗?是.它会使您的代码更危险吗?是.
如果您曾经编写过C++,那么删除它们之后就像将指针设置为NULL一样.
我认为"新"增加了代码的清晰度.清晰度值得一切.很高兴知道存在陷阱,但通过避免清晰度来避免这些陷阱似乎不适合我.
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
new
是必需的,否则你会收到错误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()
构造对象,这对某些程序员来说似乎很难看,他们宁愿使用其他样式的对象继承来构建和风格.
有关此论点的更多信息,请查看Douglas Crockford的精彩简洁书籍Javascript:The Good Parts.实际上无论如何要检查它.
new
使用new
运算符和原型分配很快.
如果你总是在构造函数中包含一些代码来检查它们是否被正确调用,那么可以很容易地防止在全局命名空间中意外运行构造函数的代码,以及在它们不是这样的情况下,根据需要适当地处理呼叫.
有关此技术的简单解释,请参阅John Resig的帖子,并对他提倡的继承模型进行更深入的解释.
我同意pez和一些人在这里.
对我来说很明显,"新"是自描述对象创建,其中GUI Dean描述的YUI模式完全被模糊.
可能有人会写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,这是不必要的难以理解的.
诀窍是使用
Object.create=function(proto){ var F = function(){}; F.prototype = proto; var instance = new F(); return instance; };
它在这个函数里面,只有在这里才应该使用new.在此之后,只需使用Object.create()方法.该方法解决了原型问题.