从ES6模块中导入函数表达式和函数声明有什么区别?

15
据我了解(见16.3.2.1节), ES6允许不同的函数/类导出操作语法。区别在于导出的函数是否需要被视为函数声明进行导入,如果需要,则编写:export default function () {} // (a),或作为函数表达式: export default (function () {}); // (b)
可能相关的附注:我读到过导入是被提升的,但我不太确定在这种情况下它的含义是什么。
以此示例为例: import foo from 'my_module'; // (c) 据我理解,上述语句将我的导出函数保存在一个foo变量中。该变量是否被提升,或者何时提升?
最重要的是,在my_module使用(a)和使用(b)导出函数时,有什么区别(就设置foo而言)?

您的问题是关于导出还是导入? - user663031
关于导入的内容 - c10b10
1个回答

24
您的问题有些复杂,但我会尽力解释一切。
首先,让我们了解模块的工作原理。一个模块有一组导出名称,每个名称都引用该模块中的本地变量。导出的名称不需要与本地绑定的名称相同。其中一个导出名称可以是“default”,针对只导出单个内容的情况,有专门的语法(在导入和导出时都适用)。

I read that imports are hoisted, but I'm not really sure what that means in this context:

import { foo } from 'my_module';
是的,导入声明是被提升的。与varfunction(实际上像其他所有声明一样)类似,标识符foo从一开始就可用,在模块中的任何语句执行之前。事实上,该绑定甚至在已声明的variables之前创建。
不同之处在于它们的初始化方式:
  • var使用undefined进行初始化
  • functionfunction*使用函数对象进行初始化
  • letconstclass未初始化
  • imported bindings甚至没有真正初始化,它们只是指向导入模块中所引用的本地变量的指针
  • imported modulesimport * as …)使用模块对象进行初始化(其属性也是这样的指针)

什么时候将foo设置为引用我的导出函数?

简短的回答:在其他所有事情之前。

长的回答:这并没有确切的规定。它是指你期望保存函数的导入模块中的局部变量。当它不是const时,局部变量可能会改变,但我们通常不会期望这种情况发生。通常情况下,它已经包含了那个函数,因为在导入它的模块被完全评估之前,已经对导入的模块进行了完全评估。所以如果你担心var functionName = function() {} vs function functionName() {}存在问题,你可能会感到宽慰——其实并没有问题。

现在回到你的标题问题:

在ES6模块中,导出函数表达式和函数声明有什么区别?

没有什么特别的,这两个方面实际上并没有太多关系:

  • export声明将导出名称链接到模块范围内的局部变量
  • 模块范围内的所有变量都像往常一样被提升
  • 函数声明与分配一个函数表达式的变量声明初始化方式不同,像往常一样

当然,在任何情况下都没有不使用更加声明式的函数声明的好理由;这在ES6模块之前并没有什么不同。如果有的话,甚至可能会有更少的理由使用函数表达式,因为所有内容都可以通过声明来涵盖:

/* for named exports */
export function foo() {…}

// or
function foo() {…}
export {foo as foo}

/* for default exports */
export default function foo() {…}

// or
function foo() {…}
export {foo as default}

// or
function foo() {…}
export default foo;

// or
export default function() {…}

好的,最后两个默认导出声明与前两个有些不同。与导出名称default关联的本地标识符不是foo,而是* default * - 它不能被重新分配。这在最后一种情况下是有意义的(其中没有名称foo),但在倒数第二种情况下,您应该注意到foo实际上只是本地别名,而不是导出的变量本身。我建议不要使用这种模式。

哦,在您询问之前:是的,最后一个默认导出确实也是一个函数声明,而不是表达式。一个匿名函数声明。这是ES6的新功能:-)

那么export default function () {}export default (function () {});之间的区别是什么?

它们在所有目的上基本相同。它们是匿名函数,具有“default”名称属性,由特殊的“*default*”绑定持有,该绑定指向匿名导出值所指向的导出名称“default”。
它们唯一的区别是变量提升-声明将在模块顶部实例化其函数,表达式仅在执行模块代码达到语句时才进行评估。但是,鉴于没有可访问名称的变量,除了一个非常奇怪的特殊情况之外,这种行为是不可观察的:导入自身的模块。嗯,是的。
import def from "myself";
def(); // works and logs the message
export default function() {
    console.log("I did it!");
}

import def from "myself";
def(); // throws a TypeError about `def` not being a function
export default (function() {
    console.log("I tried!");
});

你真的不应该这样做。如果你想在模块中使用导出的函数,就在声明中给它一个名称。
“那么为什么会有这两种语法呢?”
这种情况确实会发生。规范允许这样做是因为它没有制定额外的例外来禁止某些荒谬的事情。它并不是用来这样使用的。在这种情况下,规范甚至明确禁止在“export default”语句中使用“function”和“class”表达式,并将它们视为声明。通过使用分组运算符,你找到了一个漏洞。干得好。不要滥用它。

你对提升的解释恰好是我正在寻找的,谢谢。不过似乎我没有表述清楚我的核心问题。我编辑了主帖子以使其更清晰明了。 - c10b10
@c10b10:啊,好的。我稍后会编辑。目前来说:差异是绝对最小化的,但你肯定应该使用声明。 - Bergi
可能你的意思是 import * as def from "myself";,在这种情况下它不起作用。 - The Reason
@The:babel 似乎完全不识别匿名函数声明,而是将其视为函数表达式。这意味着两者都不应该起作用。当然,这可能会受到模块加载器/捆绑器以及其实现如何处理递归依赖关系的影响。你能把转译结果粘贴在某个地方吗?不,我确实是指 import def from …; def();,尽管 import * as x from …; x.default() 是等效的。 - Bergi
@Bergi 哦,现在我明白了,对混淆感到抱歉。 - The Reason
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接