【问题标题】:Get the class name of ES6 class instance获取 ES6 类实例的类名
【发布时间】:2015-06-01 08:41:51
【问题描述】:

是否有任何“和谐”的方式从 ES6 类实例中获取类名?除了

someClassInstance.constructor.name

目前我指望 Traceur 实施。而且似乎 Babel 有一个针对 Function.name 的 polyfill,而 Traceur 没有。

总结一下:在 ES6/ES2015/Harmony 中没有其他方法,在 ES.Next 中没有任何预期的 ATM。

它可能为未缩小的服务器端应用程序提供有用的模式,但在用于浏览器/桌面/移动设备的应用程序中是不需要的。

Babel uses core-js 到 polyfill Function.name,应该为 Traceur 和 TypeScript 应用程序手动加载它。

【问题讨论】:

  • 我遇到了同样的问题;对于 Traceur 来说,唯一的解决方案是解析实际的类代码本身以提取名称,我认为这不是和谐的。我只是吞下了药丸并切换到 Babel; Traceur 的发展似乎有些停滞,很多 ES6 特性实现得很差。正如你所提到的,instance.constructor.nameclass.name 在正确的 ES6 中返回类名。
  • 似乎是唯一的办法。
  • 这是在 ES6 标准中吗?
  • 值得一提的是,如果你丑化你的代码,someClassInstance.constructor.name 将会被破坏。
  • stackoverflow.com/questions/2648293/… 可能想看看这个,应该和.constructor一起工作。

标签: javascript ecmascript-6 traceur


【解决方案1】:

someClassInstance.constructor.name 正是这样做的正确方法。转译器可能不支持这一点,但它是规范中的标准方式。 (通过 ClassDeclaration 产生式声明的函数的 name 属性在 14.5.15 中设置,步骤 6。)

【讨论】:

  • 这就是我害怕的。你知道任何合理的polyfill吗?我试图弄清楚 Babel 是如何做到的,但收效甚微。
  • 我真的不知道你所说的语言特性(类)的 polyfill 是什么意思。
  • 我的意思是constructor.name的polyfill。似乎 Babel 已经实现了,但我没有成功理解它是如何做到的。
  • @estus someClassInstance.constructor 是一个函数。所有函数都有一个设置为其名称的name 属性。这就是为什么 babel 不需要做任何事情。请看developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • @Esteban Babel 似乎一直在推广 core-js 的 polyfill(包括 Function.name 的 polyfill),这就是为什么某些 Babel 构建可以在所有浏览器中开箱即用的原因。
【解决方案2】:

正如@Domenic 所说,使用someClassInstance.constructor.name。 @Esteban 在 cmets 中提到

someClassInstance.constructor 是一个函数。所有函数都有一个name 属性...

因此,要静态访问类名,请执行以下操作(这适用于我的 Babel 版本顺便说一句。根据@Domenic 上的 cmets,您的里程可能会有所不同)。

class SomeClass {
  constructor() {}
}

var someClassInstance = new SomeClass();
someClassInstance.constructor.name;      // === 'SomeClass'
SomeClass.name                           // === 'SomeClass'

更新

Babel 很好,但 uglify/minification 确实给我带来了问题。我正在制作游戏,并且正在创建池化 Sprite 资源的哈希(其中键是函数名称)。缩小后,每个函数/类都被命名为t。这会杀死哈希。我在这个项目中使用Gulp,在阅读了gulp-uglify docs 之后,我发现有一个参数可以防止发生这种局部变量/函数名重整。所以,在我的 gulpfile 中我改变了

.pipe($.uglify()).pipe($.uglify({ mangle: false }))

这里需要权衡性能与可读性。不修改名称将导致(稍微)更大的构建文件(更多的网络资源)和可能更慢的代码执行(需要引用 - 可能是 BS)。另一方面,如果我保持不变,我将不得不在每个 ES6 类上手动定义 getClassName - 在静态和实例级别。不用了!

更新

在 cmets 中进行讨论之后,似乎避免使用 .name 约定以支持定义这些函数是一个很好的范例。它只需要几行代码,并且允许您的代码完全缩小和通用(如果在库中使用)。所以我想我改变了主意,将在我的课程中手动定义getClassName。谢谢@estus!。无论如何,与直接变量访问相比,Getter/Setter 通常是一个好主意,尤其是在基于客户端的应用程序中。

class SomeClass {
  constructor() {}
  static getClassName(){ return 'SomeClass'; }
  getClassName(){ return SomeClass.getClassName(); }
}
var someClassInstance = new SomeClass();
someClassInstance.constructor.getClassName();      // === 'SomeClass' (static fn)
someClassInstance.getClassName();                  // === 'SomeClass' (instance fn)
SomeClass.getClassName()                           // === 'SomeClass' (static fn)

【讨论】:

  • 完全禁用重整不是一个好主意,因为重整对缩小有很大贡献。首先在客户端代码中使用主题也不是一个好主意,但是可以使用reserved Uglify 选项来保护类免于修改(可以使用正则表达式或其他方式获得类列表)。
  • 非常正确。有一个更大的文件大小是一个权衡。 Looks like this is how you can use RegEx to prevent mangling for only select items。您是什么意思“在客户端代码中使用主题也不是一个好主意”?在某些情况下会带来安全风险吗?
  • 不,就是刚才所说的。客户端 JS 被缩小是正常的,所以已经知道这种模式会给应用程序带来问题。类字符串标识符的额外一行代码而不是整洁的name 模式可能只是双赢。同样的事情可能适用于 Node,但程度较小(例如混淆的 Electron 应用程序)。根据经验,我会在服务器代码中依赖name,而不是在浏览器代码和公共库中。
  • 好的,所以您建议手动定义 2 个 getClassName 函数(静态和实例)来绕过混乱的地狱并允许完全缩小(没有烦人的 RegEx)。关于图书馆的这一点很有意义。很有意义。对我来说,我的项目是一个自制的小型 Cordova 应用程序,所以这些并不是真正的问题。除此之外,我可以看到这些问题正在上升。感谢您的讨论!如果您对帖子有任何改进意见,请随时进行编辑。
  • 是的,我最初使用name 作为 DRYer 代码从类名中提取使用类(服务、插件等)的实体的名称,但最后我找到了它使用静态道具(id_name)显式复制它只是最可靠的方法。一个不错的选择是不关心类名,使用类本身(函数对象)作为映射到此类的实体的标识符,并在需要的地方使用import(Angular 2 DI 使用的一种方法)。
【解决方案3】:

直接从类中获取类名

以前的答案解释说someClassInstance.constructor.name 工作得很好,但是如果您需要以编程方式将类名转换为字符串并且不想为此创建实例,请记住:

typeof YourClass === "function"

而且,由于每个函数都有一个name 属性,另一种获取带有类名的字符串的好方法是:

YourClass.name

下面是一个很好的例子,说明了它为什么有用。

加载网络组件

正如MDN documentation 告诉我们的,这就是您加载 Web 组件的方式:

customElements.define("your-component", YourComponent);

其中YourComponent 是从HTMLElement 扩展的类。由于在组件标记本身之后命名组件的类被认为是一种很好的做法,因此编写一个所有组件都可以用来注册自己的辅助函数会很好。所以这里是那个函数:

function registerComponent(componentClass) {
    const componentName = upperCamelCaseToSnakeCase(componentClass.name);
    customElements.define(componentName, componentClass);
}

所以你需要做的就是:

registerComponent(YourComponent);

这很好,因为它比自己编写组件标签更不容易出错。总结一下,这是upperCamelCaseToSnakeCase() 函数:

// converts `YourString` into `your-string`
function upperCamelCaseToSnakeCase(value) {
    return value
        // first char to lower case
        .replace(/^([A-Z])/, $1 => $1.toLowerCase())
        // following upper chars get preceded with a dash
        .replace(/([A-Z])/g, $1 => "-" + $1.toLowerCase());
}

【讨论】:

  • 谢谢。该示例是客户端。正如已经提到的,在浏览器中使用函数名存在一些问题。几乎每一个浏览器的代码都会被压缩,但这会破坏依赖函数名的代码。
  • 是的,你完全正确。必须将缩小器配置为不接触类名才能使这种方法起作用。
【解决方案4】:

用于 babel 转译(缩小前)

如果您将 Babel 与 @babel/preset-env 一起使用,则可以保留类定义而不将它们转换为函数(这会删除 constructor 属性)

您可以在babel.config / babelrc 中删除一些与此配置的旧浏览器兼容性:

{
  "presets": [
    ["@babel/preset-env", {"targets": {"browsers": ["> 2%"]}}]
  ]
}

更多关于targets的信息:https://babeljs.io/docs/en/babel-preset-env#targets

用于 babel 缩小(转译后)

现在看来没有简单的解决方案......我们需要看看修改排除。

【讨论】:

  • 你能进一步解释一下这应该如何帮助缩小吗? class Foo {} 将被缩小为 class a{} 之类的任何目标。缩小的源代码中不会有Foo 字样。
  • 老实说,我没有挖掘更多的文档以及它帮助我使用此配置的事实......我正在将 ECSY 用于 babel 转译项目,它需要这个参数来获取有效的类名:@987654322 @
  • 我明白了。这对于您处理的代码非常具体。例如。可以为 ES 模块和 ES6 保留名称,因为 export class Foo{} 不能被进一步有效地丑化,但这在其他地方可能会有所不同,如果不知道在构建时特定代码片段会发生什么,我们就无法知道究竟是怎么回事。无论如何,这自 2015 年以来一直没有改变。某些编译配置和代码总是可能的。而且我想说这种可能性仍然太脆弱,无法将类名用于应用程序逻辑。一次意外更改源代码后,您所依赖的类名可能会变成垃圾
  • 好的,我通过查看代码发现了正在发生的事情。我的解决方案将类转换为函数。所以它在缩小之前有帮助。但不是缩小问题。我必须继续挖掘,因为我无法理解我所有使用 constructor.name 的代码如何在缩小版本中仍然有效......不合逻辑:/
猜你喜欢
  • 2010-10-05
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
相关资源
最近更新 更多