繁体   English   中英

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

[英]How to check if a variable is an ES6 class declaration?

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

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

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

并从另一个模块导入它:

import {Thingy} from "thingy";

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

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

换句话说,我将如何实现上面示例中的isClass函数?

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

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

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

我会在前面说清楚,任何任意函数都可以是构造函数。 如果您要区分“类”和“函数”,那么您的 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而不是一个类怎么办?

检查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' });

关于什么:

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

此解决方案使用Felix 的答案修复了两个误报:

  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` :(

我很震惊lodash没有答案。 看看这个——就像 Domi 一样,我刚刚想出了一个解决故障的解决方案。 我知道它有很多代码,但它是我现在可以产生的最有效但最容易理解的东西。 也许有人可以通过正则表达式方法对其进行优化:

function isClass(asset) {

    const string_match = "function";

    const is_fn = !!(typeof asset === string_match);

    if(!is_fn){

        return false;

    }else{

        const has_constructor = is_fn && !!(asset.prototype && asset.prototype.constructor && asset.prototype.constructor === asset);

        const code = !asset.toString ? "" : asset.toString();

        if(has_constructor && !code.startsWith(string_match)){
            return true;
        }

        if(has_constructor && code.startsWith(string_match+"(")){
            return false;
        }

        const [keyword, name] = code.split(" ");

        if(name && name[0] && name[0].toLowerCase && name[0].toLowerCase() != name[0]){
            return true;
        }else{
            return false;
        }

    }

}

只需测试它:

console.log({
    _s:isClass(String),
    _a:isClass(Array),
    _o:isClass(Object),
    _c:isClass(class{}),
    fn:isClass(function(){}),
    fnn:isClass(function namedFunction(){}),
    fnc:isClass(()=>{}),
    n:isClass(null),
    o:isClass({}),
    a:isClass([]),
    s:isClass(""),
    n:isClass(2),
    u:isClass(undefined),
    b:isClass(false),
    pr:isClass(Promise),
    px:isClass(Proxy)
});

只要确保所有类都有第一个大写字母。

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

// is "class" or "function"?
function isClass(obj) {

    // if not a function, return false.
    if (typeof obj !== 'function') return false;

    // ⭐ is a function, has a `prototype`, and can't be deleted!

    // ⭐ although a function's prototype is writable (can be reassigned),
    //   it's not configurable (can't update property flags), so it
    //   will remain writable.
    //
    // ⭐ a class's prototype is non-writable.
    //
    // Table: property flags of function/class prototype
    // ---------------------------------
    //   prototype  write  enum  config
    // ---------------------------------
    //   function     v      .      .
    //   class        .      .      .
    // ---------------------------------
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'prototype');

    // ❗functions like `Promise.resolve` do have NO `prototype`.
    //   (I have no idea why this is happening, sorry.)
    if (!descriptor) return false;

    return !descriptor.writable;
}

以下是一些测试用例:

class A { }
function F(name) { this.name = name; }

isClass(F),                 // ❌ false
isClass(3),                 // ❌ false
isClass(Promise.resolve),   // ❌ false

isClass(A),                 // ✅ true
isClass(Object),            // ✅ true

很好地完成了一些答案,并认为@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、undefined 以及对对象的任何非属性操作。

到目前为止,我发现的是,从上面的讨论中,类在实例之前并不是蓝图本身的对象。 因此运行 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');

也许这可以帮助

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM