JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

04 | export default function() {}:你无法导出一个匿名函数表达式

变量提升
模块的入口定义
模块导入导出记录
导出项的名字
匿名函数
export default
export语句
匿名函数表达式
匿名函数定义
模块的装配过程
模块依赖树
导出名字的处理
其他模块的名字
重命名的名字
导出声明的名字
JavaScript代码的书写/声明
标识符与名字的语义差别
六种声明语法
转译器(例如Babel)
Node.js环境
动态加载
静态装配
思考题
知识补充
匿名函数表达式的执行结果
导出语句的处理逻辑
解析export
导出的内容
模块技术推广障碍
ECMAScript 6模块
ECMAScript 6模块技术

该思维导图由 AI 生成,仅供参考

你好,我是周爱民,欢迎回到我的专栏。
今天我要讲述的内容是从 ECMAScript 6 开始在 JavaScript 中出现的模块技术,这对许多 JavaScript 开发者来说都是比较陌生的。
一方面在于它出现得较晚,另一方面,则是因为在普遍使用的 Node.js 环境带有自己内置的模块加载技术。因此,ECMAScript 6 模块需要通过特定的命令行参数才能开启,它的应用一直以来也就不够广泛。
导致这种现象的根本原因在于 ECMAScript 6 模块是静态装配的,而传统的 Node.js 模块却是动态加载的。因而两种模块的实现效果与处理逻辑都大相径庭,Node.js 无法在短期内提供有效的手段帮助开发者将既有代码迁移到新的模块规范下。
总结起来,确实是这些更为现实的原因阻碍了 ECMAScript 6 模块技术的推广,而非是 ECMAScript 6 模块是否成熟,或者设计得好与不好。
不过即使如此,ECMAScript 6 模块仍然在 JavaScript 的一些大型应用库、包,或者对新规范更友好的项目中得到了不错的运用和不俗的反响,尤其是在使用转译器(例如 Babel)的项目中,开发者通常是首选 ECMAScript 6 模块语法的。
因此 ECMAScript 6 模块也有着非常好的应用环境与前景。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

ECMAScript 6模块技术在JavaScript中的应用受到了一定的限制,主要是因为其静态装配的特性与传统的动态加载模块有较大差异。然而,尽管受到限制,ECMAScript 6模块在一些大型应用库和使用转译器的项目中仍得到了不错的应用和反响。文章详细介绍了ECMAScript 6模块的导出内容和处理逻辑,强调了导出的名字和值的重要性。特别是对于导出值的情况,文章指出了使用`export default <expression>`语法来导出值的特殊性,以及其对模块中所有数据的导出作用。此外,文章还解析了导出语句的处理逻辑,强调了在处理export/import语句的全程中没有表达式被执行的重要结论。文章内容深入浅出,为读者解析了ECMAScript 6模块技术的关键特点和处理逻辑。文章通过讨论导出名字与导出值的差异、匿名函数表达式的执行结果以及关于export的补充知识,深入剖析了ECMAScript 6模块技术的细节。同时,通过思考题引导读者复习关键结论的得出过程,并提出了思考问题,使读者能够更好地理解和应用所学知识。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(38)

  • 最新
  • 精选
  • weineel
    ESModule 根据 import 构建依赖树,所以在代码运行前名字就是已经存在于上下文,然后在运行模块最顶层代码,给名字绑定值,就出现了‘变量提升’的效果。

    作者回复: Yes! 满分答案👍

    2019-11-18
    50
  • 海绵薇薇
    hello 老师好,感谢老师之前的回答,有醍醐灌顶之效。 下面是读完这篇文章和下面评论之后的观点,不知是否有误,望指正,一如既往的感谢:) 1. function a() {} // 函数声明,在六种声明内 function () {} // 报错,以function 开头应该是声明,但是又没有名字 (function() {}) // 函数表达式(这是一个正真的匿名函数(function() {}).name 为 “”),即使是具名函数(function a() {}),当前作用域也找不到a,因为这不是声明 var a = function() {} // 函数定义,这里的function() {} 也是表达式,只是赋给了变量a,所以有了区别,也有了名字a.name为a,称作函数定义 var b = function c() {} // 函数定义,函数function c() {} 也是表达式,只是赋值给了变量b,但是b.name却为c,和上面存在的区别,但也是函数定义 2. 导出的是"名字",我理解为名字就像一个绳子,后面拴的牛是会变的。这就是为什么import {a} from '../a.js' 这个a会变,虽然当前模块不能赋值给a。

    作者回复: 对哒!赞+2

    2019-11-22
    2
    15
  • Y
    老师,关于这边文章的中心,我能总结成这个意思吗。 export default function(){}。这个语法本身没有任何的问题。但是他看似导出一个匿名函数表达式。其实他真正导出的是一个具有名字的函数,名字的default。

    作者回复: 是的。不过,这算是题解。中心还是模块装载执行和标识符绑定全过程来着😄 标识符和值绑定是“声明”语法处理的核心,而六种声明是js静态语法的核心。而静态语法,也就是这一整篇“语言如何构建”的核心了🤓

    2019-11-18
    15
  • 万籁无声
    感觉没有抓住主题思想在表达什么,可能是我层次太低了

    作者回复: 正好,刚写完“Y”同学的留言,你不妨看看,应该正好能回答你的疑问。 (万恶的极客时间没有提供分留言链接的功能,产品同学要打手板心5次 🤔)

    2019-11-18
    12
  • 🇧🇪 Hazard🇦🇷
    老师,有一句话不太明白。 " import 的名字与 export 的名字只是一个映射关系 "。 export 一个变量,比如 count,如果设一个定时器执行,每次count都加 1; import { count }, 这个count也会每次都改变。这就是所说的映射关系吗? 这个映射关系是怎么做到的?

    作者回复: 验证这个映射关系很简单。 B模块中export一个let变量,然后在A模块中import它为x。然后你尝试在A模块中x++,你会发现提示为常量不可写。 所以A、B两个模块中的名字其实并不是同一个变量,它们名字相同(或者不同),但A模块中只是通过一个(类似于别名的)映射来指向B模块中的名字。 映射是通过创建一个专用的数据结构来实现的,访问该结构就跳到目标数据,但每个操作都有特定的限制(例如上面的只读)。——这整体上有些类似于属性中的get/setter的机制,但并不是用属性描述符来做到的。

    2020-03-23
    10
  • 许童童
    为什么在 import 语句中会出现“变量提升”的效果? 如老师所说,在代码真正被执行前,会先进行模块的装配过程,也就是执行一次顶层代码。所以如果import了一个模块,就会先执行模块内部的顶层代码,看起来的现象就是“变量提升”了。

    作者回复: 😃👍

    2019-11-18
    10
  • Marvin
    export default v=>v 这种,箭头函数是特例吗?

    作者回复: 有点特殊,但就处理逻辑(以及目的)上来说,也并不算是特例。 其实“函数定义(Function Definition)”这个概念出现得比较奇怪。 仔细分析一下就明白了,你想,“函数声明(Function Declaration)”是静态语义的,它在执行期的结果是empty,所以它必须是具名的才能导出,因为“声明(6种)”的目的都是具名,而export原则上只能“导出一个名字”。所以,由于“函数定义(Function Definition)”没有名字,所以它不能按函数声明来处理。 然后,由于“函数表达式(Function Expression)”是动态语义的,有执行语义(也就是执行结果返回不是empty),得到一个运行期概念上的“闭包”。但这并不是最关键处,最关键的地方在于函数表达式没名字——即使是具名的函数表达式,它的名字也只能闭包内有影响。由于它没有名字一个可供导出的名字,所以也不能直接直接用作export的对象。 那么到底在概念上该怎么说这个东西呢?ECMAScript在这里就加了这么一层概念,叫“函数声明(Function Declaration)”,一方面它是有静态语义的,它声明了某个东西;另一方面,它的名字又是迟绑定的,需要到了执行期根据“name = FunctionExpression”中的`name`来确认。 在这种情况下,其实“函数定义(Function Definition)”就是“函数表达式”的一层概念封装:它又有在外层(或被关联的对象)中的名字,它又是表达式;它的执行结果又是闭包,又是实例。 所以箭头函数看起来是特例,但用在导出语法的“这个位置”时,概念上却仍然是“封装了一层的‘箭头函数表达式’”,仍然还是“函数定义”。

    2019-11-20
    4
    6
  • leslee
    第三个结论推导过程的中间语法定义的引用那里(markdown '>' 符号表示的引用)读得不是很通顺, 有点迷....

    作者回复: 这是因为类似于: obj = { f: function() { }, ... } 这样位置中的匿名函数,在ECMAScript中都是称为“匿名函数定义”,而不是“匿名函数表达式”。所有在语法上记为“x = functionExpression”的,在处理上都与一般表达式有不同,这是一个非常非常小的细节,但在引擎层面,加入了好大一段逻辑呢。 真正的匿名函数表达式,是下面这样的: > (1 + function() {}) 就是:把它直接用在一个表达式计算过程中,而不是把它用来赋值(或绑定,或引用)给另一个东西。这种情况下,它才是按匿名函数表达式来处理的。 这几讲都是讲JavaScript的静态语言特性的,所以“词法分析以及对应的引擎处理”是要点,在词法分析阶段,关键在于“不能为它(函数、函数表达式、函数定义等等)创建闭包”。因为在静态处理阶段,还没有“闭包”这个概念,所以好多东西处理起来跟我们平常的理解不同,这就是根由了。

    2019-11-19
    6
  • 七月有风
    ECMAScript 6 模块是静态装配的,而传统的 Node.js 模块却是动态加载的。是不是说node是在执行阶段才会执行模块的顶层代码。

    作者回复: nodejs中,是在require()函数执行过程中来执行模块的顶层代码的。 nodejs模块被封装在一个函数中(亦即是作为一个函数的函数体),由require()在加载完指定模块的文本代码之后,用普通的调用函数的方法调用,从而实现模块装载的。

    2020-02-22
    5
  • Geek_885849
    "use strict"; (function a() { const a = 2; console.log(a); })(); 老师您好,这个函数名a 不是已经作为函数内部的标识符了吗,为什么还可以重新声明呢?

    作者回复: 关于这个问题,在《JavaScript语言精髓与编程实践》的第“5.5.2.4 函数表达式的特殊性”中专门有讲过,主要是因为“函数名作为标识符所声明的位置”所导致的。 具体来说,如果是函数声明,那么函数名是声明在它“所在”上下文的,因此它是否能“重新声明”取决于它所在的(外部的)上下文的严格模式状态。例如: ``` function foo() { function f() { "use strict"; f = 1; // 可重写,因为`f`声明在foo()中 } f() console.log(typeof f); // number } foo() ``` 为了在函数表达式中达成类似的效果(语言的一致性),所以函数表达式中这个函数名,也不是声明在函数体(以及由函数体所决定的闭包)中的。它采用了“双层作用域”的特殊构造,也就是函数名声明在外部作用域中(outerScope),而闭包的parent再指向这个outerScope。——函数的"use strict"只影响到函数自己的闭包。 所以回到你的例子, ``` "use strict"; (function a() { const a = 2; console.log(a); })(); ``` 由于是函数表达式,所以`a()`作为名字其实是声明在一个outerScope中的——没错,这个scope也是strict模式的。接下来函数body中声明了`count a`,这个名字所在的作用域(闭包)中并没有`a`这个名字,所以无论其外部,或者内部是否是严格模式,这个名字`a`都是可以创建的。 与此不同的是,函数参数是声明在闭包中的,所以它表现得跟函数名不同:如果函数参数中有名字a,那么上例中的`const a`就无法声明了。这同样也证明了函数名`a()`需要一个outerScope的重要性,因为历史中下面这样的代码“总是”合法的(无论是函数声明还是函数表达式): ``` function a(a) { ... } ```

    2020-08-21
    2
    4
收起评论
显示
设置
留言
38
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部