27

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

Harmony 维基指出:

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

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

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

但测试也将返回true任何内置函数,例如setTimeoutor Math.min

如果您获得源代码并检查它是否是 Firefox,它有点工作"native code",但它似乎不太可靠也不可移植(其他浏览器实现,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;
4

9 回答 9

13

信不信由你...

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

显然,我们无法针对您提到的两个条件中的任何一个进行测试 - 缺少原型属性和缺少as 这可能会导致主机对象或缺少( ,等)[[Construct]]的内置对象出现误报[[Construct]]Math.floorJSON.parse

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

现在,我一直建议不要使用Function.prototype.toString(所谓的函数反编译),因为它依赖于实现并且在历史上不可靠(更多细节在 Javascript 中的函数反编译状态中)。

但是 ES6 实际上试图在(至少)内置和“用户创建的”(因为缺乏更好的术语)函数被表示的方式上强制执行规则。

  1. 如果 Type(func) 是 Object 并且是内置函数对象或 具有 [[ECMAScriptCode]] 内部 slot,则

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

...

toString 表示要求:

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

  • 表示字符串中空格、行终止符和分号的使用和放置取决于实现。

  • 如果对象是使用 ECMAScript 代码定义的,并且返回的字符串表示不是 MethodDefinition 或 GeneratorMethod 的形式,那么表示必须是这样的:如果对字符串进行求值,则在与所使用的词法上下文等效的词法上下文中使用 eval要创建原始对象,它将产生一个新的功能等效的对象。在这种情况下,返回的源代码不得随意提及原始函数的源代码未随意提及的任何变量,即使这些“额外”名称最初在范围内。

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

我强调了相关的块。

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

这意味着它们必须符合ArrowFunction 语法

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

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

您显然需要确保 "=>" 遵循 ArrowParameters 而不仅仅是 FunctionBody 中存在的东西:

function f() { return "=>" }

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

于 2015-01-30T10:55:45.623 回答
9

更新

最初,我使用正则表达式实现了 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*\())/.test(fn.toString());

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

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

问题?

如果您有任何问题,请给我留言,我会制定一个修改后的解决方案。但是,请务必在答案下发表评论。我不监视此页面,因此如果您说某些内容不能作为单独的答案,我将看不到它。

于 2019-08-27T20:49:21.533 回答
5

我为 Node 编写的,应该也可以在 Chrome 中使用。

检测到“Boundness”(显然,仅在 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;
于 2016-08-08T13:48:46.307 回答
1

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

例如见

8.6.2 对象内部属性和方法

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

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

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

请注意,我引用的是 ES5 规范。ES6 仍在进行修订,本机和宿主对象现在称为外来对象。但规范几乎是一样的。它提供了一些即使它们也必须满足的不变量,但除此之外仅说明它们可能会或可能不会满足所有可选行为。

于 2015-01-30T04:32:11.883 回答
1

基于mozilla.org 上的文档并考虑到该页面Use of the new operator的副作用,我们可以尝试执行以下操作:

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

于 2020-05-28T20:17:46.387 回答
0

据我所知,这应该有效:

使用 'function' 转换为 String START 时的所有非箭头函数。箭头函数没有。

尝试测试“=>”的存在不是测试函数是否为箭头的可靠方法,因为任何非箭头函数都可以在其中包含箭头函数,因此“=>”可以出现在他们的源代码。

于 2017-09-25T15:44:01.543 回答
0

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
)

于 2019-09-07T11:58:39.060 回答
0

我找不到误报。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
于 2020-06-23T20:00:19.410 回答
0

验证箭头功能的现代解决方案:

const isArrowFunction = obj => typeof obj === 'function' && obj.prototype === undefined;
isArrowFunction(() => 10); // true
isArrowFunction(function() {}); // false
于 2021-01-04T21:46:26.903 回答