JavaScript ES6:测试箭头函数、内置函数、正则函数?

JavaScript ES6: Test for arrow function, built-in function, regular function?

是否有一种优雅的方式来区分 Harmony 的细长箭头函数与常规函数 内置函数?

Harmony wiki 指出:

Arrow functions are like built-in functions in that both lack .prototype and any [[Construct]] internal method. So new (() => {}) throws a TypeError but otherwise arrows are like functions

这意味着,您可以测试箭头函数,例如:

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

但是对于任何内置函数,测试也会 return 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;

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

例如见

8.6.2 Object Internal Properties and Methods

The Table 9 summarises the internal properties used by this specification that are only applicable to some ECMAScript objects. [...] Host objects may support these internal properties with any implementation-dependent behaviour as long as it is consistent with the specific host object restrictions stated in this document.

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

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

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

信不信由你...

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

显然,我们无法针对您提到的两种情况中的任何一种进行测试 — 缺少原型 属性 和缺少 [[Construct]],因为这可能会给宿主对象或内置对象带来误报缺少 [[Construct]]Math.floorJSON.parse 等)

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

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

但 ES6 实际上 tries to enforce rules 表示(至少)内置和 "user-created"(由于缺少更好的术语)函数。

  1. If Type(func) is Object and is either a Built-in function object or has an [[ECMAScriptCode]] internal slot, then

    a. Return an implementation-dependent String source code representation of func. The representation must conform to the rules below.

...

toString Representation Requirements:

  • The string representation must have the syntax of a FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpession, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition, or GeneratorMethod depending upon the actual characteristics of the object.

  • The use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.

  • If the object was defined using ECMAScript code and the returned string representation is not in the form of a MethodDefinition or GeneratorMethod then the representation must be such that if the string is evaluated, using eval in a lexical context that is equivalent to the lexical context used to create the original object, it will result in a new functionally equivalent object. In that case the returned source code must not mention freely any variables that were not mentioned freely by the original function’s source code, even if these “extra” names were originally in scope.

  • If the implementation cannot produce a source code string that meets these criteria then it must return a string for which eval will throw a SyntaxError exception.

我突出显示了相关块。

箭头函数具有内部 [[ECMAScriptCode]](您可以从 14.2.17 — 箭头函数的评估 - 到 FunctionCreateFunctionInitialize).

这意味着它们必须符合 ArrowFunction syntax:

ArrowFunction[In, Yield] :
  ArrowParameters[?Yield] [no LineTerminator here] => ConciseBody[?In]

..这意味着他们必须在 Function.prototype.toString 的输出中有 =>。

您显然需要确保“=>”在 ArrowParameters 之后并且不仅仅是 FunctionBody 中存在的内容:

function f() { return "=>" }

至于可靠性 — 请记住,目前 any/all 引擎不支持此行为 is/might 并且宿主对象的表示可能出于任何原因(尽管有规范努力)。

我为 Node 编写的,应该也适用于 Chrome。

检测到

"Boundness" () 并报告为 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;

据我所知这应该有效:

转换后的所有非箭头函数 以 'function ' 开始的字符串。 箭头函数没有。

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

已更新

最初,我使用正则表达式实现了 Kangax 的解决方案,但是,正如一些人指出的那样,存在一些误报和陷阱情况,表明我们需要一种稍微更彻底的方法。

考虑到这一点,我花了点时间浏览了最新的 ES 规范,找出了一个完整的方法。在下面的排他性解决方案中,我们检测所有具有 function JS 类型的非箭头函数的语法。我们还忽略注释和换行符,它们占正则表达式的大部分。

只要 JS 引擎符合 ES 规范,以下内容应该适用于所有场景:

/** Check if function is Arrow Function */
const isArrowFn = (fn) => 
  (typeof fn === 'function') &&
  !/^(?:(?:\/\*[^(?:\*\/)]*\*\/\s*)|(?:\/\/[^\r\n]*))*\s*(?:(?:(?:async\s(?:(?:\/\*[^(?:\*\/)]*\*\/\s*)|(?:\/\/[^\r\n]*))*\s*)?function|class)(?:\s|(?:(?:\/\*[^(?:\*\/)]*\*\/\s*)|(?:\/\/[^\r\n]*))*)|(?:[_$\w][\w0-9_$]*\s*(?:\/\*[^(?:\*\/)]*\*\/\s*)*\s*\()|(?:\[\s*(?:\/\*[^(?:\*\/)]*\*\/\s*)*\s*(?:(?:['][^']+['])|(?:["][^"]+["]))\s*(?:\/\*[^(?:\*\/)]*\*\/\s*)*\s*\]\())/.test(fn.toString());

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

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

有问题吗?

如果您有任何问题,请给我留言,我会制定修改后的解决方案。但是,请务必在 this 答案下发表评论。我不关注这个页面,所以如果你说某些东西不能作为单独的答案,我不会看到它。

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
)

基于 on mozilla.org and taking into account side effects of Use of the new operator and that 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

我找不到误报。对 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

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

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