您如何检查 ECMAScript 6 class 和函数之间的区别?
How do you check the difference between an ECMAScript 6 class and function?
在 ECMAScript 6 中,根据规范,classes 的 typeof
是 'function'
。
然而,根据规范,您也不允许将通过 class 语法创建的对象作为普通函数调用来调用。换句话说,您必须使用 new
关键字,否则会抛出 TypeError。
TypeError: Classes can’t be function-called
所以如果不使用 try catch,这会非常丑陋并且会破坏性能,您如何检查一个函数是来自 class
语法还是来自 function
语法?
如果我正确理解 ES6,使用 class
与您输入
的效果相同
var Foo = function(){}
var Bar = function(){
Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
在没有 keyword new
的情况下键入 MyClass()
时的语法错误只是为了防止污染全局 space 对象使用的变量。
var MyClass = function(){this.$ = "my private dollar"; return this;}
如果你有
// $ === jquery
var myObject = new MyClass();
// $ === still jquery
// myObject === global object
但如果你这样做
var myObject = MyClass();
// $ === "My private dollar"
因为this
在Constructor called as function中引用全局对象,但是当用关键字new
调用时Javascript首先创建新的空对象,然后在其上调用构造函数。
我做了一些研究,发现 ES6 classes 的原型对象 [spec 19.1.2.16] 似乎是 不可写的, 不可枚举、不可配置。
检查方法如下:
class F { }
console.log(Object.getOwnPropertyDescriptor(F, 'prototype'));
// {"value":{},"writable":false,"enumerable":false,"configurable":false
常规函数默认为可写、不可枚举、不可配置 .
function G() { }
console.log(Object.getOwnPropertyDescriptor(G, 'prototype'));
// {"value":{},"writable":true,"enumerable":false,"configurable":false}
ES6 Fiddle: http://www.es6fiddle.net/i7d0eyih/
因此 ES6 class 描述符将始终将这些属性设置为 false,如果您尝试定义描述符,将会抛出错误。
// Throws Error
Object.defineProperty(F, 'prototype', {
writable: true
});
然而,使用常规函数,您仍然可以定义这些描述符。
// Works
Object.defineProperty(G, 'prototype', {
writable: false
});
在常规函数上修改描述符并不常见,因此您可以使用它来检查它是否是 class,但这当然不是真正的解决方案。
@alexpods 将函数字符串化并检查 class 关键字的方法可能是目前最好的解决方案。
我认为检查函数是否为 ES6 class 的最简单方法是检查 .toString()
method. According to the es2015 spec 的结果:
The string representation must have the syntax of a FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpression, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition, or GeneratorMethod depending upon the actual characteristics of the object
所以检查功能看起来很简单:
function isClass(func) {
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}
运行 关于本帖中提到的不同方法的一些 performance benchmarks,这里是概述:
原生 Class - Props 方法(大型示例最快 56 倍,普通示例最快 15 倍):
function isNativeClass (thing) {
return typeof thing === 'function' && thing.hasOwnProperty('prototype') && !thing.hasOwnProperty('arguments')
}
之所以可行,是因为以下内容为真:
> Object.getOwnPropertyNames(class A {})
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} a (b,c) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(function () {})
[ 'length', 'name', 'arguments', 'caller', 'prototype' ]
> Object.getOwnPropertyNames(() => {})
> [ 'length', 'name' ]
原生 Class - 字符串方法(比正则表达式方法快大约 10%):
/**
* Is ES6+ class
* @param {any} value
* @returns {boolean}
*/
function isNativeClass (value /* :mixed */ ) /* :boolean */ {
return typeof value === 'function' && value.toString().indexOf('class') === 0
}
这也可用于确定常规 Class:
// Character positions
const INDEX_OF_FUNCTION_NAME = 9 // "function X", X is at index 9
const FIRST_UPPERCASE_INDEX_IN_ASCII = 65 // A is at index 65 in ASCII
const LAST_UPPERCASE_INDEX_IN_ASCII = 90 // Z is at index 90 in ASCII
/**
* Is Conventional Class
* Looks for function with capital first letter MyClass
* First letter is the 9th character
* If changed, isClass must also be updated
* @param {any} value
* @returns {boolean}
*/
function isConventionalClass (value /* :any */ ) /* :boolean */ {
if ( typeof value !== 'function' ) return false
const c = value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME)
return c >= FIRST_UPPERCASE_INDEX_IN_ASCII && c <= LAST_UPPERCASE_INDEX_IN_ASCII
}
我还建议查看我的 typechecker
package,其中包括上述用例 - 通过 isNativeClass
方法、isConventionalClass
方法和 isClass
检查两种类型的方法。
查看 Babel 生成的编译代码,我认为您无法判断函数是否用作 class。回到那个时候,JavaScript 没有 classes,每个构造函数都只是一个函数。今天的JavaScriptclass关键字并没有引入'classes'的新概念,而是一种语法糖。
ES6代码:
// ES6
class A{}
ES5 由 Babel:
生成
// ES5
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var A = function A() {
_classCallCheck(this, A);
};
当然,如果您喜欢编码约定,您可以解析函数(class),并检查它的名称是否以大写字母开头。
function isClass(fn) {
return typeof fn === 'function' && /^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);
}
编辑:
已经支持class关键字的浏览器可以在解析时使用它。否则,你会被大写字母 1 卡住。
编辑:
正如 balupton 指出的那样,Babel 为匿名 classes 生成 function _class() {}
。在此基础上改进了正则表达式。
编辑:
在正则表达式中添加了 _default
,以检测像 export default class {}
这样的 class
警告
BabelJS 正在大量开发中,无法保证在这些情况下他们不会更改默认函数名称。真的,你不应该依赖那个。
因为现有的答案从 ES5 环境解决了这个问题
我认为可能值得从 ES2015+ 提供答案
看法;最初的问题没有具体说明,今天很多人没有
不再需要转译 classes,这稍微改变了情况。
我特别想指出 可以明确回答
question "can this value be constructed?" 不可否认,这通常没有用
在其自己的;如果你需要知道,同样的基本问题仍然存在
如果可以调用一个值。
有些东西可以构造吗?
首先,我认为我们需要澄清一些术语,因为问是否
value is a constructor 可能意味着不止一件事:
- 从字面上看,这个值有一个 [[construct]] 插槽吗?如果是这样,那就是
可构造的。如果没有,则不可构造。
- 这个函数是打算构造的吗?我们可以产生一些底片:函数
无法建造的东西不打算建造。但我们不能
还说(不求助于启发式检查)一个函数是否是
可构造的不是用作构造函数。
2 无法回答的原因是使用 function
关键字创建的函数
alone 是可构造和可调用的,但这些函数通常是预期的
仅用于其中一个目的。正如其他一些人提到的,2 也是一个
可疑的问题——这类似于问“作者在想什么时
写这个?”我认为人工智能还不存在 :) 虽然在一个完美的世界中,也许所有作者都会保留
构造函数的 PascalCase(参见 balupton 的 isConventionalClass
函数),在
实践中遇到 false positives/negatives 并不少见。
关于这个问题的第一个版本,是的,我们可以知道一个函数是
可构造的。显而易见的事情是尝试构建它。这并不是真的可以接受,因为我们不知道这样做是否会有副作用
effects——似乎我们对函数的性质一无所知,因为如果我们知道,我们就不需要这个检查了)。幸运的是有一种方法可以构建
没有真正构造它的构造函数:
const isConstructable = fn => {
try {
new new Proxy(fn, { construct: () => ({}) });
return true;
} catch (err) {
return false;
}
};
construct
代理处理程序可以覆盖代理值的 [[construct]],但是
它不能使不可构造的值可构造。所以我们可以“嘲笑
实例化”输入以测试这是否失败。请注意构造
陷阱必须 return 一个对象。
isConstructable(class {}); // true
isConstructable(class {}.bind()); // true
isConstructable(function() {}); // true
isConstructable(function() {}.bind()); // true
isConstructable(() => {}); // false
isConstructable((() => {}).bind()); // false
isConstructable(async () => {}); // false
isConstructable(async function() {}); // false
isConstructable(function * () {}); // false
isConstructable({ foo() {} }.foo); // false
isConstructable(URL); // true
注意箭头函数、异步函数、生成器和方法不是
double-duty 以 "legacy" 函数声明和表达式的方式。这些
函数没有被赋予 [[construct]] 槽(我认为没有多少人意识到 "shorthand method" 语法是做某事的——它不仅仅是糖)。
所以回顾一下,如果您的问题确实是 "is this constructable,",以上就是结论性的。不幸的是没有别的。
可以调用吗?
我们必须再次澄清这个问题,因为如果我们非常直白,下面的测试确实有效*:
const isCallable = fn => typeof fn === 'function';
这是因为 ES 目前不允许你创建一个没有
[[call]] 插槽(好吧,绑定函数不直接有一个,但它们代理到
一个函数)。
这可能看起来不真实,因为构造函数是用 class 创建的
如果您尝试调用它们而不是构造它们,则语法会抛出。然而他们
是可调用的——只是它们的 [[call]] 槽被定义为一个函数
抛出!哎呀
我们可以通过将我们的第一个函数转换为它的镜像来证明这一点。
// Demonstration only, this function is useless:
const isCallable = fn => {
try {
new Proxy(fn, { apply: () => undefined })();
return true;
} catch (err) {
return false;
}
};
isCallable(() => {}); // true
isCallable(function() {}); // true
isCallable(class {}); // ... true!
这样的功能没有帮助,但我想把这些结果展示给
突出问题的本质。我们无法轻易检查是否
函数是 "new-only" 是答案不是根据“缺少
调用“"never-new" 的方式是根据 "absence of construct" 建模的。我们感兴趣的是隐藏在我们无法观察的方法中,除非通过它的评估,所以我们所能做的就是使用启发式检查作为我们真正想知道的代理。
启发式选项
我们可以从缩小不明确的情况开始。任何函数
is not constructable is unambiguously callable in both senses: if
typeof fn === 'function'
但 isConstructable(fn) === false
,我们有一个
call-only 函数,例如箭头、生成器或方法。
所以感兴趣的四种情况是 class {}
和 function() {}
加上界限
两者的形式。我们能说的其他一切都是可调用的。请注意 none 的
当前的答案提到了绑定函数,但是这些引入了重要的
任何启发式检查的问题。
正如 balupton 指出的那样,属性 描述符的存在与否
'caller' 属性 可以作为函数创建方式的指示符。
绑定函数外来对象不会有这个 own-property 即使
它包装的功能。 属性 将通过继承而存在
Function.prototype
,但这也适用于 class 构造函数。
同样,BFEO 的 toString 通常会开始 'function',即使绑定
函数是用 class 创建的。现在,一种检测 BFEO 本身的启发式方法
本来是看他们名字开头的'bound ',可惜这个是死的
结尾;它仍然没有告诉我们 绑定了什么——这对我们来说是不透明的。
然而,如果 toString 确实 return 'class'(例如 DOM
构造函数),这是一个非常可靠的信号,表明它不可调用。
那时我们能做的最好的事情是这样的:
const isDefinitelyCallable = fn =>
typeof fn === 'function' &&
!isConstructable(fn);
isDefinitelyCallable(class {}); // false
isDefinitelyCallable(class {}.bind()); // false
isDefinitelyCallable(function() {}); // false <-- callable
isDefinitelyCallable(function() {}.bind()); // false <-- callable
isDefinitelyCallable(() => {}); // true
isDefinitelyCallable((() => {}).bind()); // true
isDefinitelyCallable(async () => {}); // true
isDefinitelyCallable(async function() {}); // true
isDefinitelyCallable(function * () {}); // true
isDefinitelyCallable({ foo() {} }.foo); // true
isDefinitelyCallable(URL); // false
const isProbablyNotCallable = fn =>
typeof fn !== 'function' ||
fn.toString().startsWith('class') ||
Boolean(
fn.prototype &&
!Object.getOwnPropertyDescriptor(fn, 'prototype').writable // or your fave
);
isProbablyNotCallable(class {}); // true
isProbablyNotCallable(class {}.bind()); // false <-- not callable
isProbablyNotCallable(function() {}); // false
isProbablyNotCallable(function() {}.bind()); // false
isProbablyNotCallable(() => {}); // false
isProbablyNotCallable((() => {}).bind()); // false
isProbablyNotCallable(async () => {}); // false
isProbablyNotCallable(async function() {}); // false
isProbablyNotCallable(function * () {}); // false
isProbablyNotCallable({ foo() {} }.foo); // false
isProbablyNotCallable(URL); // true
带箭头的情况指出我们得到的答案不是特别喜欢。
在 isProbablyNotCallable 函数中,条件的最后一部分可以是
替换为其他答案的其他检查;我在这里选择了 Miguel Mota,因为
它恰好也适用于(大多数?)DOM 构造函数,即使是那些已定义的构造函数
在引入 ES classes 之前。但这并不重要——每一个都可能
检查有一个缺点,没有魔法组合。
以上描述了据我所知什么是可能的,什么是不可能的
当代ES。它没有解决特定于 ES5 和更早版本的需求,
尽管实际上在 ES5 和更早版本中,这两个问题的答案总是
"true" 任何函数。
未来
有人提议进行本机测试,使 [[FunctionKind]] 插槽成为可能
可观察到揭示函数是否使用 class
:
创建
https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md
如果这个提议或类似的提议得到推进,我们将获得解决问题的方法
这个问题具体到 class
至少。
* 忽略附件 B [[IsHTMLDDA]] 案例。
虽然没有直接关系,但是如果class、构造函数或函数是你生成的,你想知道是调用函数还是用new关键字实例化一个对象,你可以这样做通过在构造函数或 class 的原型中添加自定义标志。
您当然可以使用其他答案中提到的方法(例如 toString
)从函数中分辨出 class。但是,如果你的代码是使用 babel 转译的,那肯定会有问题。
为了更简单,您可以尝试以下代码 -
class Foo{
constructor(){
this.someProp = 'Value';
}
}
Foo.prototype.isClass = true;
或者如果使用构造函数 -
function Foo(){
this.someProp = 'Value';
}
Foo.prototype.isClass = true;
你可以通过检查原型 属性.
来检查它是否是 class
if(Foo.prototype.isClass){
//It's a class
}
如果 class 或函数不是您创建的,此方法显然不起作用。 React.js
使用此方法检查 React Component
是 Class 组件还是功能组件。这个答案摘自 Dan Abramov 的 blog post
可以用new.target判断是ES6class函数实例化还是函数构造函数实例化
class Person1 {
constructor(name) {
this.name = name;
console.log(new.target) // => // => [Class: Person1]
}
}
function Person2(){
this.name='cc'
console.log(new.target) // => [Function: Person2]
}
我知道这是一个非常古老的问题,但我最近一直在想同样的事情,特别是因为如果您尝试将函数用作 class 组件(例如如果你设置了 babel 来将 classes 转换为我拥有的函数)。我坐了一会儿,想出了这个:
function isClass(f) {
return typeof f === "function" && (() => { try { f(); return false } catch { return true } })();
}
isClass(function() {}); //false
isClass(() => {}); //false
isClass(class {}); //true
这是基于 ES6 classes 要求使用 new
关键字,如果缺少则抛出错误。该函数首先检查传递的参数类型是否为函数,这适用于 classes 和函数。然后它会尝试在不使用 new
关键字的情况下调用它,这只能由普通函数完成。但是请记住,如果函数检查是否正在通过 if (!(this instanceof ConstructorName)) throw
:
之类的方法使用 new 调用它,则会抛出错误,这可能会被愚弄
isClass(function Constructor() {
if (!(this instanceof Constructor)) throw new Error();
}); // true
function isClass(f) {
return typeof f === "function" && (() => { try { f(); return false } catch { return true } })();
}
console.log(isClass(function() {})); //false
console.log(isClass(() => {})); //false
console.log(isClass(class {})); //true
console.log(isClass(function Constructor() {
if (!(this instanceof Constructor)) throw new Error();
})); // true
在 ECMAScript 6 中,根据规范,classes 的 typeof
是 'function'
。
然而,根据规范,您也不允许将通过 class 语法创建的对象作为普通函数调用来调用。换句话说,您必须使用 new
关键字,否则会抛出 TypeError。
TypeError: Classes can’t be function-called
所以如果不使用 try catch,这会非常丑陋并且会破坏性能,您如何检查一个函数是来自 class
语法还是来自 function
语法?
如果我正确理解 ES6,使用 class
与您输入
var Foo = function(){}
var Bar = function(){
Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
在没有 keyword new
的情况下键入 MyClass()
时的语法错误只是为了防止污染全局 space 对象使用的变量。
var MyClass = function(){this.$ = "my private dollar"; return this;}
如果你有
// $ === jquery
var myObject = new MyClass();
// $ === still jquery
// myObject === global object
但如果你这样做
var myObject = MyClass();
// $ === "My private dollar"
因为this
在Constructor called as function中引用全局对象,但是当用关键字new
调用时Javascript首先创建新的空对象,然后在其上调用构造函数。
我做了一些研究,发现 ES6 classes 的原型对象 [spec 19.1.2.16] 似乎是 不可写的, 不可枚举、不可配置。
检查方法如下:
class F { }
console.log(Object.getOwnPropertyDescriptor(F, 'prototype'));
// {"value":{},"writable":false,"enumerable":false,"configurable":false
常规函数默认为可写、不可枚举、不可配置 .
function G() { }
console.log(Object.getOwnPropertyDescriptor(G, 'prototype'));
// {"value":{},"writable":true,"enumerable":false,"configurable":false}
ES6 Fiddle: http://www.es6fiddle.net/i7d0eyih/
因此 ES6 class 描述符将始终将这些属性设置为 false,如果您尝试定义描述符,将会抛出错误。
// Throws Error
Object.defineProperty(F, 'prototype', {
writable: true
});
然而,使用常规函数,您仍然可以定义这些描述符。
// Works
Object.defineProperty(G, 'prototype', {
writable: false
});
在常规函数上修改描述符并不常见,因此您可以使用它来检查它是否是 class,但这当然不是真正的解决方案。
@alexpods 将函数字符串化并检查 class 关键字的方法可能是目前最好的解决方案。
我认为检查函数是否为 ES6 class 的最简单方法是检查 .toString()
method. According to the es2015 spec 的结果:
The string representation must have the syntax of a FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpression, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition, or GeneratorMethod depending upon the actual characteristics of the object
所以检查功能看起来很简单:
function isClass(func) {
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}
运行 关于本帖中提到的不同方法的一些 performance benchmarks,这里是概述:
原生 Class - Props 方法(大型示例最快 56 倍,普通示例最快 15 倍):
function isNativeClass (thing) {
return typeof thing === 'function' && thing.hasOwnProperty('prototype') && !thing.hasOwnProperty('arguments')
}
之所以可行,是因为以下内容为真:
> Object.getOwnPropertyNames(class A {})
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} a (b,c) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(function () {})
[ 'length', 'name', 'arguments', 'caller', 'prototype' ]
> Object.getOwnPropertyNames(() => {})
> [ 'length', 'name' ]
原生 Class - 字符串方法(比正则表达式方法快大约 10%):
/**
* Is ES6+ class
* @param {any} value
* @returns {boolean}
*/
function isNativeClass (value /* :mixed */ ) /* :boolean */ {
return typeof value === 'function' && value.toString().indexOf('class') === 0
}
这也可用于确定常规 Class:
// Character positions
const INDEX_OF_FUNCTION_NAME = 9 // "function X", X is at index 9
const FIRST_UPPERCASE_INDEX_IN_ASCII = 65 // A is at index 65 in ASCII
const LAST_UPPERCASE_INDEX_IN_ASCII = 90 // Z is at index 90 in ASCII
/**
* Is Conventional Class
* Looks for function with capital first letter MyClass
* First letter is the 9th character
* If changed, isClass must also be updated
* @param {any} value
* @returns {boolean}
*/
function isConventionalClass (value /* :any */ ) /* :boolean */ {
if ( typeof value !== 'function' ) return false
const c = value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME)
return c >= FIRST_UPPERCASE_INDEX_IN_ASCII && c <= LAST_UPPERCASE_INDEX_IN_ASCII
}
我还建议查看我的 typechecker
package,其中包括上述用例 - 通过 isNativeClass
方法、isConventionalClass
方法和 isClass
检查两种类型的方法。
查看 Babel 生成的编译代码,我认为您无法判断函数是否用作 class。回到那个时候,JavaScript 没有 classes,每个构造函数都只是一个函数。今天的JavaScriptclass关键字并没有引入'classes'的新概念,而是一种语法糖。
ES6代码:
// ES6
class A{}
ES5 由 Babel:
生成// ES5
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var A = function A() {
_classCallCheck(this, A);
};
当然,如果您喜欢编码约定,您可以解析函数(class),并检查它的名称是否以大写字母开头。
function isClass(fn) {
return typeof fn === 'function' && /^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);
}
编辑:
已经支持class关键字的浏览器可以在解析时使用它。否则,你会被大写字母 1 卡住。
编辑:
正如 balupton 指出的那样,Babel 为匿名 classes 生成 function _class() {}
。在此基础上改进了正则表达式。
编辑:
在正则表达式中添加了 _default
,以检测像 export default class {}
警告
BabelJS 正在大量开发中,无法保证在这些情况下他们不会更改默认函数名称。真的,你不应该依赖那个。
因为现有的答案从 ES5 环境解决了这个问题 我认为可能值得从 ES2015+ 提供答案 看法;最初的问题没有具体说明,今天很多人没有 不再需要转译 classes,这稍微改变了情况。
我特别想指出 可以明确回答 question "can this value be constructed?" 不可否认,这通常没有用 在其自己的;如果你需要知道,同样的基本问题仍然存在 如果可以调用一个值。
有些东西可以构造吗?
首先,我认为我们需要澄清一些术语,因为问是否 value is a constructor 可能意味着不止一件事:
- 从字面上看,这个值有一个 [[construct]] 插槽吗?如果是这样,那就是 可构造的。如果没有,则不可构造。
- 这个函数是打算构造的吗?我们可以产生一些底片:函数 无法建造的东西不打算建造。但我们不能 还说(不求助于启发式检查)一个函数是否是 可构造的不是用作构造函数。
2 无法回答的原因是使用 function
关键字创建的函数
alone 是可构造和可调用的,但这些函数通常是预期的
仅用于其中一个目的。正如其他一些人提到的,2 也是一个
可疑的问题——这类似于问“作者在想什么时
写这个?”我认为人工智能还不存在 :) 虽然在一个完美的世界中,也许所有作者都会保留
构造函数的 PascalCase(参见 balupton 的 isConventionalClass
函数),在
实践中遇到 false positives/negatives 并不少见。
关于这个问题的第一个版本,是的,我们可以知道一个函数是 可构造的。显而易见的事情是尝试构建它。这并不是真的可以接受,因为我们不知道这样做是否会有副作用 effects——似乎我们对函数的性质一无所知,因为如果我们知道,我们就不需要这个检查了)。幸运的是有一种方法可以构建 没有真正构造它的构造函数:
const isConstructable = fn => {
try {
new new Proxy(fn, { construct: () => ({}) });
return true;
} catch (err) {
return false;
}
};
construct
代理处理程序可以覆盖代理值的 [[construct]],但是
它不能使不可构造的值可构造。所以我们可以“嘲笑
实例化”输入以测试这是否失败。请注意构造
陷阱必须 return 一个对象。
isConstructable(class {}); // true
isConstructable(class {}.bind()); // true
isConstructable(function() {}); // true
isConstructable(function() {}.bind()); // true
isConstructable(() => {}); // false
isConstructable((() => {}).bind()); // false
isConstructable(async () => {}); // false
isConstructable(async function() {}); // false
isConstructable(function * () {}); // false
isConstructable({ foo() {} }.foo); // false
isConstructable(URL); // true
注意箭头函数、异步函数、生成器和方法不是 double-duty 以 "legacy" 函数声明和表达式的方式。这些 函数没有被赋予 [[construct]] 槽(我认为没有多少人意识到 "shorthand method" 语法是做某事的——它不仅仅是糖)。
所以回顾一下,如果您的问题确实是 "is this constructable,",以上就是结论性的。不幸的是没有别的。
可以调用吗?
我们必须再次澄清这个问题,因为如果我们非常直白,下面的测试确实有效*:
const isCallable = fn => typeof fn === 'function';
这是因为 ES 目前不允许你创建一个没有 [[call]] 插槽(好吧,绑定函数不直接有一个,但它们代理到 一个函数)。
这可能看起来不真实,因为构造函数是用 class 创建的 如果您尝试调用它们而不是构造它们,则语法会抛出。然而他们 是可调用的——只是它们的 [[call]] 槽被定义为一个函数 抛出!哎呀
我们可以通过将我们的第一个函数转换为它的镜像来证明这一点。
// Demonstration only, this function is useless:
const isCallable = fn => {
try {
new Proxy(fn, { apply: () => undefined })();
return true;
} catch (err) {
return false;
}
};
isCallable(() => {}); // true
isCallable(function() {}); // true
isCallable(class {}); // ... true!
这样的功能没有帮助,但我想把这些结果展示给 突出问题的本质。我们无法轻易检查是否 函数是 "new-only" 是答案不是根据“缺少 调用“"never-new" 的方式是根据 "absence of construct" 建模的。我们感兴趣的是隐藏在我们无法观察的方法中,除非通过它的评估,所以我们所能做的就是使用启发式检查作为我们真正想知道的代理。
启发式选项
我们可以从缩小不明确的情况开始。任何函数
is not constructable is unambiguously callable in both senses: if
typeof fn === 'function'
但 isConstructable(fn) === false
,我们有一个
call-only 函数,例如箭头、生成器或方法。
所以感兴趣的四种情况是 class {}
和 function() {}
加上界限
两者的形式。我们能说的其他一切都是可调用的。请注意 none 的
当前的答案提到了绑定函数,但是这些引入了重要的
任何启发式检查的问题。
正如 balupton 指出的那样,属性 描述符的存在与否
'caller' 属性 可以作为函数创建方式的指示符。
绑定函数外来对象不会有这个 own-property 即使
它包装的功能。 属性 将通过继承而存在
Function.prototype
,但这也适用于 class 构造函数。
同样,BFEO 的 toString 通常会开始 'function',即使绑定 函数是用 class 创建的。现在,一种检测 BFEO 本身的启发式方法 本来是看他们名字开头的'bound ',可惜这个是死的 结尾;它仍然没有告诉我们 绑定了什么——这对我们来说是不透明的。
然而,如果 toString 确实 return 'class'(例如 DOM 构造函数),这是一个非常可靠的信号,表明它不可调用。
那时我们能做的最好的事情是这样的:
const isDefinitelyCallable = fn =>
typeof fn === 'function' &&
!isConstructable(fn);
isDefinitelyCallable(class {}); // false
isDefinitelyCallable(class {}.bind()); // false
isDefinitelyCallable(function() {}); // false <-- callable
isDefinitelyCallable(function() {}.bind()); // false <-- callable
isDefinitelyCallable(() => {}); // true
isDefinitelyCallable((() => {}).bind()); // true
isDefinitelyCallable(async () => {}); // true
isDefinitelyCallable(async function() {}); // true
isDefinitelyCallable(function * () {}); // true
isDefinitelyCallable({ foo() {} }.foo); // true
isDefinitelyCallable(URL); // false
const isProbablyNotCallable = fn =>
typeof fn !== 'function' ||
fn.toString().startsWith('class') ||
Boolean(
fn.prototype &&
!Object.getOwnPropertyDescriptor(fn, 'prototype').writable // or your fave
);
isProbablyNotCallable(class {}); // true
isProbablyNotCallable(class {}.bind()); // false <-- not callable
isProbablyNotCallable(function() {}); // false
isProbablyNotCallable(function() {}.bind()); // false
isProbablyNotCallable(() => {}); // false
isProbablyNotCallable((() => {}).bind()); // false
isProbablyNotCallable(async () => {}); // false
isProbablyNotCallable(async function() {}); // false
isProbablyNotCallable(function * () {}); // false
isProbablyNotCallable({ foo() {} }.foo); // false
isProbablyNotCallable(URL); // true
带箭头的情况指出我们得到的答案不是特别喜欢。
在 isProbablyNotCallable 函数中,条件的最后一部分可以是 替换为其他答案的其他检查;我在这里选择了 Miguel Mota,因为 它恰好也适用于(大多数?)DOM 构造函数,即使是那些已定义的构造函数 在引入 ES classes 之前。但这并不重要——每一个都可能 检查有一个缺点,没有魔法组合。
以上描述了据我所知什么是可能的,什么是不可能的 当代ES。它没有解决特定于 ES5 和更早版本的需求, 尽管实际上在 ES5 和更早版本中,这两个问题的答案总是 "true" 任何函数。
未来
有人提议进行本机测试,使 [[FunctionKind]] 插槽成为可能
可观察到揭示函数是否使用 class
:
https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md
如果这个提议或类似的提议得到推进,我们将获得解决问题的方法
这个问题具体到 class
至少。
* 忽略附件 B [[IsHTMLDDA]] 案例。
虽然没有直接关系,但是如果class、构造函数或函数是你生成的,你想知道是调用函数还是用new关键字实例化一个对象,你可以这样做通过在构造函数或 class 的原型中添加自定义标志。
您当然可以使用其他答案中提到的方法(例如 toString
)从函数中分辨出 class。但是,如果你的代码是使用 babel 转译的,那肯定会有问题。
为了更简单,您可以尝试以下代码 -
class Foo{
constructor(){
this.someProp = 'Value';
}
}
Foo.prototype.isClass = true;
或者如果使用构造函数 -
function Foo(){
this.someProp = 'Value';
}
Foo.prototype.isClass = true;
你可以通过检查原型 属性.
来检查它是否是 classif(Foo.prototype.isClass){
//It's a class
}
如果 class 或函数不是您创建的,此方法显然不起作用。 React.js
使用此方法检查 React Component
是 Class 组件还是功能组件。这个答案摘自 Dan Abramov 的 blog post
可以用new.target判断是ES6class函数实例化还是函数构造函数实例化
class Person1 {
constructor(name) {
this.name = name;
console.log(new.target) // => // => [Class: Person1]
}
}
function Person2(){
this.name='cc'
console.log(new.target) // => [Function: Person2]
}
我知道这是一个非常古老的问题,但我最近一直在想同样的事情,特别是因为如果您尝试将函数用作 class 组件(例如如果你设置了 babel 来将 classes 转换为我拥有的函数)。我坐了一会儿,想出了这个:
function isClass(f) {
return typeof f === "function" && (() => { try { f(); return false } catch { return true } })();
}
isClass(function() {}); //false
isClass(() => {}); //false
isClass(class {}); //true
这是基于 ES6 classes 要求使用 new
关键字,如果缺少则抛出错误。该函数首先检查传递的参数类型是否为函数,这适用于 classes 和函数。然后它会尝试在不使用 new
关键字的情况下调用它,这只能由普通函数完成。但是请记住,如果函数检查是否正在通过 if (!(this instanceof ConstructorName)) throw
:
isClass(function Constructor() {
if (!(this instanceof Constructor)) throw new Error();
}); // true
function isClass(f) {
return typeof f === "function" && (() => { try { f(); return false } catch { return true } })();
}
console.log(isClass(function() {})); //false
console.log(isClass(() => {})); //false
console.log(isClass(class {})); //true
console.log(isClass(function Constructor() {
if (!(this instanceof Constructor)) throw new Error();
})); // true