JavaScript的“new”关键字是否有害?(第二部分)

12
阅读以下问题,我感觉大部分答案都没有理解为什么有些人(比如Crockford)选择不使用 "new" 关键字。这并不是为了防止意外地在不使用 "new" 关键字的情况下调用函数。
根据Crockford关于原型继承的文章,他实现了一种对象创建技术,更清晰地展示了JS的原型性质。这种技术现在甚至已经在JS 1.8.5中实现。
他反对使用 new 的论点可以更清晰地总结为:
“这种间接性旨在使语言对于受过经典训练的程序员更加熟悉,但它没有做到这一点,我们可以从Java程序员对JavaScript的非常低的评价中看出来。JavaScript的构造器模式并没有吸引经典派的程序员。它也掩盖了JavaScript真正的原型性质。因此,很少有程序员知道如何有效地使用这种语言。”
我不一定认为“new”有害,但我同意它确实“掩盖了JavaScript的真正原型性质”,因此我在这一点上同意Crockford的看法。
您对使用更清晰的原型对象创建技术而不是使用 "new" 关键字有何看法?

8
有人必须这样做:《被视为有害的“Considered Harmful”论文》(http://meyerweb.com/eric/comment/chech.html) - T.J. Crowder
你将它标记为社区维基,但实际上并没有将问题设置为社区维基。 - Matt
@Matt:现在这是管理员的功能。 - T.J. Crowder
@Matt - 由于某些原因,复选框不可用。 - Steve
3
@T.J. - “过多的基于 Stack Overflow 的网站被视为有害的” - Steve
显示剩余5条评论
3个回答

13
你说得对,使用new被认为是有害的,因为它没有充分强调JavaScript中的面向对象是基于原型的。主要问题在于它过于类似于经典类,而一个人不应该考虑class。应该考虑对象,并使用现有对象作为蓝图创建新对象。 new是JavaScript接受"流行"的Java语法的遗迹。
现在,我们使用Object.create 进行原型面向对象,并使用ES5 shim
不再需要new
在ES5普及之前(只有FF3.6和IE8比较落后),我们使用new,因为别无选择。 someFunction.prototype 是实现原型继承的方法。
我最近写了一篇“好”的Object.create演示,尽管我仍然需要解决使用中的一些问题。重要的是要将对象与工厂函数分开。

“...只有Opera和IE8表现不佳...” 仍然有很多Firefox 3.6的用户存在... - T.J. Crowder
@T.J.Crowder 他们应该升级。 - Raynos
@Raynos,Object.create方法的第二个参数可以让你覆盖原有的方法吗?如果使用Object.create创建对象,如何添加或覆盖其中的方法?请参考:http://stackoverflow.com/questions/9022519/override-methods-with-prototypal-inheritance - ryanve
奇怪的是没有人注意到Object.create的性能缺陷:http://jsperf.com/object-create-vs-crockford-vs-jorge-vs-constructor/27 - Misha Reyzlin
@gryzzly 这些都是微不足道的。浏览器会优化用户使用的内容。大多数面向对象编程都是使用 new 完成的,所以当然更快。如果在一个流行的基准测试中大量使用 Object.create,那么 Object.create 就会变得更快。 - Raynos
显示剩余2条评论

10

我认为new并没有让JavaScript的原型特性变得难以理解。当然,这是因为我花了几年时间深入了解了这门语言(最初我犯了一个错误,没有弄清楚这门语言的实际工作方式就开始编写代码,结果付出了代价)。

我发现使用new的表达方式比在函数名前加上"new"或使用辅助函数等更具有表现力:

var fido      = new Dog();          // Simple, clear
var rover     = Dog();              // Very unclear (fortunately people mostly don't do this, but I've seen it)
var scruffles = newDog();           // Clearer, but hacky
var fifi      = Object.create(Dog); // (Crockford) Verbose, awkward, not esp. clear

无论是基于原型还是基于类(这与new关键字没有任何关系),我都认为最好按功能分组成构建块(对象、类或其他)然后处理特定的对象,这样就不会影响到那些构建块。对我而言,new只是一个清晰表达意图的手段,既不优雅也不基于原型,但很明确。
对我来说,new和原型一样是JavaScript中极其重要的部分,正如非常灵活的函数所起的作用一样。它们之间并不存在矛盾。
实际上,有大量程序员习惯使用new来创建新的实例,尽管Crockford曾经努力反对这种做法(请勿误解,我非常尊重Crockford)。如果我要招聘项目人员,我不想让他们重新接受培训。
这并不意味着你在JavaScript中定义原型对象的方式是美丽而永恒的事情。这很麻烦,特别是在没有ECMAScript5增强的情况下。但可以定义一个辅助函数来帮助你连接你的层次结构(无论你想称之为什么),这样就完成了。(请参见下面的更新。)
[是的,关于构造函数在没有new的情况下被调用的整件事,确实是一个转移话题(你也说过你不认为这是主要观点)。你在现实世界中是几乎不会看到它发生的。实际上,JavaScript对它所做的一些事情相当不错(例如StringNumber转换);其他部分(Date的行为**发抖**)则不太好... ]
自ES2015年(ES6)以来,由于新的类语法,创建原型和构造函数层次结构变得非常容易。不再需要帮助程序了。
class Base {
    constructor(name) {
        this.name = name;
    }
    greeting() {
        return "Hi, I'm " + this.name;
    }
}

class Derived extends Base {
    constructor(name, age) {
        super(name);
        this.age = age;
    }
    greeting() {
        return super.greeting() + " and I'm " + this.age + " years old";
    }
}

当然,这并不意味着我们需要在所有情况下都使用它们。当我需要扩展一个单一对象时,我会使用 Object.create 来取得很好的效果。


1
这是Object.create的一个很大的风格问题,缺乏语法糖。我似乎需要创建蓝图对象来克隆,然后为每个对象创建一个单独的工厂函数,实际上有两个对象。我认为这更多是因为不理解原型面向对象编程,并且通过花几天时间在Self中编写代码可以解决这个问题。 - Raynos
1
@Raynos: 也许吧。你可以这样做:var nu = Object.create; 然后 var spot = nu(Dog);。你知道的,如果你想要真正使事情变得非标准化。;-) - T.J. Crowder
4
@T.J. 哈哈... "Nu" 是新的“新”。 - Steve
@T.J.Crowder var nu = Object.create.bind(Object) 谁知道如果上下文是window而不是Object会发生什么破坏 - Raynos
@Raynos:是的,我也考虑过这个问题,但我认为在现实世界中这不会成为一个问题,而且我讨厌引入更多的间接性... 测试用例 但你当然是正确的。 - T.J. Crowder
显示剩余2条评论

3
我的最大问题是 new 违反了开放/封闭原则。由于 new 操作符会操纵 this 的值,构造函数变得不可扩展,这意味着如果您需要进行扩展,则必须修改它,可能会破坏调用者。
以下是 new 不允许的工厂如何进行扩展的一些示例:
隐藏对象创建的详细信息。(其余示例说明为什么您可能想要这样做)
在工厂对象上存储可变原型和 init 函数,并使用 this 访问它们。(使用 newthis 总是指新创建的对象)。
通过允许您向工厂对象添加功能来扩展可能的对象类型池(可扩展多态性)。
在实例化期间使用 .call().apply() 重新定义 this 的含义(扩展代码重用)。
返回到另一个存储空间中的新对象的代理(iframe、window、不同的机器)。
有条件地返回对现有对象的引用(实际上,您可以使用 new 和构造函数来做到这一点,但是在构造函数内部,this 是没有意义的并且会误导,因为会分配一个新实例然后被丢弃,如果您返回一个新的对象,则会有其他不同)。
使实例化策略可以进行更改而不影响调用者。
注意:我经常感到恼火,因为我不能轻松地使用这些策略中的任何一个来构建 Backbone.js,因为它强制您使用 new,除非您将其包装起来,但是这就关闭了其标准继承机制。相比之下,我从未发现自己感到恼火 jQuery 使用工厂函数来实例化 jQuery 包装的 DOM 集合。

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