为啥 babel 将导入的函数调用重写为 (0, fn)(...)?

Posted

技术标签:

【中文标题】为啥 babel 将导入的函数调用重写为 (0, fn)(...)?【英文标题】:Why does babel rewrite imported function call to (0, fn)(...)?为什么 babel 将导入的函数调用重写为 (0, fn)(...)? 【发布时间】:2015-11-23 08:33:16 【问题描述】:

给定一个输入文件

import  a  from 'b';

function x () 
  a()

babel 会编译成

'use strict';

var _b = require('b');

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

但在松散模式下编译时,函数调用输出为_b.a();

我已经对添加逗号运算符的位置进行了一些研究,希望有评论解释它。 负责添加它的代码是here。

【问题讨论】:

他们应该使用_b.a.call() 来明确意图。 @Bergi 我确定他们使用 (0, ) 的原因是为了节省转译代码中的空间。 另见Does the comma operator influence the execution context in javascript? 另见JavaScript syntax (0, fn)(args) 【参考方案1】:

(0, _b.a)() 确保调用函数_b.a 时将this 设置为全局对象(或者如果启用了严格模式,则设置为undefined)。如果您要直接调用_b.a(),则调用_b.a 并将this 设置为_b

(0, _b.a)(); 等价于

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

, 是逗号运算符,参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator)。

【讨论】:

感谢您的链接。跳过了很多次,最后决定找出发生了什么。 @RobW 我认为在文件顶部添加var _a = (0, _b.a) 然后调用_a 在很多情况下会节省更多空间,知道他们没有这样做吗?跨度> @Andy 您的建议可能有副作用,例如当_b.a 是(动态)吸气剂时。 @RobW 我明白了,所以你说的想法是在需要调用函数之前避免潜在的副作用。 请注意,模块总是严格的代码,所以它总是this === undefined,你甚至不需要提及全局对象【参考方案2】:

逗号运算符计算其每个操作数(从左到右) 并返回最后一个操作数的值。

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

那么,让我们看一个例子:

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

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

现在,在foo 方法中,this 等于a(因为foo 附加到a)。所以如果你直接调用a.foo(),它会在控制台中记录false

但是,如果你打电话给(0, a.foo)()。表达式(0, a.foo) 将评估其每个操作数(从左到右)并返回最后一个操作数的值。换句话说,(0, a.foo) 等价于

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

由于这个函数不再附加到任何东西,它的this 是全局对象window。这就是为什么它在调用(0, a.foo)() 时在控制台中记录true

【讨论】:

在开发控制台中运行 console.log(this === window); 不再记录打印。 这让我大吃一惊。这里的关键是逗号运算符“返回最后一个操作数的值”——这里的“值”是函数本身没有包含它的父项——所以 foo 不再存在于 a 中。【参考方案3】:

以这种迂回的方式调用函数:

(throwAwayValueHere, fn)(args);

像这样工作:

计算逗号表达式throwAwayValueHere, fn:逗号运算符计算其第一个操作数,丢弃该值,然后计算其第二个操作数并将该值作为结果。 然后将该值作为函数调用,并传入参数。

以这种方式调用会在两种情况下产生影响:

1。如果函数在对象属性上,例如:

(throwAwayValueHere, obj.fn)(args);

它调用函数没有在函数调用期间将this设置为obj;相反,它被设置为默认值,全局 this 值(浏览器上的 window)或严格模式下的 undefined

例子:

"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)();

这就是 Babel 在那里这样做的原因:在原始代码中,调用只是 a(),它使用默认的 this 值调用 a。即使a_b 的属性,执行(0, _b.a)() 也会做同样的事情。

2。如果函数是eval,它会使其成为一个间接的eval,这意味着它就像在全局范围内一样被评估,而不是eval从一个字符串运行任意代码的默认行为local 范围,使其可以访问所有范围内的变量。

例子:

"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();

【讨论】:

以上是关于为啥 babel 将导入的函数调用重写为 (0, fn)(...)?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在将使用 Diesel 特征的函数重写为特征方法时会出现“溢出评估需求”?

当我从 C# 代码调用导入的 C++ 函数时,为啥会引发 AccessViolationException?

要求没有babel的电子文件

在ASP.NET MVC3中 重写Controller 控制器的构造函数中 其中读取Session时为啥Session为空?

为啥我必须将 babel-presets 放在 .babelrc 和 webpack.config.js 中?

为啥使用默认的 React Google Maps 得到“TypeError:无法将类调用为函数”?