简体   繁体   English

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

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

I am exporting the following ES6 class from one module:我正在从一个模块中导出以下 ES6 类:

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

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

And importing it from another module:并从另一个模块导入它:

import {Thingy} from "thingy";

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

How can I check whether a variable is a class?如何检查变量是否是类? Not a class instance , but a class declaration ?不是类实例,而是类声明

In other words, how would I implement the isClass function in the example above?换句话说,我将如何实现上面示例中的isClass函数?

If you want to ensure that the value is not only a function, but really a constructor function for a class, you can convert the function to a string and inspect its representation.如果您想确保该值不仅是一个函数,而且是一个类的构造函数,您可以将该函数转换为字符串并检查其表示形式。 The spec dictates the string representation of a class constructor . 规范规定了类构造函数的字符串表示

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

Another solution would be to try to call the value as a normal function.另一种解决方案是尝试将该值作为普通函数调用。 Class constructors are not callable as normal functions, but error messages probably vary between browsers:类构造函数不能作为普通函数调用,但错误消息可能因浏览器而异:

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

The disadvantage is that invoking the function can have all kinds of unknown side effects...缺点是调用函数会产生各种未知的副作用……

I'll make it clear up front here, any arbitrary function can be a constructor.我会在前面说清楚,任何任意函数都可以是构造函数。 If you are distinguishing between "class" and "function", you are making poor API design choices.如果您要区分“类”和“函数”,那么您的 API 设计选择就很糟糕。 If you assume something must be a class for instance, no-one using Babel or Typescript will be be detected as a class because their code will have been converted to a function instead.例如,如果您假设某些东西必须是class ,则不会将使用 Babel 或 Typescript 的任何人检测为class ,因为它们的代码将被转换为函数。 It means you are mandating that anyone using your codebase must be running in an ES6 environment in general, so your code will be unusable on older environments.这意味着您要求使用您的代码库的任何人通常必须在 ES6 环境中运行,因此您的代码将无法在旧环境中使用。

Your options here are limited to implementation-defined behavior.您在此处的选项仅限于实现定义的行为。 In ES6, once code is parsed and the syntax is processed, there isn't much class-specific behavior left.在 ES6 中,一旦解析了代码并处理了语法,就没有太多特定于类的行为了。 All you have is a constructor function.你所拥有的只是一个构造函数。 Your best choice is to do你最好的选择是做

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
}

and if someone needs to do a non-constructor function, expose a separate API for that.如果有人需要执行非构造函数,请为此公开一个单独的 API。

Obviously that is not the answer you are looking for, but it's important to make that clear.显然这不是您要寻找的答案,但重要的是要明确这一点。

As the other answer here mentions, you do have an option because .toString() on functions is required to return a class declaration, eg正如此处提到的另一个答案,您确实有一个选项,因为函数上的.toString()需要返回类声明,例如

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

The key thing, however, is that that only applies if it can .然而,关键是只有在可以的情况下才适用。 It is 100% spec compliant for an implementation to have实现是 100% 符合规范的

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

No browsers currently do that, but there are several embedded systems that focus on JS programming for instance, and to preserve memory for your program itself, they discard the source code once it has been parsed, meaning they will have no source code to return from .toString() and that is allowed.目前没有浏览器这样做,但是有几个嵌入式系统专注于例如 JS 编程,并且为了为程序本身保留内存,它们在解析后丢弃源代码,这意味着它们将没有源代码可以返回.toString()并且这是允许的。

Similarly, by using .toString() you are making assumptions about both future-proofing, and general API design.同样,通过使用.toString()您正在对面向未来和通用 API 设计做出假设。 Say you do说你做

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

because this relies on string representations, it could easily break.因为这依赖于字符串表示,它很容易被破坏。

Take decorators for example:以装饰器为例:

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

Does the .toString() of this include the decorator?这个.toString()是否包括装饰器? What if the decorator itself returns a function instead of a class?如果装饰器本身返回一个function而不是一个类怎么办?

Checking the prototype and its writability should allow to determine the type of function without stringifying, calling or instantiating the input.检查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' });

What about:关于什么:

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

This solution fixes two false positives with Felix's answer :此解决方案使用Felix 的答案修复了两个误报:

  1. It works with anonymous classes that don't have space before the class body:它适用于在类主体之前没有空间的匿名类:
    • isClass(class{}) // true
  2. It works with native classes:它适用于本机类:
    • 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

Shortcomings缺点

Sadly, it still won't work with node built-in (and probably many other platform-specific) classes, eg:可悲的是,它仍然不适用于node内置(可能还有许多其他特定于平台的)类,例如:

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

I'm shocked lodash didnt have the answer.我很震惊lodash没有答案。 Check this out - Just like Domi I just came up with a solution to fix the glitches.看看这个——就像 Domi 一样,我刚刚想出了一个解决故障的解决方案。 I know its a lot of code but its the most working yet understandable thing I could produce by now.我知道它有很多代码,但它是我现在可以产生的最有效但最容易理解的东西。 Maybe someone can optimize it by a regex-approach:也许有人可以通过正则表达式方法对其进行优化:

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

    }

}

Just test it with:只需测试它:

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

Just make sure all classes have a frist uppercased letter.只要确保所有类都有第一个大写字母。

There are subtle differences between a function and a class , and we can take this advantage to distinguish between them, the following is my implementation:函数之间存在细微差别,我们可以利用这个优势来区分它们,以下是我的实现:

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

Here are some test cases:以下是一些测试用例:

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

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

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

Well going through some of the answers and thinks to @Joe Hildebrand for highlighting edge case thus following solution is updated to reflect most tried edge cases.很好地完成了一些答案,并认为@Joe Hildebrand 突出了边缘情况,因此更新了以下解决方案以反映大多数尝试过的边缘情况。 Open to more identification where edge cases may rest.对可能存在边缘情况的更多识别开放。

Key insights: although we are getting into classes but just like pointers and references debate in JS does not confirm to all the qualities of other languages - JS as such does not have classes as we have in other language constructs.关键见解:虽然我们正在进入类,但就像 JS 中的指针和引用辩论一样,并不能证实其他语言的所有品质 - JS 本身没有我们在其他语言结构中拥有的类。

some debate it is sugar coat syntax of function and some argue other wise.一些人争论它是功能的糖衣语法,而另一些人则认为是其他明智的。 I believe classes are still a function underneath but not so much so as sugar coated but more so as something that could be put on steroids.我相信课程仍然是一个功能,但与其说是糖衣,不如说是可以放在类固醇上的东西。 Classes will do what functions can not do or didn't bother upgrading them to do.类会做一些功能不能做或不想升级它们做的事情。

So dealing with classes as function for the time being open up another Pandora box.因此,将类作为函数处理暂时打开了另一个潘多拉盒子。 Everything in JS is object and everything JS does not understand but is willing to go along with the developer is an object eg JS 中的一切都是对象,而 JS 不理解但愿意与开发人员一起去的一切都是对象,例如

  • Booleans can be objects (if defined with the new keyword)布尔值可以是对象(如果使用 new 关键字定义)
  • Numbers can be objects (if defined with the new keyword)数字可以是对象(如果使用 new 关键字定义)
  • Strings can be objects (if defined with the new keyword)字符串可以是对象(如果使用 new 关键字定义)
  • Dates are always objects日期总是对象
  • Maths are always objects数学永远是对象
  • Regular expressions are always objects正则表达式总是对象
  • Arrays are always objects数组总是对象
  • Functions are always objects函数总是对象
  • Objects are always objects对象永远是对象

Then what the heck are Classes?那么类到底是什么? Important Classes are a template for creating objects they are not object per say them self at this point.重要的类是创建对象的模板,它们不是对象,在这一点上说他们自己。 They become object when you create an instance of the class somewhere, that instance is considered an object.当您在某处创建类的实例时,它们成为对象,该实例被视为对象。 So with out freaking out we need to sift out因此,我们需要筛选

  • which type of object we are dealing with我们正在处理哪种类型的对象
  • Then we need to sift out its properties.然后我们需要筛选出它的属性。
  • functions are always objects they will always have prototype and arguments property.函数始终是对象,它们将始终具有原型和参数属性。
  • arrow function are actually sugar coat of old school function and have no concept of this or more the simple return context so no prototype or arguments even if you attempt at defining them.箭头函数实际上是老式函数的糖衣,并且没有这个或更多简单返回上下文的概念,因此即使您尝试定义它们也没有原型或参数。
  • classes are kind of blue print of possible function dont have arguments property but have prototypes.类是可能函数的一种蓝图,没有参数属性但有原型。 these prototypes become after the fact object upon instance.这些原型在实例之后成为事实对象。

So i have attempted to capture and log each iteration we check and result of course.所以我试图捕捉和记录我们检查的每次迭代和结果。

Hope this helps希望这可以帮助

 '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'+'-------');

shorter cleaner function covering strict mode, es6 modules, null, undefined and what ever not property manipulation on object.更简洁的功能,涵盖严格模式、es6 模块、null、undefined 以及对对象的任何非属性操作。

What I have found so far is since from above discussion classes are blue print not as such object on their own before the fact of instance.到目前为止,我发现的是,从上面的讨论中,类在实例之前并不是蓝图本身的对象。 Thus running toString function would almost always produce class {} output not [object object] after the instance and so on.因此运行 toString 函数几乎总是会在实例之后产生类 {} 输出而不是 [object object] 等等。 Once we know what is consistent then simply run regex test to see if result starts with word class.一旦我们知道什么是一致的,那么只需运行正则表达式测试以查看结果是否以单词类开头。

 "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?'));

Lead from DOMI's disucssion来自 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');

Maybe this can help也许这可以帮助

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