42

在ECMAScript 6中类型根据规范,'功能'.

但是,根据规范,不允许将通过类语法创建的对象作为普通函数调用进行调用。换句话说,您必须使用新的关键字,否则将引发TypeError。

TypeError:类不能被函数调用

因此,如果不使用try-catch(这会非常难看并破坏性能),如何检查函数是否来自语法或从功能语法?

2
  • 1
    我已经将类检测添加到我的javascript库中型平安。它具有本机es6类检查,以及传统的CamelCase函数检查。 评论 2015年8月26日23:26
  • 尝试/捕捉如果函数本身抛出错误,也不会工作。更重要的是,如果它是一个老派的构造函数新的肯定会污染全球,这比性能问题更严重。
    – 狮子座
    评论 2019年3月12日7:53

10个答案10

重置为默认值
44

我认为检查函数是否为ES6类的最简单方法是检查.toString()方法。根据es2015规范:

字符串表示形式的语法必须为FunctionDeclaration FunctionExpression、GeneratorDeclation、Generator Expression,类别声明,类表达式、ArrowFunction、MethodDefinition或GeneratorMethod,具体取决于对象的实际特征

所以check函数看起来很简单:

函数isClass(func){函数的返回类型===“函数”&&/^class\s/.test(Function.prototype.toString.call(func));}
13
  • @当然,穆格在jsfiddle中不起作用。它使用一些编译器,如babel,witch编译ES6类来运行。 评论 2015年3月17日8:23
  • @穆格斯,你确定es6fiddle没有在es5中转换你的javascript吗?
    – 哈克托
    评论 2015年3月17日8:23
  • @穆格斯,你可以检查一下操作系统.js具有和谐旗帜或镀铬金丝雀 评论 2015年3月17日8:24
  • @穆格斯,你可以在源代码中看到转换后的es5代码,文件名为_1.js
    – 哈克托
    评论 2015年3月17日8:26
  • 1
    @亚历山大,我想你可以函数类型===“function”&&/^class\s/.test(func+“”)
    – 哈克托
    评论 2015年3月17日8:40
20

因为现有的答案从ES5环境中解决了这个问题我认为值得从ES2015中给出答案+观点;最初的问题没有具体说明,今天很多人都没有更长的时间需要转换类,这稍微改变了情况。

我特别想指出可以明确回答“这个价值观能被构建吗?”诚然,这通常没有用处独立;如果你需要知道,同样的基本问题仍然存在如果可以调用值。

什么东西是可以建造的吗?

首先,我认为我们需要澄清一些术语,因为我们需要询问value是一个构造函数可能意味着不止一件事:

  1. 从字面上看,这个值有[[construct]]槽吗?如果是,那就是可施工。如果没有,则它是不可构造的。
  2. 是这个功能吗有意的要建造吗?我们可以产生一些负面影响:功能不能建造的,不是打算建造的。但我们不能也可以说(不采用启发式检查)可建造的不是意味着用作构造函数。

2无法回答的是,使用功能关键字单独一个函数既可构造又可调用,但通常需要这样的函数仅用于其中一个目的。正如其他人提到的,2也是一个可疑的问题-这类似于问“作者在写作时是怎么想的写了这个?“我认为人工智能还没有出现:)虽然在一个完美的世界里,也许所有的作者都会保留施工人员的PascalCase(见balupton’s是常规类功能),in实践中,这种测试经常会出现假阳性/假阴性。

关于这个问题的第一个版本,是的,我们可以知道函数是否可施工。显然,要做的是尝试构建它。但这实际上是不可接受的,因为我们不知道这样做是否会有好处效果-我们似乎对函数的性质一无所知,因为如果我们知道,就不需要进行此检查)。幸运的是,有一种方法可以构建没有的构造函数真正地构建它:

const isConstructable=fn=>{尝试{新代理(fn,{construct:()=>({})});返回true;}捕获(错误){返回false;}};

这个建造代理处理程序可以覆盖代理值的[[construct]],但它不能使不可构造的值可构造。所以我们可以“嘲笑”实例化“输入以测试是否失败。请注意陷阱必须返回一个对象。

isConstructable(类{});//真的isConstructable(类{}.bind());//真的是可构造的(函数(){});//真的isConstructable(函数(){}.bind());//真的可构造(()=>{});//isConstructable((()=>{}).bind());//isConstructable(异步()=>{});//isConstructable(异步函数(){});//isConstructable(函数*(){});//可构造({foo(){}}.foo);//可构造(URL);//真的

注意,箭头函数、异步函数、生成器和方法不是“遗留”函数声明和表达式具有双重责任。这些函数没有[[construct]]槽(我想没有多少人意识到“速记方法”语法是有作用的——它不仅仅是糖)。

综上所述,如果你的问题真的是“这是可构造的”,那么上面的结论是肯定的。不幸的是,其他的都不会。

有什么可以调用的吗?

我们必须再次澄清这个问题,因为如果我们非常直白,那么以下测试实际上有效*:

const isCallable=fn=>类型fn===“函数”;

这是因为ES当前不允许您创建没有[[调用]]槽(好吧,绑定函数没有直接的槽,但它们代理到函数)。

这可能看起来不真实,因为构造函数是用类创建的如果你试图调用它们而不是构造它们,语法就会抛出。然而,他们是可调用的-只是它们的[[调用]]槽被定义为一个函数真是太棒了!Oy公司。

我们可以通过将第一个函数转换为其镜像来证明这一点。

//仅用于演示,此功能无效:常量可调用=fn=>{尝试{新代理(fn,{apply:()=>未定义})();返回true;}捕获(错误){返回false;}};isCallable(()=>{});//真的可调用(函数(){});//真的isCallable(类{});//。。。真的!

这样的函数没有帮助,但我想将这些结果显示给关注问题的本质。我们无法轻松检查功能是“新的”,即答案不是根据“缺少所谓“永无止境”的方式是根据“缺乏构造”来建模的。我们感兴趣的知识被埋藏在一种方法中,我们只能通过评估才能观察到它,所以我们所能做的就是使用启发式检查作为我们真正想知道的东西的代理。

启发性选项

我们可以从缩小模棱两可的案例开始。任何功能constructable在两种意义上都是明确可调用的:如果类型fn===“函数”但是isConstructable(fn)===假,我们有一个只调用函数,如箭头、生成器或方法。

所以这四个有趣的案例是类{}函数(){}加上界限两者的形式。我们能说的一切都是可以调用的。请注意当前的答案提到了绑定函数,但这些函数引入了重要的任何启发式检查的问题。

正如balupton所指出的“caller”属性可以作为函数创建方式的指示器。绑定函数外来对象将不具有此自身属性,即使它包装的功能。该属性将通过继承而存在功能.原型,但对于类构造函数也是如此。

同样,BFEO的toString通常以“函数”开头,即使绑定函数是用类创建的。现在,一种探测BFEO本身的启发式方法会去看看他们的名字是否开始“绑定”,但不幸的是这是一个死亡结束;它仍然没有告诉我们什么被绑定了&这对我们来说是不透明的。

然而,如果toString确实返回“class”(对于DOM来说,这不是真的构造函数),这是一个非常可靠的信号,表明它不可调用。

那么我们能做的最好的事情就是这样:

常量为DefinelyCallable=fn=>类型fn===“函数”&&!可建造(fn);isDefinelyCallable(类{});//isDefinitelyCallable(类{}.bind());//isDefinelyCallable(函数(){});//false<--可调用isDefinitionCallable(函数(){}.bind());//false<--可调用isDefinelyCallable(()=>{});//真的isDefinelyCallable(()=>{}).bind());//真的isDefinelyCallable(异步()=>{});//真的isDefinelyCallable(异步函数(){});//真的isDefinelyCallable(函数*(){});//真的isDefinelyCallable({foo(){}}.foo);//真的isDefinitelyCallable(URL);//常数isProbablyNotCallable=fn=>类型fn!=='函数'||fn.toString().startsWith(“类”)||布尔值(fn.原型&&!Object.getOwnPropertyDescriptor(fn,“prototype”).writeable//或您的最爱);isProbablyNotCallable(类{});//真的isProbablyNotCallable(类{}.bind());//false<--不可调用isProbablyNotCallable(函数(){});//isProbablyNotCallable(函数(){}.bind());//isProbablyNotCallable(()=>{});//isProbablyNotCallable(()=>{}).bind());//isProbablyNotCallable(异步()=>{});//isProbablyNotCallable(异步函数(){});//isProbablyNotCallable(函数*(){});//isProbablyNotCallable({foo(){}}.foo);//isProbablyNotCallable(URL);//真的

带箭头的案例指出了我们不太喜欢的答案。

在isProbablyNotCallable函数中,条件的最后一部分可以是替换为其他答案中的其他检查;我选择了Miguel Mota在这里,因为它碰巧也适用于(大多数?)DOM构造函数,甚至那些定义的在引入ES类之前。但这并不重要——每一种可能支票有缺点,没有魔术组合。


据我所知,以上描述了在当代ES。它没有解决ES5和更早版本的特定需求,尽管在ES5和更早的版本中,这两个问题的答案总是“true”表示任何函数。

未来

有一个关于本地测试的建议,该测试将成为[[FunctionKind]]插槽在揭示函数是否是用:

https://github.com/caitp/TC39-Proposals/blob/master/TC39-reflect-isconstructor-iscallable.md

如果这项提议或类似的提议取得进展,我们将获得解决方案这个问题具体到什么时候至少。

*忽略附录B[[IsHTMLDDA]]案例。

4
  • 1
    +1表示巧妙使用代理:-)
    – 贝吉
    评论 2018年4月29日21:05
  • 有趣的事情可能会让你惊讶:可构造({a(){}}.a)为false:对象速记为纯粹是语法糖,因为用它创建的方法不是构造函数。 评论 2019年5月8日11:47
  • @克里斯·摩根(ChrisMorgan)是的,答案中提到过:“这些函数没有[[construct]]槽(我想没有多少人意识到“速记法”语法是有作用的——它不仅仅是糖)。”
    – 分号
    评论 2019年5月8日21:24
  • 哦,错过了,对不起! 评论 2019年5月13日0:59
16

我做了一些研究,发现原型对象[规范19.1.2.16]ES6课程的不可写的,不可枚举的,不可配置的.

以下是检查方法:

类F{}console.log(Object.getOwnPropertyDescriptor(F,‘prototype’));//{“value”:{},“writable”:false,“enumerable”:false,“configurable”:false

默认情况下,常规函数是可写的,不可枚举的,不可配置的.

函数G(){}console.log(Object.getOwnPropertyDescriptor(G,‘prototype’));//{“值”:{},“可写”:true,“枚举”:false,“可配置”:false}

ES6提琴:网址:http://www.es6fiddle.net/i7d0eyih/

因此,ES6类描述符的这些属性总是设置为false,如果您尝试定义描述符,则会抛出错误。

//引发错误Object.defineProperty(F,“原型”{可写:true});

然而,使用常规函数,您仍然可以定义这些描述符。

//工程Object.defineProperty(G,“原型”{可写:false});

在常规函数上修改描述符并不常见,所以您可以使用它来检查它是否是类,但这当然不是一个真正的解决方案。

@alexpods将函数字符串化并检查class关键字的方法可能是目前最好的解决方案。

7
  • 1
    有趣的解决方案,但这并不适用于所有情况,因此这不是一个真正的解决方案。 评论 2015年3月17日8:09
  • 1
    是的,我提到过,但离这更近了一步。
    – 米格尔
    评论 2015年3月17日8:10
  • 1
    你正在尝试解决一个语言功能,伙计们。。。JS中没有类,即使它们引入了关键字类。
    – 羽绒被
    评论 2015年3月17日8:19
  • @我不同意。虽然ES6中引入的类当然不是其他语言的类,实际上它们只是重用了ECMAScript已有的特性,但通过类语法创建的东西的语义与其他方法不同。因此,这是一个独特的特性,不能称为“JS类”。
    – 蒙卡德
    评论 2015年3月17日8:22
  • @穆格斯,我认为你的引用是错误的。F.原型在计算类主体时设置,如下所述:peoples.mozilla.org/~jorendorff/…; 的属性描述符对象.原型是完全不同的东西(尽管它有时会影响上面链接的算法)。这个算法太复杂了,我很快就看不出你对F.原型总是不可枚举、不可写等。但如果有人想深入研究,那就应该进行检查。
    – 伊桑
    评论 2015年10月19日5:53
15

跑了一些性能基准关于本主题中提到的不同方法,以下是概述:


Native Class-Props方法(在大型示例中速度最快为56倍,在小型示例中速度为15倍):

函数isNativeClass(thing){return typeof thing==='function'&&thing.hasOwnProperty('prototype')&&!thing.hasOwnProperty(“论据”)}

这是可行的,因为以下是正确的:

>Object.getOwnPropertyNames(A类{})[“长度”,“名称”,“原型”]>Object.getOwnPropertyNames(类A{构造函数(A,b){}})[“长度”,“名称”,“原型”]>getOwnPropertyNames(类A{构造函数(A,b){}A(b,c){}})[“长度”,“名称”,“原型”]>Object.getOwnPropertyNames(函数(){})[“length”,“name”,“arguments”,“caller”,“prototype”]>Object.getOwnPropertyNames(()=>{})>[“长度”,“名称”]

原生类-字符串方法(比regex方法快约10%):

/***是ES6+级*@param{any}值*@返回{boolean}*/函数isNativeClass(value/*:mixed*/)/*:boolean*/{return typeof value===“函数”&&value.toString().indexOf('class')===0}

这也可用于确定常规类别:

//字符位置const INDEX_OF_FUNCTION_NAME=9//“函数X”,X位于索引9const FIRST_UPPERCASE_INDEX_IN_ASCII=65//A位于ASCII索引65处const LAST_UPPERCASE_INDEX_IN_ASCII=90//Z位于ASCII索引90处/***是常规类*查找首字母大写的函数MyClass*第一个字母是第9个字符*如果更改,则还必须更新isClass*@param{any}值*@返回{boolean}*/函数为ConventionalClass(值/*:任意*/)/*:布尔值*/{if(typeof value!==“function”)返回falseconst c=value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME)返回c>=FIRST_UPPERCASE_INDEX_IN_ASCII&&c}

我还建议您查看我的打字检查器包裹其中包括上述的用例-通过是NativeClass方法,is常规课程方法和是Class方法检查这两种类型。

4
  • 你是对的。你不能添加呼叫者论据事实之后类上的属性,因为两者都是受限函数属性试图添加它们会抛出一个类型错误. 评论 2015年12月2日4:58
  • 通过我的设置和Babel 6,函数和类都返回了[“长度”,“名称”,“原型”]。所以这个对我不起作用. 评论 2015年12月2日5:05
  • 这将为箭头函数返回误报,箭头函数也没有论据呼叫者财产。即。(()=>{}).hasOwnProperty(“参数”)返回true。 评论 2017年3月21日3:59
  • 2
    @PierreArnaud是意料之中的事,因为编译时它不再是本机类-所以本机类检查不是你想要的-相反,你可以使用是Class来自打字检查器包裹它检查原生类和常规类。 评论 2017年3月22日1:32

查看由生成的编译代码巴别塔,我认为您无法判断函数是否用作类。当时,JavaScript没有类,每个构造函数都只是一个函数。今天的JavaScript类关键字并没有引入“类”的新概念,而是一种语法糖。

ES6代码:

//欧洲标准6A类{}

ES5生成人巴别塔:

//欧洲标准5“使用严格”;function_classCallCheck(instance,Constructor){if(!(instance-instanceof-Constructor)){throw new TypeError(“无法将类作为函数调用”);}}var A=函数A(){_类调用检查(this,A);};

当然,如果您喜欢编码约定,可以解析函数(类),并检查其名称是否以大写字母开头。

函数isClass(fn){返回类型fn===“函数”&&/^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);}

编辑:

已经支持class关键字的浏览器可以在解析时使用它。否则,你会被大写字母1卡住。

编辑:

正如巴鲁普顿指出的那样,巴别尔产生了函数_class(){}用于匿名类。基于此改进了正则表达式。

编辑:

补充_默认到正则表达式,检测类,如导出默认类{}

警告

BabelJS正在大量开发中,无法保证在这些情况下不会更改默认函数名。真的,你不应该依赖这个。

4
  • 这对编译到Babel的匿名类仍然有效吗?因此,匿名函数被编译为函数类(){}-源代码函数a(){返回类{}&&类{}},可以相应地修改正则表达式。 评论 2015年8月26日22:02
  • 1
    更新了babel匿名类的regex:返回类型fn===“函数”&&/^(?:class | function(?:[A-Z]| _class))/.test(fn) 评论 2015年8月26日22:08
  • Hrmm,匿名类与导出默认类{},也许只是检查一下_类调用检查是一个更好的选择。 评论 2015年8月26日22:14
  • 我想检查一下_类调用检查在整个函数中是多余的,并且具有边际效应。检查姓名的大写首字母是这里的薄弱环节 评论 2015年8月26日22:19
2

您可以使用new.target来确定它是由ES6类函数实例化还是由函数构造函数实例化

类人员1{建造师(姓名){this.name=名称;console.log(new.target)//=>//=>[类:Person1]}}函数Person2(){this.name='cc'console.log(new.target)//=>[函数:Person2]}
0

虽然它不是直接相关的,但是如果类、构造函数或函数是由您生成的,并且您想知道是否应该调用函数或使用new关键字实例化对象,那么可以通过在构造函数或类的原型中添加自定义标志来实现。您当然可以使用其他答案中提到的方法(例如toString(字符串)). 然而,如果您的代码是使用babel实现的,那么这肯定是一个问题。

为了更简单,您可以尝试以下代码-

Foo类{构造函数(){this.someProp=“值”;}}Foo.prototype.isClass=true;

或者如果使用构造函数-

函数Foo(){this.someProp=“值”;}Foo.prototype.isClass=true;

您可以通过检查prototype属性来检查它是否为类。

if(Foo.prototype.isClass){//这是一门课}

如果类或函数不是由您创建的,那么这个方法显然不起作用。反应.js使用此方法检查反应组件是类组件或功能组件。这个答案来自丹·阿布拉莫夫的博客帖子

1
  • @trincot修好了!我试图提供一种方法,但没有考虑这些场景。 评论 2019年3月7日6:56
0

我知道这是一个很古老的问题,但最近我一直在想同样的事情,特别是因为如果你试图将函数用作类组件,React会抛出一个错误(例如,如果你设置babel将类转换为函数,就像我做的那样)。我坐了一会儿,想出了这个:

函数为Class(f){返回类型f===“函数”&&()=>{try{f();return false}catch{return true})();}isClass(函数(){})//isClass(()=>{})//isClass(类{})//真的

这是基于这样一个事实,即ES6类需要使用新的关键字,如果缺少,则抛出错误。该函数首先检查传递的参数的类型是否为函数,这适用于类和函数。然后它尝试在没有新的关键字,只能由普通函数完成的内容。然而,请记住,如果函数抛出错误,如果它检查是否正在通过类似的方法用new调用它,那么这可能会被愚弄if(!(此ConstructorName实例)引发:

isClass(函数Constructor(){if(!(此构造函数实例))抛出新的Error();}); // 真的

函数为Class(f){返回类型f===“函数”&&()=>{try{f();return false}catch{return true})();}console.log(isClass(function(){}))//console.log(isClass(()=>{}))//console.log(isClass(类{}))//真的console.log(isClass(函数Constructor(){if(!(此构造函数实例))抛出新的Error();})); // 真的

0
函数isClass(obj){return“class”===obj.toString().split(“”)[0];}
1
-1

如果我正确理解ES6使用具有与您打字相同的效果

var Foo=函数(){}var Bar=函数(){Foo.call(this);}Bar.protype=Object.create(Foo.prototype);Bar.prototype.constructor=Bar;

键入时出现语法错误我的类()没有关键字new只是为了防止对象使用的变量污染全局空间。

var MyClass=function(){this.$=“我的私人美元”;返回这个;}

如果你有

//$===jqueryvar myObject=新MyClass();//$===仍然是jquery//myObject===全局对象

但如果你这么做了

var myObject=我的类();//$===“我的私人美元”

因为在构造函数中作为函数调用是指全局对象,但在使用关键字调用时新的Javascript首先创建新的空对象,然后对其调用构造函数。

4
  • 2
    是的,原因之一可能就是这样。然而,如何检查它是否是一个类或函数,就像问题所问的那样?
    – 蒙卡德
    评论 2015年3月17日7:48
  • 正如我所说,使用class执行上述步骤。所以没有类的类型。它是函数的类型,因为它是函数。在运行时检查是否使用“new”或nor并不是一种非常糟糕的做法。太糟糕了,ES6创建者决定在这种情况下抛出语法错误。
    – 羽绒被
    评论 2015年3月17日7:58
  • 如果你试图调用函数,而不是检查它,ES6创建者决定抛出一个语法错误。假设你正在构建一个库,它将接收函数对象并决定执行它们或将它们存储在某个类数据库中(只是一个随机想法)。你无法知道你现在收到了什么样的东西。那么,在不执行的情况下,如何检查它是否可以执行?
    – 蒙卡德
    评论 2015年3月17日8:02
  • 任何函数都可以作为构造函数调用,这就是javascript的工作方式。因此,如果您正在构建类库,其中的任何函数都与ES6“class”一样好。
    – 羽绒被
    评论 2015年3月17日8:13

您的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.