zoukankan      html  css  js  c++  java
  • js当中CommonJS 和es6的模块化引入方案以及比较

    js当中CommonJS 和es6的模块化引入方案以及比较:https://blog.csdn.net/jackTesla/article/details/80796936

    在es6之前,对于模块化方案主要是CommonJS和AMD两种。咱们这次说一下ES6和CommonJS的区别。

        它们有两个重大差异:

    CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
        第一个差异:
        CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。

        // lib.js

        var counter = 3;

        function incCounter() {

          counter++;

        }

        module.exports = {

          counter: counter,

          incCounter: incCounter,

        };

        上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。

        // main.js

        var mod = require('./lib');

        console.log(mod.counter);  // 3

        mod.incCounter();

        console.log(mod.counter); // 3

        上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

        // lib.js

        var counter = 3;

        function incCounter() {

          counter++;

        }

        module.exports = {

          get counter() {

            return counter

          },

          incCounter: incCounter,

        };

        上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

        ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

    还是举上面的例子。

        // lib.js

        export let counter = 3;

        export function incCounter() {

          counter++;

        }

        // main.js

        import { counter, incCounter } from './lib';

        console.log(counter); // 3

        incCounter();

        console.log(counter); // 4

        上面代码说明,ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。

        再举一个出现在export一节中的例子。

        // m1.js

        export var foo = 'bar';

        setTimeout(() => foo = 'baz', 500);

        // m2.js

        import {foo} from './m1.js';

        console.log(foo);

        setTimeout(() => console.log(foo), 500);

        上面代码中,m1.js的变量foo,在刚加载时等于bar,过了 500 毫秒,又变为等于baz。

        让我们看看,m2.js能否正确读取这个变化。

        $ babel-node m2.js

        bar

        baz

        上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。

        由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。

        // lib.js

        export let obj = {};

        // main.js

        import { obj } from './lib';

        obj.prop = 123; // OK

        obj = {}; // TypeError

        上面代码中,main.js从lib.js输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。

        第二个差异:
        因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

        Es6模块的设计思想是尽量放入静态化,使得在编译时就能确定依赖关系,而CommonJS就只能在运行时确定这些输入和输出的变量。

        // CommonJS模块

        let { stat, exists, readFile } = require('fs');

        // 等同于

        let _fs = require('fs');

        let stat = _fs.stat;

        let exists = _fs.exists;

        let readfile = _fs.readfile;

        上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

    ES6 通过export命令显式指定输出的代码,再通过import命令输入。

        // ES6模块

        import { stat, exists, readFile } from 'fs';

        上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

        在es6中,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

        export var foo = 'bar';

        setTimeout(() => foo = 'baz', 500);

        上面代码输出变量foo,值为bar,500 毫秒之后变成baz。

        这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新。

        export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

        function foo() {

          export default 'bar' // SyntaxError

        }

        foo()

        上面代码中,export语句放在函数之中,结果报错。

        注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

        foo();

        import { foo } from 'my_module';

        上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

        由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

        // 报错

        import { 'f' + 'oo' } from 'my_module';

        // 报错

        let module = 'my_module';

        import { foo } from module;

        // 报错

        if (x === 1) {

          import { foo } from 'module1';

        } else {

          import { foo } from 'module2';

        }

        上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

        这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

        const path = './' + fileName;

        const myModual = require(path);

        上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import命令做不到这一点。

        因此,有一个提案,建议引入import()函数,完成动态加载。

        import(specifier)

        上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。

        import()返回一个 Promise 对象。下面是一个例子。

        const main = document.querySelector('main');

        import(`./section-modules/${someVariable}.js`)

          .then(module => {

            module.loadPageInto(main);

          })

          .catch(err => {

            main.textContent = err.message;

          });

        import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

        目前阶段,通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。

        require('core-js/modules/es6.symbol');

        require('core-js/modules/es6.promise');

        import React from 'React';

    注:
        import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

        import {a} from './xxx.js'

        a = {}; // Syntax Error : 'a' is read-only;

        上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是,如果a是一个对象,改写a的属性是允许的。

        import {a} from './xxx.js'

        a.foo = 'hello'; // 合法操作

        上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性。
    ————————————————
    版权声明:本文为CSDN博主「jackTesla」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/jackTesla/article/details/80796936

  • 相关阅读:
    truncate table
    SSIS学习笔记
    Bing Developer Assistant开发随记
    数组中的逆序对
    第一个只出现一次的字符
    丑数
    把数组排成最小的数
    连续子数组的最大和
    最小的k个数
    数组中出现次数超过一半的数字
  • 原文地址:https://www.cnblogs.com/bydzhangxiaowei/p/12238747.html
Copyright © 2011-2022 走看看