如何检查一个变量是否是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;
}
}
缺点是调用函数会有各种未知的副作用…
我在这里先说清楚,任何函数都可以是构造函数。如果你区分"类"answers"函数",你就会做出糟糕的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'
});
这是一个古老的问题,几乎没有答案是正确的,除了这个和但是这里有一个警告…
为什么答案是错误的?
让我们从任何人建议调用函数开始…这是一种容易造成灾难的方法,应该在ChatGPT将其视为建议代码之前从建议中删除……下一个…
在JS运行时中,函数的字符串表示形式或其余代码在生产环境中不存在,可能在调试模式中,但在prod中不一定,完全相反。
这是因为一些JS运行时可以节省大量的final "字节码"通过从其几乎所有内容中删除源来缩小大小。
因此,每个建议任何字符串检查的人都不知道或考虑这些场景,而且任何函数toString
方法也可以用其他东西替换,使得大多数答案不是防弹的。
为什么没有正确答案?
最接近的答案是在任意函数的prototype
描述符处检查writable
:
- 像
- 简写方法根本就没有原型
- 编译成ES5函数的类可能会有可写,除非编译器非常关注这个细节,像Babel,导致运行时也稍微慢一些
- 只有未编译的本地ES2015+代码将通过所有测试:
prototype
存在并且它的writable
值恰好是false
{method(){}}
这样的const isESClass = fn => (
typeof fn === 'function' &&
Object.getOwnPropertyDescriptor(
fn,
'prototype'
)?.writable === false
);
重要的是要明白这在ES5的项目中会失败(无论出于什么原因),但基本上没有办法保证一个泛型函数,在es2015之前的世界里,是一个类还是不是,jQuery(以及其他)使用了如下模式,所有情况都是允许的:
function jQuery(...args) {
if (!(this instanceof jQuery))
return new jQuery(...args);
// do everything jQuery does
}
与ES2015+类不同的是,该实用程序既可以作为常规函数又可以作为new function
,所以基本上这个问题没有正确的答案,只是需要考虑的妥协和目标列表。
为方便读者参考:
- 对于常规函数,
- 原型默认是可写的,因为MakeConstructor使用默认的
- 类使用不可写的原型-参见第16点,MakeConstrcut被调用传递
false
函数和类之间有细微的差别,我们可以利用这个优势来区分它们,下面是我的实现:
// 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
about:
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` :(
来得太晚了,但如果我理解正确的话,这是另一种可以满足编译器和意图的方法。
function isInheritable(t) {
try {
return Boolean(class extends t {
})
} catch {
return false;
}
}
通过一些答案,并认为@Joe Hildebrand突出了边缘情况,因此以下解决方案更新以反映大多数尝试过的边缘情况。
关键观点:虽然我们正在研究类,但就像JS中指针和引用的争论一样,并没有确认其他语言的所有品质——JS本身不像其他语言结构那样有类。
有些人认为它是功能的糖衣语法,有些人则持相反的观点。我相信类在本质上仍然是一个函数,但不是像糖衣上的糖衣,而更像是一种可以加在类固醇上的东西。类可以做函数不能做的事情,或者不必费心升级它们来做的事情。
因此,暂时将类作为函数处理打开了另一个潘多拉盒子。JS中的所有东西都是对象,所有JS不理解但愿意与开发人员合作的东西都是对象,例如
- 布尔值可以是对象(如果用new关键字定义)
- 数字可以是对象(如果用new关键字定义)
- 字符串可以是对象(如果用new关键字定义)
- 日期总是对象
- 数学永远是对象
- 正则表达式总是对象
- 数组总是对象 函数总是对象
- 对象始终是对象
那么类到底是什么? 重要信息类是用于创建对象的模板,在这一点上它们不是对象。当您在某处创建类的实例时,它们成为对象,该实例被认为是对象。所以我们需要筛选出
- 我们正在处理的对象类型
- 然后我们需要筛选它的属性。
- 函数总是对象,它们总是有prototype和arguments属性。
- 箭头函数实际上是老派函数的糖衣,没有这个或更多简单返回上下文的概念,所以没有原型或参数,即使你试图定义它们。
- 类是可能的函数的蓝图,没有参数属性,但有原型。这些原型在实例之后成为事实对象。
因此,我尝试捕获并记录我们检查的每个迭代和结果。
希望有帮助
'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],等等。一旦我们知道什么是一致的,然后简单地运行regex测试,看看结果是否以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?'));
来自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');
我很震惊lodash竟然没有答案。看看这个-就像多米一样,我刚想出了一个解决方案来解决小故障。我知道这是很多代码,但这是目前为止我能写出的最有效、最容易理解的东西。也许有人可以通过regex方法来优化它:
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)
});
确保所有类的第一个大写字母。
也许这能帮上忙
let is_class = (obj) => {
try {
new obj();
return true;
} catch(e) {
return false;
};
};
- ES6是否引入了一种机制来生成块范围的函数语句(而不是表达式)
- node.js测试事件是否是在不使用超时的情况下使用sinon.js发出的
- 确定var是否是javascript中的elementFinder对象的方法是什么
- 检查对象是否是mongo游标
- 是否可以测试javascript函数是否是构造函数
- 检查请求的用户是否是django中的所有者
- 角度 2 :检查路由名称是否是当前名称
- 我怎么知道日期是否是星期六
- 从不断变化的动作创建者那里获取商店数据是否是一种常见的做法
- 在node.js/ssocket.io中,如何判断对象是否是套接字的实例
- 在jQuery中,如何测试一个元素是否是同一类的多个元素中的第一个
- 是否有跨浏览器和跨框架的方法来检查对象是否是HTML元素
- 如何判断页面是否是从fetch()调用的
- Node.js如何知道回调的第一个参数是否是错误处理程序
- 如何检查一个对象是否是“;深空”;
- javascript测试一个值是否是一个数字和一个大于0的数字
- jqueryangularjs如何知道鼠标按下事件是否是由浏览器的滚动条触发的
- 如何检查具有类的元素是否是该类的第一个子元素
- 找出回调函数的对象是否是ES6箭头
- 如何检查一个变量是否是ES6的类声明