美文网首页
ES6模块默认导出和变量绑定(Default Exports a

ES6模块默认导出和变量绑定(Default Exports a

作者: 唯泥Bernie | 来源:发表于2019-08-22 00:40 被阅读0次

    背景

    JavaScript 现在最主流的模块机制是 commonjs 和 ES6 module。两者不单是语法上有所区别,在加载的时候也有所不同,譬如 commonjs 是运行时加载,ES6模块是编译时输出接口的。还有一个重要的区别是 commonjs 导出的东西是值拷贝,而 ES6 模块导出的东西是……暂时认为是引用拷贝吧。具体表现出来的区别看下面的例子:

    commonjs模块

    // a.js
    var counter = 3;
    function incCounter() {
      counter++;
    }
    module.exports = {
      counter: counter,
      incCounter: incCounter,
    };
    
    // main.js
    var mod = require('./a');
    
    console.log(mod.counter);  // 3
    mod.incCounter();
    console.log(mod.counter); // 3
    

    可以看到 counter 在模块内部被改变,但是使用此模块的代码获取 counter 始终是 export 时候的值,不会变。

    ES6模块

    // a.js
    export let counter = 3;
    export function incCounter() {
      counter++;
    }
    
    // main.js
    import { counter, incCounter } from './a';
    console.log(counter); // 3
    incCounter();
    console.log(counter); // 4
    

    ES6 模块导出的变量始终指向的是模块内部的变量,使用时可以获得此变量的最新值。我们叫导出绑定:Exporting binding。

    问题

    如果你去看 webpack 编译后的实现,它会把 counter 变量转换成 counter 的 getter,这么就可以实现绑定的效果。但是在看 webpack 对默认导出代码的转换时,发现实现并不使用 getter。也就是说按这种实现,使用 export default counter 是不会产生 Exporting binding。看看代码:

    // a.js
    let counter = 3;
    export function incCounter() {
      counter++;
    }
    export default counter;
    
    // main.js
    import counter, { incCounter } from './a';
    console.log(counter); // 3
    incCounter();
    console.log(counter); // 3
    

    解释

    为什么会有这种效果?其实 export default 是一种语法糖,当模块只有一个导出的时候,简化写代码人的代码量,我们把这个语法糖还原下:

    // 语法糖
    // myFunc.js
    function myFunc() {}
    export default myFunc;
    // main.js
    import myFunc from './myFunc';
    
    // 非语法糖
    // myFunc.js
    function myFunc() {}
    export { myFunc as default };
    // main.js
    import { default as myFunc } from './myFunc';
    
    

    也就是说把 export 的东西重命名/赋值给 default,再在 import 的时候把 default 重命名为你想要的名字。问题就出在这层语法糖的转换上,规范对于 export default x 的行为有解释,x 的类型不同,则行为不同:

    有名字的函数和类

    export default function foo() {}
    export default class Bar {}
    

    相当于

    function foo() {}
    export { foo as default };
    
    class Bar {}
    export { Bar as default };
    

    没有名字的函数和类

    export default function () {}
    export default class {}
    

    相当于

    function *default*() {}
    export { *default* as default };
    
    class *default* {}
    export { *default* as default };
    

    JS会把给匿名函数或类给一个内部的变量*default*,然后再重命名为 default 导出。这个内部变量是无法通过程序获取到的。

    原始类型

    export default 1;
    // --- 或者 ---
    let x = 4;
    export default x;
    

    相当于

    let *default* = 1
    export { *default* as default };
    // --- 或者 ---
    let x = 4;
    let *default* = x;
    export { *default* as default };
    

    当 export default x 中的 x 是没有名字的函数或者类,又或者是原始类型,export binding 的是内部变量*default* 并不是 x。所以改了 x 并不等于改了*default*,自然 import 的东西没有变化。

    参考文献

    1. https://2ality.com/2015/07/es6-module-exports.html
    2. https://stackoverflow.com/questions/39276608/is-there-a-difference-between-export-default-x-and-export-x-as-default/39277065#39277065
    3. https://github.com/rollup/rollup/issues/1078
    4. https://ponyfoo.com/articles/es6-modules-in-depth#bindings-not-values

    相关文章

      网友评论

          本文标题:ES6模块默认导出和变量绑定(Default Exports a

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