JavaScript: 好的部分 - 如何完全不使用 `new`

58

Crockford在他的书《JavaScript权威指南》(第114页)中表示,构造函数的名称应该始终以大写字母开头(例如Point),而其他函数名应该使用小写字母,只有构造函数应该使用大写字母开头。

这个约定有助于我们避免在使用构造函数时忘记使用new运算符。

他接着说:"更好的策略是根本不使用new。"

那么如何在JavaScript编程中完全不使用new呢?

  • 我们可以用字面量{}[]来避免使用new Object()new Array()
  • 我们可以用0true''来避免使用new Number()new Boolean()new String()
  • 我们可以使用类似于/pattern/的东西来避免使用new RegExp()

那么如何避免使用new Date()呢?

最重要的是,如何避免在自定义对象中使用new呢?


1
原型制作,原型制作,原型制作。 - Oded
他有什么论据?我有一个完整的项目,很大程度上依赖于“new”,它在组织方面帮了很大的忙。 - pimvdb
3
我实在想不通为什么你会想要放弃这样一个结构,似乎只是出于自己的懒惰或者为了弥补自己的健忘。不过还是挺有趣的。 - Grant Thomas
1
@GrantThomas,我认为这是为了弥补在某些编程语言中可以省略单词“new”,并且直到运行时才发现错误的“愚蠢”而设计的。 - ToolmakerSteve
9个回答

42

在他的JavaScript演讲中,Crockford给出了一个对象创建函数的示例,这个函数应该由JS本身提供,可以在http://developer.yahoo.com/yui/theater/找到。

然而,YUI(3)团队本身使用"new",他们确实遵循了他的建议(因为他曾经是雅虎首席JS架构师(更新:当这个回答最初写下时,此说法是正确的)。我理解这个具体的说法更多地是在“学术”层面上,如果语言被设计得“正确”,而不是带有一些基于类的继承的剩余物,那么它应该是什么样子的。他(我认为是正确的)指出JS的现状非常矛盾,基于原型,但同时也有来自“经典类”继承语言的东西。

然而,JS就是这样,所以继续使用“new”。

你可以在这里找到他的对象创建函数:http://javascript.crockford.com/prototypal.html

 if (typeof Object.create !== 'function') {
     Object.create = function (o) {
         function F() {}
         F.prototype = o;
         return new F();
     };
 }
 newObject = Object.create(oldObject);

编辑:更新使用Crockford的最新版本该函数 - 有三个。


更新 2015年6月:我们已经有了Object.create(...) 很长一段时间了,所有现代浏览器都支持(包括IE9及以上版本),因此没有必要使用Crockford的函数。

然而,事实证明,如果你使用Object.create,你应该确保不要频繁使用:那个函数比使用new Constructor()慢得多!

请参见http://mrale.ph/blog/2014/07/30/constructor-vs-objectcreate.html以获得解释(针对V8引擎),并查看http://jsperf.com/object-create-vs-crockford-vs-jorge-vs-constructor/62以获取性能演示。

不转向new Constructor(...)的另一个原因是,ES6类即使只是因为大多数JavaScript开发人员来自基于类的语言,也肯定会得到广泛的采用。

还要查看这篇文章,该文章辩称支持Object.createhttp://davidwalsh.name/javascript-objects-deconstruction

无论你喜欢与否,特别是在你想与广泛的人(在空间和时间上 - 意味着现在或以后,在你之后由其他人接管)分享项目时,使用new的原因更多。

更新 2015年9月:对于我自己,我已经开始使用ES 2015 JavaScript来做所有事情 - 使用io.js和/或Babel。我在我的项目中也不使用任何new,除了像new Error(...)这样的JavaScript内置函数。我更喜欢使用功能强大得多的函数式方法,我完全忽略对象系统。[my-object].prototypethis在我的项目中完全消失了。长期以来,我非常怀疑这些想法,因为“对象可以正常工作”。但是,在新(io.js)项目的开始非常勉强地尝试之后,它“就开窍了”,我不明白为什么我浪费了20年时间。好吧,不完全是这样,今天JS引擎和硬件更有利于这种风格。特别是在ES 2015中,我建议尝试一个完全不包含thisclass(新的ES 2015关键字或基于使用constructorFn.prototype)的函数式风格。可能需要你几个星期,但一旦“开窍了”,我保证你永远不会回去 - 不自愿的。它更方便、更强大。

更新 2018年2月:虽然我仍然执行我在上一个更新中写的内容,但现在我想要补充一点,有时候


你是指特定的“函数式风格”吗?还是只是将函数式编程哲学应用于Javascript以消除其中一些问题? - djfdev
1
@Mörre Noseshine 关于您的更新(2015年9月),我真的很想看看您是如何实现这一点的(不使用任何“new”或“this”)。您能提供一个例子吗?您是否使用工厂函数以及Object.create来实现这一点?谢谢。 - mbx-mbx
4
不需要。我只是调用函数。您在对象中存储的状态,我将其存储在函数的变量中 - 使用语言的(词法)作用域规则。如果外部需要访问我的内部状态,我会返回一个带有(仅)方法的对象(这些方法位于我的词法范围内,因此具有访问权限),只是一个对象文本容器。这是必需的,因为一个函数只能有一个返回值。词法作用域取代了对象。 - Mörre
1
请不要破坏您的帖子。 - greg-449
1
我不会称之为破坏行为,而是启蒙。 - user6445533

12

我不知道如何避免使用new Date()new XMLHttpRequest()。但我知道如何避免在我的自定义类型中使用new

首先,我使用Object.create()。这是一个ES5的方法,因此并非在所有地方都可用。我使用es5-shim添加它,然后我就可以开始了。

我喜欢模块模式,所以我开始将我的类型包装在自执行匿名函数中,var Xyz = (function() {...})()。这意味着我有一个私有空间可以工作,而不会在全局命名空间中搞砸。我返回一个带有create()函数和prototype属性的对象。 create()函数是给我的类型的用户使用的。当他们想要创建一个时,他们调用Xyz.create(),然后得到一个新的、初始化好的对象。如果需要继承,则可以使用prototype属性。

下面是一个示例:

var Vehicle = (function(){
        var exports = {};
        exports.prototype = {};
        exports.prototype.init = function() {
                this.mph = 5;
        };
        exports.prototype.go = function() {
                console.log("Going " + this.mph.toString() + " mph.");
        };

        exports.create = function() {
                var ret = Object.create(exports.prototype);
                ret.init();
                return ret;
        };

        return exports;
})();

继承看起来像这样:

var Car = (function () {
        var exports = {};
        exports.prototype = Object.create(Vehicle.prototype);
        exports.prototype.init = function() {
                Vehicle.prototype.init.apply(this, arguments);
                this.wheels = 4;
        };

        exports.create = function() {
                var ret = Object.create(exports.prototype);
                ret.init();
                return ret;
        };

        return exports; 

})();

11

不使用new并盲目追随Crockford是愚蠢的。

了解JavaScript并编写良好的代码。使用new关键字是JavaScript面向对象编程的基石

避免使用new会使你错过很多优秀的JavaScript代码。

与其任意地削减你的工具包,不如学习并正确使用它。

Crockford有一种说法,认为JavaScript中任何曾经导致他代码出错的都是不好的。

我个人会进一步说:"[更好的应对策略是变得能干]"


25
我不会说new是JavaScript面向对象编程的基石。 - Sean McMillan
11
@Raynos: 我不同意;虽然 {}new Object() 有相同的结果(一个新的空对象),但它们在于 {} 不使用 new 关键字。如果你使用 es5 的 Object.create(),那么你只需要在 DateError (在核心JS中)这两个地方使用 new。你可以编写实质性、组织良好的javascript代码,而不使用 new - Sean McMillan
4
@Raynos:当然,绝对没问题。对 JavaScript 中 new 的担忧主要是因为无法确定一个函数是否需要 new,如果忘了使用 new 就会在全局对象上运行构造函数,从而导致破坏 this 在全局函数中的作用。可怜的 new 被责备成全局函数的 this 失效的罪魁祸首。 - Sean McMillan
4
嗯...你拼错了 "competent" :) - fredoverflow
8
@SeanMcMillan,两个月后我同意,“new” 很糟糕,应该使用 “Object.create” :) - Raynos
显示剩余6条评论

6

这个问题已经被问过并得到了回答:JavaScript的"new"关键字是否有害?

正如Raynos所说,不理解为什么Crockford(或其他人)会说出他们所说的话,盲目地跟随是愚蠢的。


非常好的观点,因为在这个语境下,Crockford谈到了JS的历史以及它的更广泛背景,以及它应该是什么样子。由于我们最终使用的是JS现有的形式,所以我们使用“new”。 - Mörre

6

通过创建工厂函数,您可以避免使用new

var today = Date.getToday();

(如果你想知道的话,你不能在工厂函数本身上避免它 :))
Date.getToday = function() { return new Date(); };

我认为只有在添加语义值(如上述情况)或者可以设置一些构造函数参数的默认值时,才应该创建这样的函数。换句话说,不要仅仅为了避免使用 new 而这样做。


1
我认为他的意思是:一个人会这样做:Date.prototype.getToday = function() { ... } - Pointy
1
虽然这是正确的,但这不是道格拉斯·克罗克福德想要表达的,而这个问题正是关于这个的。请参见http://javascript.crockford.com/prototypal.html。 - Mörre
@Pointy:不,他的意思就是他写的那样。重点是要能够编写Date.getToday()而不是new Date()。如果他在原型上声明了该函数,那么他将不得不编写new Date().getToday(),从而打败了练习的目的。 - KaptajnKold
哦,好的,我现在明白你们在做什么了。感谢澄清(虽然最初并不是不清楚;我只是有点困惑)。 - Pointy
2
特别是为您自己的类型提供工厂。即使您必须使用 new,也不要让客户使用它。 - Sean McMillan

3

我认为他不建议完全不使用new是概念性(学术)的,不能字面理解。对于Date类来说,它是一个完美的例外,因为除此之外如何使用标准ECMAScript获取当前(或任意)日期对象呢?

然而,对于自定义对象,不使用new有几种策略可供选择。一种方法是使用工厂式的方法替代构造函数,可以将一个对象实例作为参数传递给该方法,以“祝福”您的新类型,或者默认使用新的对象字面量。请考虑以下内容:

var newCar = function(o) {
  o = o || {};
  // Add methods and properties to "o"...
  return o;
}

2
你可以在实例化别人的对象时使用“new”,但是对于你自己的代码,你可以避免使用“this”和“new”的陷阱。
(顺便说一下,“new”本身并没有问题。但是使用“new”实例化的对象可能会在内部使用“this”。使用“this”会导致频繁而微妙的错误,因为javascript的“this”要求调用方法的调用者额外地绑定被调用的方法到正确的对象上,并且不正确的绑定在运行时之前很难进行检测或者其他提示。) 简短版: 要避免使用“new”实例化你编写的对象,只需从任何函数返回一个对象。在该函数内部,将任何方法附加到该对象并进行任何初始化。完成——该函数既是你的类定义又是构造函数。 长版,带示例: 以下“self”模式避免了使用“new”和“this”。虽然这种模式在每个对象中都存储方法副本,但除非你在运行时创建很多对象,否则这并不重要。如果是这种情况,那么你可以始终使用“flyweight”模式。(虽然该页面上的示例仍然稍微使用了一些“this”,但是将这些示例适应以下模式稍作调整即可。)

// this function defines a "class" -- the whole function is the constructor
function Foo(x) {
    // create/init whatever object type you want here
    var self = {x: x};
    // if subclassing, for instance, you can do:
    // var self = Baz(x);

    // public method
    self.foo = function() {
        return self.x;
    };

    // public method
    self.bar = function() {
        console.log(self.x);
        logger();
    };

    // private method
    function logger() {
        console.log(self.x);
    }

    // ...more constructor bits can go here

    // don't forget to return self
    return self;
}

var f = Foo(1); 
var g = Foo(2); 
setTimeout(f.bar, 1000);
setTimeout(g.bar, 1000);

console.log(g.foo()); // 2
g.x = 5;
console.log(f.foo()); // 1
console.log(g.foo()); // 5

// ...then, 1 second later:
// 1  (from f.bar)
// 1  (from f.logger)
// 5  (from g.bar)
// 5  (from g.logger)

// blows up if uncommented
// f.logger();


1
function F() { return { /* your fields and methods here */ } }

1
您可以通过返回匿名对象并在构造函数中使用闭包来避免使用 "new"。这也有助于隐藏私有数据。
考虑以下示例:
function SomeCounter(start) {

    var counter = start;

    return {
        getCounter : function() {
            return counter;
        },

        increaseCounter : function() {
            counter++;
        }

    };
}

现在要使用它,你只需要做的就是

var myCounter = SomeCounter(5);
myCounter.increaseCounter();
console.log(myCounter.getCounter()); //should log 6

这个的美妙之处在于你不需要记得使用“new”,但如果你使用它也不会有任何问题。
var myCounter = new SomeCounter(5); //still works

1
虽然这是真的,但如果你在不需要它的情况下使用 new 调用函数,我会非常不高兴。而将所有方法都转换为闭包意味着你需要为类型的每个实例存储每个方法。 - Sean McMillan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接