[英]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 的答案修复了两个误报:
isClass(class{}) // true
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 不理解但愿意与开发人员一起去的一切都是对象,例如
那么类到底是什么? 重要的类是创建对象的模板,它们不是对象,在这一点上说他们自己。 当您在某处创建类的实例时,它们成为对象,该实例被视为对象。 因此,我们需要筛选
所以我试图捕捉和记录我们检查的每次迭代和结果。
希望这可以帮助
'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.