JavaScript核心原理解析
周爱民
《JavaScript语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
立即订阅
3529 人已学习
课程目录
已更新 16 讲 / 共 21 讲
0/3登录后,你可以任选3讲全文学习。
开篇词 (1讲)
开篇词 | 如何解决语言问题?
免费
从零开始:JavaScript语言是如何构建起来的 (5讲)
01 | delete 0:JavaScript中到底有什么是可以销毁的
02 | var x = y = 100:声明语句与语法改变了JavaScript语言核心性质
03 | a.x = a = {n:2}:一道被无数人无数次地解释过的经典面试题
04 | export default function() {}:你无法导出一个匿名函数表达式
05 | for (let x of [1,2,3]) ...:for循环并不比使用函数递归节省开销
从表达式到执行引擎:JavaScript是如何运行的 (6讲)
06 | x: break x; 搞懂如何在循环外使用break,方知语句执行真解
07 | `${1}`:详解JavaScript中特殊的可执行结构
08 | x => x:函数式语言的核心抽象:函数与表达式的同一性
09 | (...x):不是表达式、语句、函数,但它却能执行
10 | x = yield x:迭代过程的“函数式化”
11 | throw 1;:它在“最简单语法榜”上排名第三
从原型到类:JavaScript是如何一步步走向应用编程语言的 (1讲)
12 | 1 in 1..constructor:这行代码的结果值,既可能是true,也可能是false
不定期加餐 (3讲)
加餐 | 捡豆吃豆的学问(上):这门课讲的是什么?
免费
加餐 | 捡豆吃豆的学问(下):这门课该怎么学?
免费
加餐 | 让JavaScript运行起来
免费
JavaScript核心原理解析
登录|注册

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

周爱民 2019-11-18
你好,我是周爱民,欢迎回到我的专栏。
今天我要讲述的内容是从 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《JavaScript核心原理解析》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(16)

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

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

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

    作者回复: 😃👍

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

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

    2019-11-18
    3
  • 海绵薇薇
    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
  • 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
    2
    1
  • 万籁无声
    感觉没有抓住主题思想在表达什么,可能是我层次太低了

    作者回复: 正好,刚写完“Y”同学的留言,你不妨看看,应该正好能回答你的疑问。

    (万恶的极客时间没有提供分留言链接的功能,产品同学要打手板心5次 🤔)

    2019-11-18
    1
  • 穿秋裤的男孩
    可以这样理解吗?
    静态解析期:export只导出名字到某个名字表,import从名字表获取映射关系。
    执行期:执行代码,为名字赋值。

    作者回复: 是的。这个“执行期”在用户代码之前。

    2019-11-29
  • 穿秋裤的男孩
    所谓模块的装配过程,就是执行一次顶层代码而已。

    这边的顶层代码是指什么呢?模块装配不是在静态解析期进行的吗?为什么还会执行代码?还是这边指的执行并不是一般意义上的执行呢?

    作者回复: ```
    // t.mjs
    console.log("here =>", typeof f);
    import f from './f.mjs';

    // f.mjs
    export default function() {}
    console.log('NOW');

    // test
    > node --experimental-modules t.mjs
    NOW
    here => function
    ```

    想想,
     1. 为什么`here`为什么是function呢?import语句还没有到呢。
     2. 为什么`NOW`在`here`之前?这是哪个时候的执行过程?

    2019-11-29
  • 穿秋裤的男孩
    老师,下面这句话我不是很懂,希望给解惑:

    ------
    因此,该匿名函数初始化时才会绑定给它左侧的名字“default”,这会导致import f from ...之后访问f.name值会得到“default”这个名字。
    ------
    根据这句话我实际运行了下代码,如下:
    // a.js
    export default function() {};

    // b.js
    import a from './a.js';
    console.log(a.name); // 打印为空字符串“”;

    // 疑惑点
    按照您的解释,这边不是应该打印default吗?很奇怪

    作者回复: 不知道你的引擎的情况,我这里显示会是default。

    ```
    // f.mjs
    export default function() {}

    // t.mjs
    import f from './f.mjs';
    console.log(f.name);

    // test
    > node --experimental-modules t.mjs
    default
    ```

    2019-11-29
    1
  • 海绵薇薇
    Hello 老师好:)

    函数定义

    var a = function foo() {

    console.log(foo)

    }

    当前上下文没有标识符foo,但是foo函数内却可以拿到该标识符,所以foo这个标识符应该是声明了,但是不在当前作用域,那么可以简单理解为

    var a = eval('\
        let foo;\
        foo = function (){\
            console.log(foo)\
        }\
    ')

    可以这么理解吗?

    作者回复: 是的。这样没问题。除了缺一咪咪的严谨之外,你的理解是对的。^^.

    2019-11-28
  • 陆昱嘉
    “声明(6种)”的目的都是具名,“函数声明(Function Declaration)”必须是具名的才能导出。
    既然这样,不就是这个函数声明function(){}具名导出给default这个名了吗?
    现在又说
    “它并不是导出了一个匿名函数表达式,而是导出了一个匿名函数定义。”
    不是函数表达式理解,为什么非说导出“函数定义”而不是“函数声明”呢?
    函数声明,函数表达式,和函数定义之间的区别联系是什么,搞晕了。

    作者回复: 这个问题请参考一下在评论区给Marvin的回复就好了。^^.

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

    作者回复: 这是因为类似于:

    obj = {
      f: function() {
      },
      ...
    }

    这样位置中的匿名函数,在ECMAScript中都是称为“匿名函数定义”,而不是“匿名函数表达式”。所有在语法上记为“x = functionExpression”的,在处理上都与一般表达式有不同,这是一个非常非常小的细节,但在引擎层面,加入了好大一段逻辑呢。

    真正的匿名函数表达式,是下面这样的:
    > (1 + function() {})

    就是:把它直接用在一个表达式计算过程中,而不是把它用来赋值(或绑定,或引用)给另一个东西。这种情况下,它才是按匿名函数表达式来处理的。

    这几讲都是讲JavaScript的静态语言特性的,所以“词法分析以及对应的引擎处理”是要点,在词法分析阶段,关键在于“不能为它(函数、函数表达式、函数定义等等)创建闭包”。因为在静态处理阶段,还没有“闭包”这个概念,所以好多东西处理起来跟我们平常的理解不同,这就是根由了。

    2019-11-19
  • westfall
    export default function x() { }
    导出一个具名函数表达式
    export default function() { }
    不是导出一个匿名函数表达式,而是一个匿名函数定义

    作者回复: 第一个也不是“(具名)函数表达式”。因为如果它是表达式(expression),那么它的执行结果就是一个实例,而实例是无法导出的。只有它是一个“函数定义”,它才能在“静态语法分析之后、代码执行之前”被导出。

    2019-11-18
    4
  • Wiggle Wiggle
    文中多次出现“最顶层代码”,那什么是最顶层代码呢?

    作者回复: 这个细述起来有点复杂,因为有好些细节的地方都能被称为“顶层的(代码)”。简而言之,你把一个.js文件中所有在全局的声明语句去掉,剩下的就是顶层代码了。

    顶层这个概念其实挺重要的,因为tc39现在有一份提案在推,就是top-level await。这个在引擎实现的层面上,以及应用的层面上都挺关键的。

    2019-11-18
    4
  • 可可
    没有JavaScript基础的人表示,听起来很吃力啊,估计要先学学JavaScript,然后在多读几遍本专栏才行。

    作者回复: 呵呵,其实真讲JavaScript的地方并不特别的多。就好象讲数据结构总需要一门基础语言一样,那个基础语言其实不重要,重要的是数据结构。
    除了简单的语法和一些惯例之外,这门课真在讲“JavaScipt怎么用”的地方并不多。^^.

    2019-11-18
    1
  • 2102
    说白了export就是将代码导出,并没有执行表达式。js中执行是要加()

    作者回复: “并没有执行表达式”
    +1

    2019-11-18
收起评论
16
返回
顶部