Why does calling a function in the Node.js REPL with )( work?

hyde picture hyde · Oct 11, 2013 · Viewed 7.8k times · Source

Why is it possible to call function in JavaScript like this, tested with node.js:

~$ node
> function hi() { console.log("Hello, World!"); };
undefined
> hi
[Function: hi]
> hi()
Hello, World!
undefined
> hi)( // WTF?
Hello, World!
undefined
>

Why does the last call, hi)(, work? Is it bug in node.js, bug in V8 engine, officially undefined behaviour, or actually valid JavaScript for all interpreters?

Answer

Jonathan Lonowski picture Jonathan Lonowski · Oct 11, 2013

It's due to how the REPL evaluates the input, which is ultimately as:

(hi)()

The additional parenthesis are added to force it to be an Expression:

  // First we attempt to eval as expression with parens.
  // This catches '{a : 1}' properly.
  self.eval('(' + evalCmd + ')',
      // ...

The intent is to treat {...} as Object literals/initialisers rather than as a block.

var stmt = '{ "foo": "bar" }';
var expr = '(' + stmt + ')';

console.log(eval(expr)); // Object {foo: "bar"}
console.log(eval(stmt)); // SyntaxError: Unexpected token :

And, as leesei mentioned, this has been changed for 0.11.x, which will just wrap { ... } rather than all input:

  if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) {
    // It's confusing for `{ a : 1 }` to be interpreted as a block
    // statement rather than an object literal.  So, we first try
    // to wrap it in parentheses, so that it will be interpreted as
    // an expression.
    evalCmd = '(' + evalCmd + ')\n';
  } else {
    // otherwise we just append a \n so that it will be either
    // terminated, or continued onto the next expression if it's an
    // unexpected end of input.
    evalCmd = evalCmd + '\n';
  }