【问题标题】:How to check if a variable is an ES6 class declaration?如何检查变量是否是 ES6 类声明?
【发布时间】:2015-08-25 20:38:29
【问题描述】:

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

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

  world() {
    console.log("B");
  }
}

并从另一个模块导入它:

import {Thingy} from "thingy";

if (isClass(Thingy)) {
  // Do something...
}

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

也就是说,我将如何实现上面示例中的isClass 函数?

【问题讨论】:

标签: javascript class prototype ecmascript-6


【解决方案1】:

如果您想确保该值不仅是一个函数,而且是一个类的构造函数,您可以将函数转换为字符串并检查其表示形式。 The spec dictates the string representation of a class constructor.

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

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

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;
  }
}

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

【讨论】:

  • 语句的第二部分在某些实现上会失败(因为该部分的规范不是很具体)。例如。在 Babel 构建中转译了 ES6,但是在这种情况下,您仍然可以检查 class 是否存在,并且您可以在声明中添加 || /_class\S+/i.test(v.toString())
  • 有趣的是,似乎在 Node.js REPL 中,您可以区分 classes 和 function ƒ () {} 因为 ƒ 将有一个 arguments 属性,而 class 没有不。 () => {} 不会有prototype,不像classfunction。但是,当使用 Node 运行脚本文件时,这似乎并不成立……
  • 不要调用函数测试!如果有副作用怎么办?如果函数问题是fireNuclearWeapons怎么办?
  • 仅供参考 class\s+ 对于像 var foo=class{}; 这样的缩小类可能会失败。
  • @wprl 节点 REPL 未处于严格模式。在严格模式下,没有区别。
【解决方案2】:

这里我先说清楚,任意函数都可以是构造函数。如果您要区分“类”和“函数”,那么您的 API 设计选择就很糟糕。例如,如果您假设某些东西必须是 class,那么使用 Babel 或 Typescript 的任何人都不会被检测为 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
}

如果有人需要执行非构造函数,请为此公开一个单独的 API。

显然,这不是您要寻找的答案,但明确这一点很重要。

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

class Foo {}
Foo.toString() === "class Foo {}" // true

然而,关键是它只适用于如果可以的话。实现是 100% 符合规范的

class Foo{}
Foo.toString() === "throw SyntaxError();"

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

同样,通过使用.toString(),您对面向未来和通用 API 设计做出了假设。说你做

const isClass = fn => /^\s*class/.test(fn.toString());

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

以装饰器为例:

@decorator class Foo {}
Foo.toString() == ???

这里的.toString() 是否包括装饰器?如果装饰器本身返回 function 而不是类怎么办?

【讨论】:

  • 有一个类。常规的 ES5 构造函数和 ES 6 类的构造函数是不一样的。试试Thingy() 看看有什么不同。 ES6 类的构造函数会抛出错误,因为如果没有 new 就无法调用它们。这个答案很有帮助,但它没有回答原始问题,恕我直言不应该被接受。
  • 我认为这是一个不准确的区别。某些函数不能with new(如箭头函数)使用,某些函数不能 new(如类)使用。这不会使它们或多或少成为一种功能。使用 ES6 class 语法是创建没有 new 就无法创建的函数的一种方法,但还有其他方法。试图区分函数的子类型几乎总是一个坏主意,因为你永远不会 100% 正确。
  • 这样说吧……我不是来这个页面了解typeof x == 'function'的……答案很不满意恕我直言。
  • 我要注意的是,正则表达式在 \s 之前测试为 false class
  • @KnightYoshi 不错,已修复。
【解决方案3】:

检查prototypearguments 应该允许在不对输入进行字符串化、调用或实例化的情况下确定函数的类型。

    function isFunction(x) {
        return typeof x !== 'function'
           ? ''
           : x.hasOwnProperty('arguments')
              ? 'function'
              : x.prototype
                 ? 'class'
                 : x.constructor.name === 'AsyncFunction'
                    ? 'async'
                    : 'arrow';
    }

...确实如此(在节点和客户端中)

console.assert([
   isFunction(null),
   isFunction(class C{}),
   isFunction(function f(){}),
   isFunction(()=>{})
   isFunction(async function(){})
].join(',') === ',class,function,arrow,async');

【讨论】:

  • 这在我的测试中返回 false:( function funk (x){ }) 。 hasOwnProperty ('参数'); .所以它并没有告诉我“funk”是一个普通的函数而不是一个类。这发生在 Node.js
【解决方案4】:

怎么样:

function isClass(v) {
   return typeof v === 'function' && v.prototype.constructor === v;
}

【讨论】:

  • 这个答案是错误的! (function(){}).constructor - 这会返回一个函数
  • (function(){}).constructor === window.Functiontrue,这正是您所期望的:函数是 Function 的一个实例。请注意,我比较的是值的 prototype 构造函数,而不是值的构造函数。
  • 我的意思是你提出的检查并没有告诉一个普通函数的类。这就是我在找到这个答案之前搜索的内容。然而现在我意识到最初的问题只是从一个对象中告诉一个类,这要容易得多。 runkit.com/disjunction/581f5cff1cdd06001402ea5f
  • 好吧,你可以在你的例子中做new hello()。 Javascript 真是一门糟糕的语言。
  • v.prototype.constructor === v 对于每个非箭头函数都是正确的。所有函数都有原型,所有函数原型都有构造函数,默认为函数本身。
【解决方案5】:

此解决方案使用Felix's answer 修复了两个误报:

  1. 它适用于在类主体之前没有空格的匿名类:
    • isClass(class{}) // true
  2. 它适用于本机类:
    • isClass(Promise) // true
    • isClass(Proxy) // true
function isClass(value) {
  return typeof value === 'function' && (
    /^\s*class[^\w]+/.test(value.toString()) ||

    // 1. native classes don't have `class` in their name
    // 2. However, they are globals and start with a capital letter.
    (globalThis[value.name] === value && /^[A-Z]/.test(value.name))
  );
}

const A = class{};
class B {}
function f() {}

console.log(isClass(A));                // true
console.log(isClass(B));                // true
console.log(isClass(Promise));          // true

console.log(isClass(Promise.resolve));  // false
console.log(isClass(f));                // false

缺点

遗憾的是,它仍然不适用于 node 内置(可能还有许多其他特定于平台的)类,例如:

const EventEmitter = require('events');
console.log(isClass(EventEmitter));  // `false`, but should be `true` :(

【讨论】:

    【解决方案6】:

    也许这会有所帮助

    let is_class = (obj) => {
        try {
            new obj();
            return true;
        } catch(e) {
            return false;
        };
    };
    

    【讨论】:

    • 添加一个typeof obj == 'function' 并删除new,你会得到一些接近的东西......但我建议反对调用该函数来尝试确定是否它是一个 ES6 类,因为 1)它可能有副作用(例如,我想知道 triggerNuclearBomb 是否是一个 ES6 类......让我们称之为找出答案!)并且 2 个编译器可能会优化生产构建的错误检查,这意味着它不适用于转译的代码。
    • 一个完全有效的构造函数可能会在缺少参数时抛出错误,这也会返回 false。
    • 将寻求您对上述答案的评论......我已经揭示了这里正在讨论的内容
    【解决方案7】:

    很好地浏览了一些答案,并认为@Joe Hildebrand 突出了边缘情况,因此更新了以下解决方案以反映大多数尝试过的边缘情况。对可能存在边缘情况的更多识别开放。

    关键见解:虽然我们正在进入类,但就像 JS 中的指针和引用辩论一样,并不能证实其他语言的所有品质 - JS 本身没有我们在其他语言结构中拥有的类。

    有些争论它是函数的糖衣语法,有些争论是另一种明智的。我相信课程仍然是一个功能,但与其说是糖衣,不如说是可以放在类固醇上的东西。类会做一些功能不能做或不想升级它们做的事情。

    因此暂时将类作为函数处理打开了另一个潘多拉盒子。 JS 中的一切都是对象,JS 不理解但愿意跟随开发者的一切都是对象,例如

    • 布尔值可以是对象(如果使用 new 关键字定义)
    • 数字可以是对象(如果使用 new 关键字定义)
    • 字符串可以是对象(如果使用 new 关键字定义)
    • 日期总是对象
    • 数学总是对象
    • 正则表达式始终是对象
    • 数组总是对象
    • 函数始终是对象
    • 对象永远是对象

    那么类到底是什么? 重要 类是创建对象的模板,在这一点上它们不是对象。 当您在某处创建类的实例时,它们成为对象,该实例被视为对象。 因此,我们需要进行筛选

    • 我们正在处理哪种类型的对象
    • 然后我们需要筛选出它的属性。
    • 函数始终是对象,它们始终具有原型和参数属性。
    • 箭头函数实际上是老式函数的糖衣,没有这个或更多简单返回上下文的概念,因此即使您尝试定义它们也没有原型或参数。
    • 类是一种可能的函数的蓝图,没有参数属性但有原型。这些原型在实例之后成为事实对象。

    因此,我尝试捕获并记录我们检查的每次迭代以及结果。

    希望对你有帮助

    'use strict';
    var isclass,AA,AAA,BB,BBB,BBBB,DD,DDD,E,F;
    isclass=function(a) {
    if(/null|undefined/.test(a)) return false;
        let types = typeof a;
        let props = Object.getOwnPropertyNames(a);
        console.log(`type: ${types} props: ${props}`);
    
    
        return  ((!props.includes('arguments') && props.includes('prototype')));}
        
        class A{};
        class B{constructor(brand) {
        this.carname = brand;}};
        function C(){};
         function D(a){
         this.a = a;};
     AA = A;
     AAA = new A;
     BB = B;
     BBB = new B;
     BBBB = new B('cheking');
     DD = D;
     DDD = new D('cheking');
     E= (a) => a;
     
    F=class {};
     
    console.log('and A is class: '+isclass(A)+'\n'+'-------');
    console.log('and AA as ref to A is class: '+isclass(AA)+'\n'+'-------');
    console.log('and AAA instance of is class: '+isclass(AAA)+'\n'+'-------');
    console.log('and B with implicit constructor is class: '+isclass(B)+'\n'+'-------');
    console.log('and BB as ref to B is class: '+isclass(BB)+'\n'+'-------');
    console.log('and BBB as instance of B is class: '+isclass(BBB)+'\n'+'-------');
    console.log('and BBBB as instance of B is class: '+isclass(BBBB)+'\n'+'-------');
    console.log('and C as function is class: '+isclass(C)+'\n'+'-------');
    console.log('and D as function method is class: '+isclass(D)+'\n'+'-------');
    console.log('and DD as ref to D is class: '+isclass(DD)+'\n'+'-------');
    console.log('and DDD as instance of D is class: '+isclass(DDD)+'\n'+'-------');
    console.log('and E as arrow function is class: '+isclass(E)+'\n'+'-------');
    console.log('and F as variable class is class: '+isclass(F)+'\n'+'-------');
    console.log('and isclass as variable  function is class: '+isclass(isclass)+'\n'+'-------');
    console.log('and 4 as number is class: '+isclass(4)+'\n'+'-------');
    console.log('and 4 as string is class: '+isclass('4')+'\n'+'-------');
    console.log('and DOMI\'s string is class: '+isclass('class Im a class. Do you believe me?')+'\n'+'-------');

    更短的清理函数,涵盖严格模式、es6 模块、null、未定义以及对象上的任何非属性操作。

    到目前为止,我发现的是,从上面的讨论中,类在实例出现之前并不是它们自己的蓝图。因此运行 toString 函数几乎总是会在实例之后产生类 {} 输出而不是 [object object] 等等。一旦我们知道什么是一致的,那么只需运行正则表达式测试以查看结果是否以单词类开头。

    "use strict"
    let isclass = a =>{
    return (!!a && /^class.*{}/.test(a.toString()))
    }
    class A {}
    class HOO {}
    let B=A;
    let C=new A;
    Object.defineProperty(HOO, 'arguments', {
      value: 42,
      writable: false
    });
    
    
    console.log(isclass(A));
    console.log(isclass(B));
    console.log(isclass(C));
    console.log(isclass(HOO));
    console.log(isclass());
    console.log(isclass(null));
    console.log(HOO.toString());
    //proxiy discussion
    console.log(Proxy.toString());
    //HOO was class and returned true but if we proxify it has been converted to an object
    HOO = new Proxy(HOO, {});
    console.log(isclass(HOO));
    console.log(HOO.toString());
    console.log(isclass('class Im a class. Do you believe me?'));

    来自 DOMI 的讨论

    class A {
    static hello (){console.log('hello')}
    hello () {console.log('hello there')}
    }
    
    A.hello();
    B = new A;
    B.hello();
    
    console.log('it gets even more funnier it is properties and prototype mashing');  
    
    class C {
      constructor() {
        this.hello = C.hello;
      }
    static hello (){console.log('hello')}
    }
    C.say = ()=>{console.log('I said something')} 
    C.prototype.shout = ()=>{console.log('I am shouting')} 
    
    C.hello();
    D = new C;
    D.hello();
    D.say();//would throw error as it is not prototype and is not passed with instance
    C.say();//would not throw error since its property not prototype
    C.shout();//would throw error as it is prototype and is passed with instance but is completly aloof from property of static 
    D.shout();//would not throw error
    console.log('its a whole new ball game ctaching these but gassumption is class will always have protoype to be termed as class');

    【讨论】:

    • 讽刺的是,在你的实现中 isclass(isclass) 返回 true。
    • @Joe Hildebrand 很好的识别......坐下来让它更清晰,更严格地测试 isclass。
    • @Joe Hildebrand 查看新功能涵盖了几乎所有的新测试用例,与之前的测试用例相比显得微不足道。我走了很长一段路才找到简短的解决方案。感谢更新。我保留了旧的更新,以便未来的读者也可以一起讨论。
    • 您提供了两种不同的实现方式,但都无法正常工作。第一个在strict 模式下失败,第二个是另一个答案的错误版本;试试:isClass('class I'm a class. Do you believe me?')
    • @Syed 你说得对,我的一些 cmets 不够具体。以下是一些演示假阴性和假阳性的 sn-ps。您可能想了解更多关于类型的基础知识以及函数的工作原理。整个arguments + properties 检查完全关闭,因为您的目标是错误的。如果它是正确的,它仍然不能在严格模式下工作。对于您的第一个解决方案:console.log(isclass(class Thisisanullclass { }), isclass({ prototype: 'hi' }))。 --- 对于您的第二个解决方案:console.log(isclass('class {}'), isclass('class' + (() => {})))
    猜你喜欢
    • 2015-09-11
    • 2018-05-01
    • 1970-01-01
    • 2015-06-09
    • 1970-01-01
    • 2012-01-01
    • 2014-03-14
    • 1970-01-01
    • 2022-10-28
    相关资源
    最近更新 更多