122

Given an input file like

import { a } from 'b';

function x () {
  a()
}

babel will compile it to

'use strict';

var _b = require('b');

function x() {
  (0, _b.a)();
}

but when compiled in loose mode the function call is output as _b.a();

I've done some research into where the comma operator is added in the hope there was a comment explaining it. The code responsible for adding it is here.

Will Smith
  • 1,905
  • 1
  • 14
  • 19
  • 4
    They should've done `_b.a.call()` to make the intention clear. – Bergi Aug 28 '15 at 22:56
  • @Bergi I'm sure the reason they it with (0, ) is to save space in the transpiled code. – Andy Aug 28 '16 at 23:44
  • 2
    see also [Does the comma operator influence the execution context in Javascript?](http://stackoverflow.com/q/36076794/1048572) – Bergi Sep 16 '16 at 12:29
  • 1
    see also [JavaScript syntax (0, fn)(args)](https://stackoverflow.com/q/11541134/1048572) – Bergi Dec 01 '19 at 13:15

3 Answers3

170

(0, _b.a)() ensures that the function _b.a is called with this set to the global object (or if strict mode is enabled, to undefined). If you were to call _b.a() directly, then _b.a is called with this set to _b.

(0, _b.a)(); is equivalent to

0; // Ignore result
var tmp = _b.a;
tmp();

(the , is the comma operator, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator).

Rob W
  • 341,306
  • 83
  • 791
  • 678
  • 4
    thanks for the link. passed over this so many times and finally decided to find out what was happening. – theflowersoftime May 24 '16 at 19:51
  • @RobW I would think adding `var _a = (0, _b.a)` at the top of the file and then calling `_a` would save more space in a lot of cases, any idea they didn't do that? – Andy Aug 28 '16 at 23:47
  • 1
    @Andy Your suggestion may have side effects, e.g. when `_b.a` is a (dynamic) getter. – Rob W Aug 29 '16 at 17:37
  • @RobW I see, so you're saying to idea is to avoid potential side effects until the function needs to be called. – Andy Aug 30 '16 at 03:47
  • Notice that modules are always strict code, so it's always `this === undefined` and you don't even need to mention the global object – Bergi Sep 16 '16 at 12:30
  • if the goal is to access the global this object, would using an arrow function achieve the same result? – Jonathan de M. Jul 23 '20 at 02:26
  • @JonathandeM. In general, no, using an arrow function is NOT equivalent to `(0, fn)()`. Arrow functions have a fixed "this" value, fixed to the `this` of the scope where the arrow function was defined. The `this` of an arrow function will therefore only resolve to the global object it was defined in the global scope of a classic script (opposed to a module script), or if the `this` of the scope where the arrow function was defined already resolved to the global object. – Rob W Jul 26 '20 at 15:16
37

The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.

console.log((1, 2)); // Returns 2 in console
console.log((a = b = 3, c = 4)); // Returns 4 in console

So, let see an example:

var a = {
  foo: function() {
    console.log(this === window);
  }
};

a.foo(); // Returns 'false' in console
(0, a.foo)(); // Returns 'true' in console

Now, in foo method, this is equal to a (because foo is attached to a). So if you call a.foo() directly, it will log false in console.

But, if you were call (0, a.foo)(). The expression (0, a.foo) will evaluate each of its operands (from left to right) and returns the value of the last operand. In other words, (0, a.foo) is equivalent to

function() {
  console.log(this === window);
}

Since this function no longer is attached to anything, its this is the global object window. That's why it log true in console when call (0, a.foo)().

Huong Nguyen
  • 676
  • 7
  • 6
  • running `console.log(this === window);` in dev console does't log print anymore. – kushdilip May 17 '19 at 09:56
  • 8
    This was blowing my mind. The key here is that the Comma operator "returns the value of the last operand" - the "value" here is the function itself absent its containing parent - so foo no longer lives inside of a. – martinp999 Jun 07 '19 at 00:41
  • the function doesn't `return` true, rather logs true to console. Except for that, I liked this explanation more! – Alex Sorokoletov Jan 19 '23 at 18:31
4

Calling a function in this roundabout way:

(throwAwayValueHere, fn)(args);

works like this:

  • The comma expression throwAwayValueHere, fn is evaluated: The comma operator evaluates its first operand, throws away that value, then evaluates its second operand and takes that value as its result.
  • That value is then called as a function, passing in the arguments.

Doing the call that way has an effect in two situations:

1. If the function is on an object property, e.g.:

(throwAwayValueHere, obj.fn)(args);

it calls the function without setting this to obj during the function call; instead, it's set to the default, either the global this value (window on browsers) or undefined in strict mode.

Example:

"use strict";
const obj = {
    value: 42,
    fn: function() {
        console.log(`typeof this = ${typeof this}`);
        if (typeof this === "object") {
            console.log(`this.value = ${this.value}`);
        }
    }
};

// Normal call:
console.log(`obj.fn():`);
obj.fn();

// Indirect call:
console.log(`(0, obj.fn)():`);
(0, obj.fn)();

This is the reason Babel is doing it there: In the original code, the call was simply a(), which calls a with the default this value. Doing (0, _b.a)() does the same thing even though a is a property of _b.

2. If the function is eval, it makes it an indirect eval which means it's evaluated as though at global scope, rather than eval's default behavior of running arbitrary code from a string in local scope, giving it access to all in-scope variables.

Example:

"use strict";

let a = "global a";

function directEval() {
    let a = "local a";
    eval("console.log(`a = ${a}`);");
}

function indirectEval() {
    let a = "local a";
    (0,eval)("console.log(`a = ${a}`);");
}

console.log("direct:");
directEval();
console.log("indirect:");
indirectEval();
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875