匿名类实例 - 这是一个不好的主意吗?

52

在 ES6 中,我们可以使用匿名类:

var entity = class {
}

但是我们也可以立即实例化它:

var entity = new class {
    constructor(name) { this.name = name; }
    getName() { return this.name; }
}('Foo');
console.log(entity.getName()); // Foo

背后做了什么,它会带来什么好处,也会带来什么注意事项?


1
个人口味;我喜欢C++和Java风格的对象声明。 - Steve Fan
我认为这种方式和普通的原型类创建没有什么不同,只是语法上的差异。https://www.quora.com/Is-class-syntax-in-ECMAScript-6-just-a-syntactic-sugar-for-classical-prototypes/answer/Fionn-Kelleher-1?srid=kdJ8 - evolutionxbox
看起来这篇博客文章与我的问题相关,还不错:http://jasonwyatt.co/post/866536821/anonymous-classes-with-javascript-and-self-calling - Steve Fan
2
ES5 也有匿名构造函数:entity = new function(name) { this.name = name }("Foo")class 只是语法糖,用于表示一个构造函数和一些额外的内容。因此,当存在匿名构造函数时,也会存在匿名类。 - user6445533
实际上,你发布的示例会抛出一个“TypeError: Cannot set property name of [object Object] which has only a getter”的错误。 - Bergi
显示剩余5条评论
2个回答

71

立即实例化匿名类——这是一个坏主意吗?

是的,非常糟糕。就像在ES5中使用new function() { ... }一样糟糕。

这种写法会导致每次评估表达式时都创建一个新的构造函数和原型对象。如果您使用此方法创建多个对象,则它们将不会获得类/原型的任何好处。

如果您打算使用此模式创建单例对象,则也失败了。构造函数仍然被创建,并且甚至可以访问-使用new entity.constructor可以轻松创建第二个实例,从而破坏整个目的。

因此,永远不要使用它。一个简单的对象字面量更容易编写、阅读和实例化:

var entity = {
    name: 'Foo',
    getName() { return this.name; }
};
console.log(entity.name); // Foo

不要被其他编程语言中常见的“新类”模式所迷惑,它在JavaScript中的工作方式与其他语言有很大区别。

5
读者应该注意引用:“这种写法会在每次表达式被评估时创建一个新的构造函数和原型对象。如果您使用这种方法创建多个对象,它们将无法获得类/原型的任何好处。”——除非我误解了,这是针对为每个对象创建一个匿名类的合法反对意见,但不一定反对使用匿名类来创建多个对象,在这种情况下,它们仍将受益于原型继承。(可能存在其他反对意见,也可能不存在。) - ninjagecko
2
@ninjagecko 是的,通常不建议使用class来表示“单例模式”。即使不是匿名类也是如此。 - Bergi
7
如果您将此用于单例模式,那么并不算是“失败”。您可以通过简单地克隆对象来创建 任何 对象的第二个实例。JavaScript 是一种动态语言,具有内省、eval 和可在运行时修改的标准库。如果您想要保护代码不受恶意或愚蠢的同事的影响,那么您选择了错误的编程语言! - alextgordon
4
你没有提供任何反对使用匿名类的明确论点。类/原型的好处是什么?当使用匿名类时,它们到底缺少了什么?至于“每次评估时都创建一个类”,这完全取决于程序在运行过程中执行此操作的频率(多少次)。在某些情况下,这种缺陷可能微不足道。但通常情况下,我建议坚持使用普通类,除非你有充分的理由不这样做。 - x-yuri
@x-yuri 类/原型的好处在于多个实例可以共享相同的原型对象及其方法。如果为每个实例创建一个新类,则不会发生这种情况,使得使用这些方法的代码难以优化引擎(除了显然不具备内存效率)。 - Bergi
显示剩余16条评论

12

如果您清楚自己在做什么,正在创建类层次结构(即想要复制的东西)的元编程系统,并且没有其他优雅的扩展解决方案,则可能需要使用匿名类。

{
    myImplementation: class extends MyBaseClass {
        someMethod(x) {
            super().someMethod(x);
            insert extended behavior
        }
    }
}

当然,您可以使用一些滥用魔法函数来实现上述功能,这些函数使用Object.assign

{
    myImplementation: extendMagic(mySuper => ({
        someMethod(x) {
            mySuper.someMethod(x);
            insert extended behavior
        }
    }))
}

或者找到更好的方法来完成您正在做的事情。但是人为制造的用例偶尔(虽然很少)存在。

这些用例从未很好,但当您需要使用难以使用原型继承编写的类功能时,它们最具有说服力(当然,由于类是原型继承的包装器,因此您的用例归结为想要语法糖加上ES6+类功能...您是否需要构造函数、super、extends、staticmethod等?您是否被困在已经存在的类中,还是可以使用对象和断言编写所有内容?并且它真的需要是匿名的吗?这些问题可能会帮助您做出决定。)


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