Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Is there an elegant way to tell Harmony's slim arrow functions apart from regular functions and built-in functions?

The Harmony wiki states that:

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

Which means, you can test for arrow functions like:

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

But the test will also return true for any built-in function, e.g. setTimeout or Math.min.

It sort of works in Firefox if you get the source code and check if it's "native code", but it doesn't seem much reliable nor portable (other browser implementations, NodeJS / iojs):

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

The small GitHub project node-is-arrow-function relies on RegExp-checks against the function source code, which isn't that neat.

edit: I gave the JavaScript parser acorn a try and it seems to work quite okay - even though it's pretty overkill.

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; 
like image 673
CodeManX Avatar asked Jan 29 '15 18:01

CodeManX


People also ask

Should I use arrow function or normal function?

Function expressions are best for object methods. Arrow functions are best for callbacks or methods like map , reduce , or forEach . Use function declarations for functions you'd call by name (because they're hoisted). Use arrow functions for callbacks (because they tend to be terser).

How are arrow functions () => {} different than traditional function expressions?

Unlike regular functions, arrow functions do not have their own this . The value of this inside an arrow function remains the same throughout the lifecycle of the function and is always bound to the value of this in the closest non-arrow parent function.

What does () => mean in JavaScript?

It's a new feature that introduced in ES6 and is called arrow function. The left part denotes the input of a function and the right part the output of that function.

When should you not use arrow functions in ES6?

An arrow function doesn't have its own this value and the arguments object. Therefore, you should not use it as an event handler, a method of an object literal, a prototype method, or when you have a function that uses the arguments object.


2 Answers

Believe it or not...

Testing for presence of "=>" in string representation of a function is likely the most reliable way (but not 100%).

Obviously we can't test against either of 2 conditions you mentioned — lack of prototype property and lack of [[Construct]] as that might give false positives with either host objects or built-in ones that lack [[Construct]] (Math.floor, JSON.parse, etc.)

We could, however, use good old Function.prototype.toString to check if function representation contains "=>".

Now, I've always recommended against using Function.prototype.toString (so-called function decompilation) due to its implementation-dependent and historically unreliable nature (more details in State of function decompilation in Javascript).

But ES6 actually tries to enforce rules on the way (at least) built-in and "user-created" (for the lack of better term) functions are represented.

  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.

I highlighted relevant chunks.

Arrow functions have internal [[ECMAScriptCode]] (which you can track from 14.2.17 — evaluation of arrow function - to FunctionCreate to FunctionInitialize).

This means they must conform to ArrowFunction syntax:

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

..which means they must have => in Function.prototype.toString's output.

You'll obviously need to ensure "=>" follows ArrowParameters and is not just something present in FunctionBody:

function f() { return "=>" } 

As for reliability — remember that this behavior is/might not be supported by any/all engines at the moment and that host objects' representation might lie (despite specs efforts) for whatever reasons.

like image 94
kangax Avatar answered Sep 28 '22 00:09

kangax


Updated

Originally, I implemented Kangax's solution using regex, however, as several pointed out, there were some false positives and gotcha situations, indicating that we need a slightly more thorough approach.

With that in mind, I took a moment to look through the latest ES spec to work out a complete method. In the following exclusionary solution, we detect syntax for all non-arrow functions which have the function JS type. We also ignore comments and line breaks, which accounts for the bulk of the regex.

Provided the JS engine conforms to ES spec, the following should work in all scenarios:

/** 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 

Problem?

If you have any issues, leave me a comment, and I'll work out a revised solution. Be sure to leave a comment under this answer, however. I don't monitor this page, so I won't see it if you say something doesn't work as a separate answer.

like image 35
Ron S. Avatar answered Sep 28 '22 00:09

Ron S.