ES6 Module之export 解读

作者: 蒋启钲 | 来源:发表于2017-08-03 16:19 被阅读0次

    已默认读者了解本篇自言自语的context,且对于module有所了解,对于module的相关扩展说明将穿插在内容中(本篇不会提及class),由于是只是export,所以......import请容我忽略

    本篇代码运行环境为{"presets": [ "es2015","stage-2" ] }这只是作为参考,且只是运行环境,不推荐在学习ES6时将代码全部转译为ES5,ES6转化后的代码只能告诉你结果,相比较而言,原因或是理由的价值超过结果,学习ES6,不单单要知道代码运行的结果,最重要的目的是,知其所以然,了解一个行为为什么会这么发生,行为的背后又是什么,这才是学习者所需要追寻的

    文章参考:
    You-Dont-Know-JS
    ECMAScript 6 入门
    ES6规范15.2.3.2 Static Semantics: BoundNames

    首先登场的就是 export , 这是一个主要关键词,基本用法是放置在一个"声明"之前,或一组由{}语法(注意,此处的{}语法与对象无关)包裹的即将被导出的"标识符"之前

    //export 放置在"声明"之前
    export var a = 1, b = 2, c = 3
    export let a = 1
    export const a = 1
    export let { a } = { a: 1 }
    export var foo = function() {}
    export function foo() {}
    
    //export 放置在一组"标识符"之前
    var a = 1, b = 2
    export { a, b }
    //等同于
    export var a = 1
    export var b = 2
    

    以上例子有一个明确的共同点,export 后面没有出现“表达式”。实际上,单独的export 是对变量标识符(指针位置)的绑定,并期许将来会把对应的标识符(指针)导出。

    将“变量标识符”导出,这样的描述容易产生混淆,考虑下面的代码

    var a = 1
    export { a }
    a = 3
    //等同于
    export var a = 1
    a = 3
    

    当这个模块被导出后,如果赋值发生,那么已被导出的值也将被更新,无论导出发生在任何阶段。进一步的说,在被导入时的值是无关紧要的。这些绑定是实时的链接,所以唯一重要的是当你访问这个绑定时它当前的值是什么。

    1. 声明
    2. 导出标识符 a,此刻的 a 是指向变量本身的一个引用,或指针,而不是它的值的一个拷贝
    3. 模块内部,a 被重新赋值,已经导出的值也会被自动更新
    参考规范15.2.3.2
    ExportDeclaration : export VariableStatement
      1. Return the BoundNames of VariableStatement.
    ExportDeclaration : export Declaration
      1. Return the BoundNames of Declaration.
    可以看到export 的导出是明确的
    

    另外“标识符”一词引用于The above rule means that each ReferencedBindings of ExportClause is treated as an IdentifierReference.主要是词穷,并且因为模块导出与赋值是不同的,在进行导出时,实质上是导出了一个单向绑定的(不允许在导入的一方进行改变)变量的引用,是的,更准确的说应该是对于变量这个容器的引用,模块导出并不关心变量的值。

    在进行导出的时候,可以使用别名,关键词 as

    var a = 1
    export { a as b }
    //将 a 重命名为 b
    

    "在一个模块中,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。"

    "为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。"

    上述两句直接摘自ECMAScript 6 入门,主要是感觉是很恰当的描述,如果加以变动反倒画蛇添足了,当然还不够全面,所以对default进来以下的补充

    1. 每个模块定义只能有一个default,它是唯一的,每个被导出的模块只包含一个default元素,所以export default命令在模块内只被允许使用一次。

    2. 本质上export default就是输出一个叫做default的默认标识符。等同于,将export default 之后的内容以赋值的形式添加到default元素上。

    var a = 1
    export default a 
    //等同于
    export default 1
    //导出的是那表达式在那一刻的值的绑定,不是 标识符a的绑定(export.default = 表达式)
    

    export default 有许多微妙的细节,令人困扰的(不是结果,而是行为)。请思考下面的代码。

    //m2.js
    1.
    function foo() {}
    export default foo
    foo = 'change'
    
    2.
    export default (function foo() {})
    foo = 'change'
    
    3.
    function foo() {}
    export { foo as default }
    foo = 'change'
    //由于上文已经描述过,default接近于标识符,所以,可以直接重命名foo作为default导出。
    
    4.
    export default function foo() {}
    foo = 'change'
    
    //另一模块
    import * as all from 'm2.js'
    console.log(all)
    

    你的大脑能够清晰的知道每个模块即将会发生的事情吗?如果不能那么请继续阅读,如果能,那么也希望你继续阅读,重温复习这一片段。下面让我复制代码,描述并解释每一模块的行为。

    模块1.
    function foo() {}
    //声明foo
    export default foo
    //将foo赋值给default元素(注意,此时foo是表达式)
    foo = 'change'
    
    //结果
    { default: [Function: foo] }
    

    export default 导出的是那一个函数表达式在那一刻的值的绑定,不是 标识符foo的绑定。换句话说,export default ..接收一个表达式。如果你稍后在你的模块内部赋给foo一个不同的值,这个模块导入将依然表示原本被导出的函数,而不是那个新的值。

    规范里定义了export default 表达式的导出相关行为export default AssignmentExpression

    ExportDeclaration : export default AssignmentExpression ;
    1.Return «"default"».
    简单解释下,就是将表达式的值赋予default,然后返回default

    模块2.
    export default (function foo() {})
    将(..)赋值给default 元素(注意,()是表达式)
    foo = 'change'
    //结果
    ReferenceError: foo is not defined
    export default !function foo() {}
    !等运算符可以包装一个函数使它作为一个表达式返回值
    

    export default (function foo(){}),export default后面的是函数表达式,并不是函数声明定义,所以它对应的规范与模块1相同,导出的ExportedBindings也就是一个«"default"»。

    这里之所以报错是因为函数表达式只会返回函数本身作为值,并不会在外部作用域定义同名变量,所以下面的foo = 'change'找不到foo这个定义。

    模块3.
    function foo() {}
    export { foo as default }
    foo = 'change'
    //结果
    { default: 'change' }
    

    ExportDeclaration : export Declaration
    1.Return the BoundNames of Declaration.
    行为与 export '标识符‘相同,所以引用的规范相同,唯一需要理解的是default是可以被赋值

    模块4.
    export default function foo() {}
    //一个函数声明出现了!
    foo = 'change'
    //结果
    { default: 'change' }
    

    function foo..部分是一个函数表达式,但是对于模块内部作用域来说,它被视为一个函数声明,因为名称foo被绑定在模块的顶层作用域

    export default 函数声明定义在规范中定义的行为,对应的是export default HoistableDeclaration.
    ExportDeclaration : export default HoistableDeclaration
    1.Let declarationNames be the BoundNames of HoistableDeclaration.
    2.If declarationNames does not include the element "default", append "default" to declarationNames.
    3.Return declarationNames.

    可以看到按照规范如果将要导出的声明没有包含元素default,那么就进行赋值(规范概念),最后返回的是一个当前绑定的标识符,与前面的表达式时状态不同。所以结果能够看到,导出值被更新了。

    原标题为ES6 Module 详解,然后发现自己并没有真正理解Module这部分于是停笔,回去看书了,实际意义上本文是为了总结知识

    初学ES6,文章有误请指点,文内部分用词不准确也请谅解,虽然引用了规范,但是并没有能力进行解读,惭愧。

    文章参考:
    You-Dont-Know-JS
    ECMAScript 6 入门
    ES6规范15.2.3.2 Static Semantics: BoundNames

    相关文章

      网友评论

        本文标题:ES6 Module之export 解读

        本文链接:https://www.haomeiwen.com/subject/rwjulxtx.html