V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
haishiwuyuehao
V2EX  ›  问与答

JavaScript 模块化 Export 命名子句导出 的一些想法

  •  
  •   haishiwuyuehao · 2020-10-31 23:20:10 +08:00 · 1240 次点击
    这是一个创建于 1512 天前的主题,其中的信息可能已经有所发展或是发生改变。

    export 常见有几种导出方式:命名行内导出,命名子句导出,默认导出

    命名子句导出是通过在大括号内指定导出的变量。

    const foo = 'foo';
    const bar = 'bar';
    export {foo};
    export {bar as baz, foo};
    export {foo, bar};
    
    在这里我想到,在 JavaScript 中 {} 是一个块。每个块都有其作用域
    
    因此子句导出是创建了一个单独的作用域,在该作用域内指定要导出的变量,但是这么想的话 在指定的作用域中又不能创建方法并返回
    
    例如做到这样:export { hi(){} } 
    
    
    当我通过命名行导出和默认导出方法时:
    
    export function hi(){};
    
    export default function(){};
    
    对于该 export 语句
    1,创建了 hi 方法 并
    2,返回 hi 方法的引用 给 export
    
    3,export 创建 default 
    4,创建 匿名方法
    5,返回匿名方法的引用给 default
    
    从 1-2,3-5 来看,对于方法都是创建了一个作用域块的引用给到 export 。而在 javascript 中 {} 也是简写对象的一种形式,在这里并不是为 export 创建一个对象,返回该对象的引用。
    
    那么我的问题来了,如果对于 export “{}”并不是一个作用域块,不能在其中声明方法,初始变量等。也不是对象简写,那么 子句块的作用就只限于用 as 进行别命名了。真的有这个必要么?在导出命名行进行 as 不是更好吗??
    
    7 条回复    2020-11-01 11:40:56 +08:00
    yafoo
        1
    yafoo  
       2020-10-31 23:40:51 +08:00 via Android
    一直都记不清 export 有几种写法,也不知道为啥搞这么多写法
    noe132
        2
    noe132  
       2020-10-31 23:54:40 +08:00
    啥叫作用域块的引用?
    只有值才有引用,作用域块怎么说都不像是一种值吧?

    不要把 对象字面量语法和语句块语法 和这个大括号搞混在一起,这 3 个都不是 1 个东西,不是所有的大括号都是对象或者语句块,就比如模板字符串 `${a}`,建议把基本的语法学习一下

    你只需要知道 每个模块有自己的 namespace,export 只是单纯的在这个 namespace 上添加对应的绑定而已。
    export let a = 1
    export function b() { a += 1}
    export default 'c'
    export { a as d }
    这 4 个语句都是一样的,在 namespace 上添加绑定。通常 namespace 是一个对象,这个 namespace log 出来大致长这样
    { a: 1, b: function b(){ a += 1 }, _default: 'c', d: 1 }
    但不要把 namespace 看作简单的对象,首先它是不可修改的,其次所有的 export 都是对对应变量的绑定,而不是简单的把值导出来。比如如果你在模块内 修改 a = 2,那么 d 的值也会变成 2,因为 d 只是到 a 变量的一个绑定。
    import { a, b, d } from 'module'
    console.log(a, d) // 1, 1
    b()
    console.log(a, d) // 2, 2

    具体这个绑定是怎么实现的,通常你不需要关心,不同的运行环境或者编译器有不同的处理方式。
    noe132
        3
    noe132  
       2020-11-01 00:00:17 +08:00
    至于为啥不在定义变量的时候 as,设计的时候就是这么设计的,而且正好和 import 的 as 保持一致。如果硬要在变量定义时 as,我觉得这样的语法怎么设计都不会好看
    aaronlam
        4
    aaronlam  
       2020-11-01 00:37:11 +08:00
    据我所知,export {}; 这种 export 方法是方便你一股脑的把需要 named export 的塞进去 export,而不用每次都

    export const foo = 'bar'
    export const foo1 = 'bar1'
    export function func() {}
    haishiwuyuehao
        5
    haishiwuyuehao  
    OP
       2020-11-01 07:52:28 +08:00
    @noe132 我不理解你的 namespace 指的是什么。如果 export * from './myModule.js' ,按照你说的那么此刻 namespace 是怎么样的,在这里 再 export 与 myModule 模块相同的值,就会发生覆盖。此刻的 namespace 又是怎么样的。

    但是在 export 子句并非是一个对象,也不是一个作用域块。

    我理解,对于一个 module 来说,每个对 module 的引用都是在访问同一个 module 。在该 module 的 export 里,每个 export 具体指向会被引用在被引用的对象中

    ```javascript
    myModule.js

    export const foo = 'foo', bar = 'baz';
    export default class{
    constructor(baz){
    this.baz = baz || bar;
    console.log(this.baz);
    }
    }


    other.js

    import Baz, * as myModule from './myModule.js';
    let bax = new Baz();

    let bax2 = new myModule.default(myModule.foo);
    ```

    上面的代码就变成
    var myModule = (function(moduleConfig){
    moduleConfig.foo = 'foo';
    moduleConfig.bar = 'bar';
    return moduleConfig;
    })(myModule || {});

    var myModule = (function(moduleConfig){
    moduleConfig.default = class { ... }
    return moduleConfig;
    })(myModule || {});

    同样的,other.js 就变成这样
    var other= (function(moduleConfig){
    const Baz = myModule.default;
    const myModule = myModule;
    //操作 1
    let bax = new Baz();
    //操作 2
    let bax2 = new myModule.default(myModule.foo);
    })(other);

    只有这样才能解释的通,为什么修改在一个模块修改 myModule.js 的 foo = 'abc',其它模块访问到的 foo 是 abc 而不是 foo

    每个模块都是 IIFE,最终都将返回一个对象的引用给其它所有模块,以此同步所有模块访问到的是同一个模块。

    我的问题 还是 {},在 ecmascript 中,不管是 if(condition){} 还是 function(){} 都是表示一个块,都能在块中定义 function,命名变量。但是在 export 里 却不是这样,为什么,export 的子句要设置的如此奇特。export 子句未来是怎么样的
    noe132
        6
    noe132  
       2020-11-01 11:03:53 +08:00 via Android
    你的理解是错误的。你看到的 iife 只是 transpiler 对 esmodule 的一种实现。实际上可能根本没有 iife 。
    试想
    // a.js
    export const a = 1

    // b.js
    import { a } from 'a'
    console.log(a)

    transpiler 完全可以编译成
    const a = 1
    console.log(a)

    事实上 webpack 开启 concatenation plugin 后就是这样的结果。
    为啥总是要认为 export 是一个块?这不就是一个类似对象的语法么,和 import 语句是一样的,只是一种语法,和作用域块没有一点关系。这就不是一个语句块。如果这是个语句块,那你觉得写出来到底 export 了哪些东西?难道 export 了整个语句块?难道 import 语句的括号内我也要可以定义变量么?

    export 是完全静态的,export 只能存在模块顶部,不能在任何语句块内。export 只有标记的作用。要求能在代码不运行的情况分析所有的 import export 。如果按你的说法,export {} 里还能定义变量,写语句,那就不符合静态分析这一要求了。

    esmodule 只是一个规范,并不是具体的实现,并且规定了 export 不能变,那就不可能有 export 会有语句块这种问题。不要把 iife 的 module 实现直接和 esmodule 划等号

    至于 esmodule 是怎么实现的,那是 transpiler 或者 runtime 具体的事情。你不能因为某一种实现就认为所有的实现都有一样的特性,因为可能某些实现有 bug 或者限制
    haishiwuyuehao
        7
    haishiwuyuehao  
    OP
       2020-11-01 11:40:56 +08:00
    @noe132 没说过 export 子句是块。我问的是为什么不是块。变成块不好么。不过你说对,我还是在再解下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2733 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 15:00 · PVG 23:00 · LAX 07:00 · JFK 10:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.