JavaScript ES6: Тест для функции стрелки, встроенной функции, регулярной функции?

Есть ли элегантный способ рассказать о функциях тонкой стрелки Harmony, помимо встроенных функций и?

Harmony wiki утверждает, что:

Функции стрелок похожи на встроенные функции в том, что отсутствует .prototype и любой внутренний метод [[Construct]]. Итак, new (() = > {}) выдает TypeError, но в противном случае стрелки похожи на функции

Это означает, что вы можете проверить функции стрелок, например:

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

Но тест также вернет true для любой встроенной функции, например. setTimeout или Math.min.

Это работает в Firefox, если вы получаете исходный код и проверяете, есть ли он "native code", но он не кажется надежным и переносимым (другие версии браузера, NodeJS/iojs):

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

Небольшой проект GitHub node-is-arrow-function полагается на проверки RegExp на исходный код функции, который не является таким аккуратным.

edit: Я дал парсер 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;

Ответ 1

Верьте или нет...

Тестирование наличия "= > " в строчном представлении функции, вероятно, является самым надежным способом (но не 100%).

Очевидно, мы не можем протестировать ни одно из двух упомянутых вами условий: отсутствие свойства прототипа и отсутствие [[Construct]], поскольку это может дать ложные срабатывания с объектами хоста или встроенными, которые не имеют [[Construct]] (Math.floor, JSON.parse и т.д.)

Мы могли бы использовать старый добрый Function.prototype.toString, чтобы проверить, содержит ли представление функции "= > " .

Теперь я всегда рекомендовал против использовать Function.prototype.toString (так называемая декомпиляция функций) из-за ее зависящей от реализации и исторически ненадежной природы (подробности в Состояние декомпиляции функции в Javascript).

Но ES6 фактически пытается обеспечить соблюдение правил по пути (по крайней мере) встроенного и "созданного пользователями" (из-за отсутствия лучший термин).

  1. Если Type (func) является Object и является либо встроенным функциональным объектом, либо имеет внутренний слот [[ECMAScriptCode]], затем

    а. Вернуть представление, зависящее от реализации String исходного кода func. Представление должно соответствовать приведенным ниже правилам.

...

toString Требования к представлению:

  • Строковое представление должно иметь синтаксис FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpession, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition или GeneratorMethod в зависимости от фактического характеристики объекта.

  • Использование и размещение пробелов, ограничителей строк и точек с запятой в представлении String зависит от реализации.

  • Если объект был определен с использованием кода ECMAScript, а возвращаемое строковое представление не имеет форму MethodDefinition или GeneratorMethod тогда представление должно быть таким, что если строка оценивается с использованием eval в лексическом контексте, который эквивалентно лексическому контексту, используемому для создания исходного объекта, это приведет к созданию нового функционально эквивалентного объекта. В таком случае возвращенный исходный код не должен свободно упоминать любые переменные, которые не были свободно упомянуты исходным кодом исходных функций, даже если эти "дополнительные" имена были первоначально в области.

  • Если реализация не может создать строку исходного кода, которая соответствует этим критериям, тогда она должна вернуть строку, для которой eval будет бросать исключение SyntaxError.

Я выделил соответствующие куски.

Функции стрелок имеют внутренний [[ECMAScriptCode]] (который вы можете отслеживать с 14.2.17 - оценка функции стрелки - в FunctionCreate to FunctionInitialize).

Это означает, что они должны соответствовать синтаксису ArrowFunction:

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

.., что означает, что они должны иметь вывод = > в Function.prototype.toString.

Вам, очевидно, нужно будет убедиться, что "= > " следует за ArrowParameters и не только что-то присутствует в FunctionBody:

function f() { return "=>" }

Что касается надежности - помните, что это поведение/не может поддерживаться никакими/всеми двигателями на данный момент и что представление объектов хоста может лежать (несмотря на усилия по настройке) по каким-либо причинам.

Ответ 2

Я написал это для Node, должен работать и в Chrome.

"Ограниченность" обнаружена (по-видимому, только на 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;

Ответ 3

Насколько я могу судить, это должно работать:

Все функции без стрелок при преобразовании для String START с функцией. Функции стрелки не работают.

Попытка проверить наличие '= > ' не является надежным способом проверки того, функция - стрелка или нет, потому что любая функция, отличная от стрелки, может содержать стрелки внутри них и, следовательно, '= > ' может присутствовать в исходном коде.

Ответ 4

Кангакс сделал великую мысль. Если бы вы хотели совершенства, вы бы использовали библиотеку синтаксического анализатора 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

Logic

Это предполагает, что все функциональные блоки, не являющиеся стрелками, должны быть окружены {}. Я не думаю, что существует среда, которая могла бы оказать в противном случае, но, пожалуйста, дайте мне знать, если я ошибаюсь.

Примечание: регулярное выражение также написано в предположении, что функция => всегда будет в первой строке. Это может быть легко изменено, но опять же, я не могу представить, чтобы что-нибудь отображало новую строку до этого.

Как это работает?

  • ^ - начинать с начала строки
  • [ ^ { ] +? - просмотрите все, что следует до следующего шаблона (=>), но если вы обнаружите { первым, это не совпадение
  • => - найдено => до {, поэтому совпадение

Проблема?

Оставьте комментарий, если вы найдете случай, когда он не работает, и я посмотрю, сможем ли мы принять меры.

Ответ 5

ECMAScript отказывается от многих своих гарантий для объектов хоста и, следовательно, по расширению хост-функций. Это делает свойства доступными через отражение в основном зависящими от реализации с небольшими гарантиями согласованности, по крайней мере, по сравнению с спецификацией ecmascript, спецификации W3C могут быть более конкретными для объектов хоста браузера.

например. см.

8.6.2 Внутренние свойства и методы объекта

В таблице 9 приведены внутренние свойства, используемые этой спецификацией, которые применимы только к некоторым объектам ECMAScript. [...] Объекты хоста могут поддерживать эти внутренние свойства с любым зависящим от реализации поведением, если оно согласуется с конкретными ограничениями на объекты хоста, указанными в этом документе.

Таким образом, встроенные функции могут быть вызываемыми, но не иметь прототипа (т.е. не наследуются от функции). Или они могут иметь один.

Спектр говорит, что они могут вести себя по-другому. Но они также могут реализовать все стандартное поведение, делая их неотличимыми от обычных функций.

Обратите внимание, что я цитирую спецификацию ES5. ES6 по-прежнему подвергается ревизии, объекты native и host теперь называются экзотическими объектами. Но спецификация в значительной степени говорит то же самое. Он предоставляет некоторые инварианты, которые даже они должны выполнять, но в противном случае только говорят, что они могут или не могут выполнять все необязательные поведения.

Ответ 6

Решение 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
)

Ответ 7

  • Преобразование функции в строку 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);