JavaScript ES6:测试箭头功能,内置函数,常规函数?

时间:2015-01-29 18:45:34

标签: javascript function ecmascript-6 ecmascript-harmony arrow-functions

除了常规功能内置功能之外,还有一种优雅的方式可以告诉Harmony的纤细箭头功能吗?

Harmony wiki表示:

  

箭头函数就像内置函数一样,缺少.prototype 和任何[[Construct]]内部方法。所以new(()=> {})抛出TypeError,否则箭头就像函数

这意味着,您可以测试箭头功能,如:

!(()=>{}).hasOwnProperty("prototype") // true
!(function(){}).hasOwnProperty("prototype") // false

但是对于任何内置函数,测试也会返回true,例如setTimeoutMath.min

如果您获得源代码并检查它是否为"native code",它在Firefox中是有效的,但它看起来不太可靠也不可移植(其他浏览器实现,NodeJS / iojs):

setTimeout.toSource().indexOf("[native code]") > -1

小的GitHub项目node-is-arrow-function依赖于函数源代码的RegExp检查,这不是那么整洁。

编辑:我尝试了JavaScript解析器acorn,它似乎工作得很好 - 即使它太过分了。

acorn = require("./acorn");

function fn_sample(a,b){
    c = (d,e) => d-e;
    f = c(--a, b) * (b, a);
    return f;
}

function test(fn){
    fn = fn || fn_sample;
    try {
        acorn.parse("(" + fn.toString() + ")", {
            ecmaVersion: 6,
            onToken: function(token){
                if(typeof token.type == "object" && token.type.type == "=>"){
                    console.log("ArrowFunction found", token);
                }
            }
        });
    } catch(e) {
        console.log("Error, possibly caused by [native code]");
        console.log(e.message);
    }
}

exports.test = test;

12 个答案:

答案 0 :(得分:7)

信不信由你......

测试是否存在“=>”函数的字符串表示可能是最可靠的方式(但不是100%)。

显然我们无法测试你提到的两个条件中的任何一个 - 缺少原型属性和缺少[[Construct]],因为这可能会导致主机对象或缺少{{1}的内置对象的误报}([[Construct]]Math.floor等。)

但是,我们可以使用好的旧JSON.parse来检查函数表示是否包含“=>”。

现在,我一直建议使用Function.prototype.toString(所谓的函数反编译反对,因为它依赖于实现且历史上不可靠(更多) State of function decompilation in Javascript)中的详细信息。

但ES6实际上tries to enforce rules在路上(至少)内置并且“用户创建”(缺少更好的术语)功能被表示出来。

  
      
  1. 如果Type(func)是Object并且是内置函数对象或   有一个[[ECMAScriptCode]]内部插槽,然后

         

    一个。返回func的依赖于实现的String源代码表示。表示必须符合以下规则

  2.         

    ...

         

    toString表示要求:

         
        
    • 字符串表示必须具有FunctionDeclaration FunctionExpression,GeneratorDeclaration的语法,   GeneratorExpession,ClassDeclaration,ClassExpression, ArrowFunction ,   MethodDefinition或GeneratorMethod取决于实际   对象的特征。

    •   
    • 在表示字符串中使用和放置空格,行终止符和分号   实现相关的。

    •   
    • 如果使用ECMAScript代码定义了对象,并且返回的字符串表示形式不是MethodDefinition或   那么GeneratorMethod的表示必须是这样的   在词汇上下文中使用eval来评估字符串   相当于用于创建原始对象的词法上下文,   它将导致一个新的功能相当的对象。在这种情况下   返回的源代码不能自由提及任何变量   原始函数的源代码没有自由提及,甚至   如果这些“额外”名称最初属于范围。

    •   
    • 如果实现无法生成满足这些条件的源代码字符串,那么它必须返回一个eval将抛出的字符串   一个SyntaxError异常。

    •   

我强调了相关的块。

箭头函数具有内部Function.prototype.toString(您可以从14.2.17 - 箭头函数的评估 - 到 FunctionCreate FunctionInitialize )进行跟踪。

这意味着他们必须符合ArrowFunction syntax

[[ECMAScriptCode]]

..这意味着他们必须拥有=>在ArrowFunction[In, Yield] : ArrowParameters[?Yield] [no LineTerminator here] => ConciseBody[?In] 的输出中。

你显然需要确保“=>”遵循ArrowParameters并且不仅仅是FunctionBody中存在的东西:

Function.prototype.toString

至于可靠性 - 请记住,目前任何/所有引擎都不支持此行为,并且无论出于何种原因,主机对象的表示都可能存在(尽管规范有所作为)。

答案 1 :(得分:3)

我为Node写了这个,也应该在Chrome中工作。

检测到“界限”(apparently, only on ES6)并报告为native && bound。这可能是也可能不是问题,具体取决于您使用该信息的内容。

const flags = {
  function: f instanceof Function,
  name: undefined,
  native: false,
  bound: false,
  plain: false,
  arrow: false
};

if (flags.function) {
  flags.name = f.name || '(anonymous)';
  flags.native = f.toString().trim().endsWith('() { [native code] }');
  flags.bound = flags.native && flags.name.startsWith('bound ');
  flags.plain = !flags.native && f.hasOwnProperty('prototype');
  flags.arrow = !(flags.native || flags.plain);
}

return flags;

答案 2 :(得分:3)

Kangax提出了一个很重要的观点。如果您想要完美,则可以使用AST解析器库来确定函数类型,但这对您中的许多人来说可能是过大的选择。

为此,以下是使用正则表达式实现的方法:

/** Check if function is Arrow Function */
const isArrowFn = (fn) => (typeof fn === 'function') && /^[^{]+?=>/.test(fn.toString());

/* Demo */
const fn = () => {};
const fn2 = function () { return () => 4 }

isArrowFn(fn)  // True
isArrowFn(fn2) // False

逻辑

这假定所有非箭头功能块必须用{}包围。我认为不存在会导致这种情况的环境,但是如果我做错了,请告诉我。

注意:正则表达式的编写还假定函数的=>将始终位于第一行。可以很容易地更改它,但是同样,我无法想象在它之前会出现换行符。

它如何工作?

  • ^ -从行首开始
  • [ ^ {] +?-查看所有内容,直到出现下一个模式(=>),但如果找到 {首先,这不是比赛
  • => -在 {之前找到 => ,因此是匹配项

问题?

如果发现无法使用的情况,请发表评论,我会看看我们是否可以容纳。

答案 3 :(得分:2)

据我所知,这应该有效:

转换时的所有非箭头功能 使用'function'字符串START。 箭头功能没有。

尝试测试是否存在'=>' 不是一种可靠的测试方法 函数是否为箭头,因为 任何非箭头功能都可以包含 箭头 - 在它们内部的功能,因此 '=>'可以出现在他们的源代码中。

答案 4 :(得分:1)

ECMAScript放弃了对主机对象的许多保证,因此通过扩展来放弃主机功能。 这使得属性可以通过反射访问,主要是依赖于实现,几乎不保证一致性,至少就ecmascript规范而言,W3C规范可能更具体的浏览器主机对象。

E.g。见

8.6.2 Object Internal Properties and Methods

  

表9总结了本规范使用的内部属性,这些属性仅适用于某些ECMAScript对象。 [...]   主机对象可能支持具有任何依赖于实现的行为的这些内部属性,只要它与本文档中声明的特定主机对象限制一致。

所以内置函数可能是可调用的,但没有原型(即不从函数继承)。或者他们可以有一个。

规范说它们可能表现不同。但它们也可能实现所有标准行为,使它们与正常功能无法区分。

请注意,我引用了ES5规范。 ES6仍在进行修订,本机和主机对象现在称为外来对象。但规范几乎也是这样说的。它提供some invariants即使他们必须履行,但只是说他们可能会或可能不会履行所有可选行为。

答案 5 :(得分:1)

Ron S的解决方案效果很好,但可以检测到误报:

/** Check if function is Arrow Function */
const isArrowFn = (fn) => (typeof fn === 'function') && /^[^{]+?=>/.test(fn.toString());

/* False positive */
const fn = function (callback = () => null) { return 'foo' }

console.log(
  isArrowFn(fn)  // true
)

答案 6 :(得分:1)

我找不到误报。对罗恩·S(Ron S)的做法进行了小改动:

const isArrowFn = f => typeof f === 'function' && (/^([^{=]+|\(.*\)\s*)?=>/).test(f.toString().replace(/\s/, ''))

const obj = {
    f1: () => {},
    f2 () {}
}

isArrowFn(obj.f1) // true
isArrowFn(() => {}) // true
isArrowFn((x = () => {}) => {}) // true

isArrowFn(obj.f2) // false
isArrowFn(function () {}) // false
isArrowFn(function (x = () => {}) {}) // false
isArrowFn(function () { return () => {} }) // false
isArrowFn(Math.random) // false

答案 7 :(得分:0)

尝试了上述所有解决方案并阅读了@kangax等人的精彩文章之后,我涵盖所有用例的唯一方法是构建自己的解析器,名为check-is-arrow-function(灵感来自我的名字, ?)。到目前为止,它已经处理了我抛出的所有问题……包括:

() => {}
()=>{}
() => 1
(x) => x
(x) => (y) => x + y
(callback = () => 4) => callback
(callback=()=>4)=>callback
({prop}) => prop
(callback = () => "(yo") => callback
(callback = () => "\"(yo") => callback
(() => {})
r => r
...and more

易于使用且轻巧:

checkIsArrowFunction(r => r); // true
checkIsArrowFunction(function a () { return () => 0 }); // false

希望它也可以帮助其他人!

答案 8 :(得分:0)

基于documentation基于mozilla.org,并考虑了Use of the new operatorthat page的副作用,我们可以尝试执行以下操作:

function isArrow (fn) {
  if (typeof fn !== 'function') return false
  try {
    new fn()
  } catch(err) {
   if(err.name === 'TypeError' && err.message === 'fn is not a constructor') {
    return true
   }
  }
  return false
}

console.log(isArrow(()=>{})) // true
console.log(isArrow(function () {})) // false
console.log(isArrow({})) // false
console.log(isArrow(1)) // false


let hacky = function () { throw new TypeError('fn is not a constructor') }
console.log(isArrow(hacky)) // unfortunately true

答案 9 :(得分:0)

验证箭头函数的现代解决方案:

const isArrowFunction = obj => typeof obj === 'function' && obj.prototype === undefined;
isArrowFunction(() => 10); // true
isArrowFunction(function() {}); // false

答案 10 :(得分:-1)

  • 将函数转换为字符串toString
  • 删除此字符串的所有空格。
  • 如果")=>"存在且索引大于或等于1 => 99.99%是箭头功能。

        F.toString().replace(/\s+/g, '').indexOf(')=>')>=1
    

DEMO:



var fn1=function(e){
   e.target.value=new Date();
};

var fn2=(a,b)=> a+b;

function isArrow(name,F){
         if(F.toString().replace(/\s+/g, '').indexOf(')=>')>=1){
                 console.log(`${name} is arrow-function`);
         }else{
           console.log(`${name} is classic-function`);
          }
}

isArrow('fn1',fn1);
isArrow('fn2',fn2);




答案 11 :(得分:-1)

JavaScript的箭头功能不具有prototype属性。

function isArrowFunc(fn){
    return typeof(fn)=="function" && fn.prototype === undefined && !(/\{\s*\[native code\]\s*\}/).test(fn.toString());
}

isArrowFunc(()=>{}) // true
isArrowFunc(function(){}) // false
isArrowFunc(123) // false