如何检查变量是否是ES6类声明?

Xåp*_* - 25 javascript prototype class ecmascript-6

我从一个模块导出以下ES6类:

export class Thingy {
  hello() {
    console.log("A");
  }

  world() {
    console.log("B");
  }
}
Run Code Online (Sandbox Code Playgroud)

并从另一个模块导入它:

import {Thingy} from "thingy";

if (isClass(Thingy)) {
  // Do something...
}
Run Code Online (Sandbox Code Playgroud)

如何检查变量是否为类?不是类实例,而是类声明

换句话说,我将如何isClass在上面的例子中实现该功能?

Fel*_*ing 28

如果要确保该值不仅是一个函数,而且实际上是一个类的构造函数,您可以将该函数转换为字符串并检查其表示形式.规范规定了类构造函数的字符串表示形式.

function isClass(v) {
  return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
}
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是尝试将值调用为正常函数.类构造函数不能作为普通函数调用,但错误消息可能因浏览器而异:

function isClass(v) {
  if (typeof v !== 'function') {
    return false;
  }
  try {
    v();
    return false;
  } catch(error) {
    if (/^Class constructor/.test(error.message)) {
      return true;
    }
    return false;
  }
}
Run Code Online (Sandbox Code Playgroud)

缺点是调用函数可能会产生各种未知的副作用......

  • 声明的第二部分将在某些实现上失败(因为规范在该部分上不是非常具体).例如,在Babel版本中编译了ES6,但是在这种情况下你仍然可以检查`class`是否存在并且你可以使用`|| /_class\S+/i.test(v.toString())`到该语句. (2认同)
  • 不要调用该函数来测试它!如果它有副作用怎么办?如果函数有问题是`fireNuclearWeapons`怎么办? (2认同)
  • 仅供参考,对于像“var foo=class{};”这样的缩小类,“class\s+”可能会失败。 (2认同)

log*_*yth 15

我会在前面说清楚,任何任意函数都可以是构造函数.如果您区分"类"和"功能",那么您的API设计选择很差.如果你假设某些东西必须是class例如,那么使用Babel或Typescript的人将被检测为a,class因为他们的代码将被转换为函数.这意味着您要求使用您的代码库的任何人通常必须在ES6环境中运行,因此您的代码在旧环境中将无法使用.

您在此处的选项仅限于实现定义的行为.在ES6中,一旦解析了代码并处理了语法,就没有太多特定于类的行为.你所拥有的只是一个构造函数.你最好的选择就是做

if (typeof Thingy === 'function'){
  // It's a function, so it definitely can't be an instance.
} else {
  // It could be anything other than a constructor
}
Run Code Online (Sandbox Code Playgroud)

如果有人需要执行非构造函数,请为其公开单独的API.

显然,这不是您正在寻找的答案,但重要的是要明确这一点.

正如这里提到的另一个答案,你有一个选项,因为.toString()在函数上需要返回一个类声明,例如

class Foo {}
Foo.toString() === "class Foo {}" // true
Run Code Online (Sandbox Code Playgroud)

然而,关键是,只有在可行的情况下才适用.它是100%规范兼容的实现

class Foo{}
Foo.toString() === "throw SyntaxError();"
Run Code Online (Sandbox Code Playgroud)

目前没有任何浏览器可以做到这一点,但是有几个嵌入式系统专注于JS编程,为了保存程序本身的内存,它们一旦被解析就会丢弃源代码,这意味着它们没有源代码可以从.toString()这是允许的.

同样,通过使用,.toString()您可以对未来验证和一般API设计进行假设.说你做

const isClass = fn => /^\s*class/.test(fn.toString());
Run Code Online (Sandbox Code Playgroud)

因为这依赖于字符串表示,它很容易破坏.

以装饰者为例:

@decorator class Foo {}
Foo.toString() == ???
Run Code Online (Sandbox Code Playgroud)

请问.toString()这包括装饰?如果装饰器本身返回function而不是类,该怎么办?

  • 那里*是*一个班级.常规ES5构造函数和ES 6类构造函数不一样.试试`Thingy()`来看看差异.ES6类构造函数将抛出错误,因为没有new可能不会调用它们.这个答案是有帮助的,但它没有回答原始问题,并且不应该被接受. (7认同)
  • 让我这样说...我来到这个页面不是为了了解`typeof x == 'function'`...恕我直言,答案非常令人不满意。 (4认同)

Ian*_*ter 13

检查prototype及其可写性应该允许确定函数的类型,而无需字符串化、调用或实例化输入。

/**
 * determine if a variable is a class definition or function (and what kind)
 * @revised
 */
function isFunction(x) {
    return typeof x === 'function'
        ? x.prototype
            ? Object.getOwnPropertyDescriptor(x, 'prototype').writable
                ? 'function'
                : 'class'
        : x.constructor.name === 'AsyncFunction'
        ? 'async'
        : 'arrow'
    : '';
}

console.log({
  string: isFunction('foo'), // => ''
  null: isFunction(null), // => ''
  class: isFunction(class C {}), // => 'class'
  function: isFunction(function f() {}), // => 'function'
  arrow: isFunction(() => {}), // => 'arrow'
  async: isFunction(async function () {}) // => 'async'
});
Run Code Online (Sandbox Code Playgroud)


And*_*chi 6

这是一个古老的问题,除了这个之外几乎没有答案是正确的,有一个警告......

为什么答案错误?

让我们从任何建议调用函数的人开始......这是一种容易发生灾难的方法,应该在 ChatGPT 甚至将其视为建议代码之前从建议中删除......接下来......

在某些 JS 运行时,函数的字符串表示形式或其余代码在生产中不存在,可能在调试模式下,但不一定在产品中存在,恰恰相反。

这是因为某些 JS 运行时可以通过从几乎所有内容中删除源代码来节省大量最终“字节码”大小。

因此,建议任何字符串检查的每个人都不知道或考虑这些情况,而且任何函数toString方法也可以用其他方法替换,这使得大多数答案都不是万无一失的。

为什么没有正确答案?

最接近的答案是检查任何函数的描述符writableprototype

  • 速记方法,例如根本{method(){}}没有原型
  • 将类转译为 ES5 函数可能会使其可写,除非转译器非常关注这个细节,例如 Babel,这也会导致运行时速度稍慢
  • 只有未转译的原生 ES2015+ 代码才能通过所有测试:prototype存在且其writable值恰好是false
const isESClass = fn => (
  typeof fn === 'function' &&
  Object.getOwnPropertyDescriptor(
    fn,
    'prototype'
  )?.writable === false
);
Run Code Online (Sandbox Code Playgroud)

重要的是要明白,在陷入 ES5 转译(无论出于何种原因)的项目中,这将失败,但从根本上没有办法保证通用函数,在 ES2015 之前的世界中,是一个类,还是不是一个类,使用了jQuery(以及其他)类似以下的模式和所有情况都是允许的:

function jQuery(...args) {
  if (!(this instanceof jQuery))
    return new jQuery(...args);
  // do everything jQuery does
}
Run Code Online (Sandbox Code Playgroud)

与 ES2015+ 类不同,该实用程序既可以作为常规函数也可以作为new function,因此基本上这个问题没有正确的答案,只是需要考虑的妥协和目标列表。


为了规格读者的缘故:


loc*_*wei 5

函数之间存在细微的区别,我们可以利用这个优势来区分它们,以下是我的实现:

\n
// is "class" or "function"?\nfunction isClass(obj) {\n\n    // if not a function, return false.\n    if (typeof obj !== \'function\') return false;\n\n    // \xe2\xad\x90 is a function, has a `prototype`, and can\'t be deleted!\n\n    // \xe2\xad\x90 although a function\'s prototype is writable (can be reassigned),\n    //   it\'s not configurable (can\'t update property flags), so it\n    //   will remain writable.\n    //\n    // \xe2\xad\x90 a class\'s prototype is non-writable.\n    //\n    // Table: property flags of function/class prototype\n    // ---------------------------------\n    //   prototype  write  enum  config\n    // ---------------------------------\n    //   function     v      .      .\n    //   class        .      .      .\n    // ---------------------------------\n    const descriptor = Object.getOwnPropertyDescriptor(obj, \'prototype\');\n\n    // \xe2\x9d\x97functions like `Promise.resolve` do have NO `prototype`.\n    //   (I have no idea why this is happening, sorry.)\n    if (!descriptor) return false;\n\n    return !descriptor.writable;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

以下是一些测试用例:

\n
class A { }\nfunction F(name) { this.name = name; }\n\nisClass(F),                 // \xe2\x9d\x8c false\nisClass(3),                 // \xe2\x9d\x8c false\nisClass(Promise.resolve),   // \xe2\x9d\x8c false\n\nisClass(A),                 // \xe2\x9c\x85 true\nisClass(Object),            // \xe2\x9c\x85 true\n
Run Code Online (Sandbox Code Playgroud)\n