es6模块

作者: 小泡_08f5 | 来源:发表于2019-07-02 11:26 被阅读0次

    模块化主要是帮助我们更好的组织代码, 模块允许我们将相关的变量和函数放在一个模块中。 在ES6模块化之前,JS语言并没有模块的概念,只有函数作用域和全局作用域非常容易发生命名冲突。 之前的RequireJS, SeaJS, AMD, UMD,CMD在一定层面上都是为了解决JS模块化的问题。ES6模块取其精华:

    • 它提供了简洁的语法
    • 以及异步的, 可配置的模块加载

    什么是模块

    模块是自动运行在严格模式下并且没有办法退出运行的JavaScript代码

    1. 在模块的顶部this的值是undefined
      2.其模块不支持html风格的代码注释
    2. 除非用default关键字,否则不能用这个语法导出匿名函数或类

    任何未限制导出的变量、函数或类都是模块私有的,无法从模块外部访问

    为什么要使用模块

    目前最普遍的JS运行平台便是浏览器,在浏览器中,所有的代码都运行在同一个全局上下文中, 这使得你即使更改应用中的很小一部分, 你也要担心可能会产生的命名冲突。

    传统的JS应用被分离在多个文件夹中,并且在构建的时候连接在一起,这稍显笨重。所以人们开始将每个文件内的代码都包在一个自执行函数中: (function(){ ... })(); 。 这种方法创建了一个本地作用域,于是最初的模块化的概念产生了, 之后的CommonJS和AMD系统中所称的模块, 也是由此实现的。

    创建模块

    一个JS模块就是一个对其他模块暴露一些内部的属性、方法的文件。 这里仅讨论浏览器中的ES2016模块系统。

    每个模块都有自己的上下文

    和传统的JS不同,在使用模块时,你不必担心污染全局作用域。恰恰相反,你需要把所有你需要用到的东西从其他模块中导入进来,这样会使得模块之间的依赖关系更为清晰

    导入导出

    可以使用ES6的新关键字 importexports 来导入或导出模块中的对象。 模块可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值等。

    默认导出

    每一个模块都支持导出一个不具名的变量,这为默认导出:

    // helloWord.js
    export default function(){
        console.log('111');
    }
    
    // main.js
    import hello from './helloWord';
    import anotherHello from './helloWord';
    
    hello(); // 111
    anotherHello(); //111
    console.log(hello === anotherHello); //true
    

    等价的CommonJS语法:

    // helloWord.js
    module.exports = function(){
        console.log('111');
    }
    
    //main.js
    var hello = require('./helloWord');
    var anotherHello = require('./helloWord');
    
    hello(); // 111
    anotherHello(); //111
    console.log(hello === anotherHello); //true
    

    任何JS值都是可以被默认导出的:

    // helloWord.js
    export default 3.14
    //export default { foo: 'bar' };
    //export default 'hello word';
    
    // main.js
    import hello from './helloWord';
    
    console.log(hello); // 3.14
    

    这里如果把注释放开,同样输出3.14, 当有多条export default语句,只会输出第一条export default的值

    // helloWord.js
    export default 3.14
    export default { foo: 'bar' };
    export default 'hello word';
    
    // main.js
    import hello from './helloWord';
    
    console.log(hello); // 3.14
    
    具名导入

    除了默认导出外, ES6的模块系统还支持导出任意数量个具名的变量:

    const PI = 3.14
    const value = 42;
    export function hello(){
        console.log('2111');
    }
    export {PI,value}
    
    // 等同于CommonJS语法
    
    // var PI = 3.14;
    // var value = 42;
    // module.exports.hello = function(){
    //     console.log('2111');
    // }
    // module.exports.PI = PI;
    // module.exports.value = value;
    

    导入的时候:

    import {PI,value,hello} from './helloWord';
    
    console.log(PI,value,hello());
    
    image.png

    导入的时候可以使用as关键字来重命名导入的变量:

    import {PI as PI2,value as val,hello as helloWord} from './helloWord';
    
    console.log(PI2,val,helloWord());
    

    结果是一样的

    导入所有

    最简单的,在一条命令中导入一个模块中所有变量的方法, 是使用*标记。 这样一来,被导入模块中所有导出的变量都会变成它的属性, 默认导出的变量则会被置于default属性中。

    // helloWord.js
    const PI = 3.14
    const value = 42;
    export function hello(){
        console.log('2111');
    }
    export {PI,value}
    
    // main.js
    import * as Hello from './helloWord';
    
    console.log(Hello);
    
    image.png

    注意一点
    import * as foo fromimport foo from的区别。 后者仅仅会导入默认导出的变量,而前者则会在一个对象中导入所有,如:

    // helloWord.js
    const PI = 3.14
    const value = 42;
    const foo = {
        'a':'123'
    }
    export function hello(){
        console.log('2111');
    }
    export {PI,value,foo}
    
    // main.js
    import * as foo from './helloWord';
    
    console.log(foo);
    console.log(foo.foo);
    
    image.png

    对比四种模块价值规范

    1. CommonJS
    2. AMD
    3. CMD
    4. ES6模块
    1. CommonJS

    commonJS是服务器端的模块化规范, node.js 就是参照commonJS规范实现的。commonJS中有一个全局的方法 require()用来加载模块

    function myModule(){
      this.hello = function(){
        return "hello"
      }
      this.goodbye = function(){
      return "goodbye"
      }
     }
    module.exports = myModule
    

    其实module变量代表当前模块
    这样就可以在其他模块中使用这个模块

    var myModule = require('myModule');
    
    var myModuleInstance = new myModule();
    myModuleInstance.hello();
    myModuleInstance.goodbye();
    

    关于commonJS的更多,见CommonJS规范

    2. AMD

    commonJS定义模块的方式和引入模块的方式还是比较简单的,但不适合浏览器端, 因为commonJS是同步加载的。 而AMD是异步加载的,模块的加载不影响它后面语句的运行。 所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 这个用require.js实现AMD规范的模块化, 用require.config()指定引用路径等,
    通过define()来定义模块, 用requier加载模块

    首先我们需要引入require.js文件和一个入口文件main.js. main.js中配置require.config()并规定项目中用到的基础模块。

    /** 网页中引入require.js及main.js **/
    <script src="js/require.js" data-main="js/main"></script>
    
    /** main.js 入口文件/主模块 **/
    // 首先用config()指定各模块路径和引用名
    require.config({
      baseUrl: "js/lib",
      paths: {
        "jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js
        "underscore": "underscore.min",
      }
    });
    // 执行基本操作
    require(["jquery","underscore"],function($,_){
      // some code here
    });
    
    

    引用模块的时候,我们将模块名放在[]中作为 require() 的第一参数; 如果我们定义的模块本身也依赖其他模块, 那就需要将他们放在[]中作为define的第一参数

    // 定义math.js模块
    define(function () {
        var basicNum = 0;
        var add = function (x, y) {
            return x + y;
        };
        return {
            add: add,
            basicNum :basicNum
        };
    });
    // 定义一个依赖underscore.js的模块
    define(['underscore'],function(_){
      var classify = function(list){
        _.countBy(list,function(num){
          return num > 30 ? 'old' : 'young';
        })
      };
      return {
        classify :classify
      };
    })
    
    // 引用模块,将模块放在[]内
    require(['jquery', 'math'],function($, math){
      var sum = math.add(10,20);
      $("#sum").html(sum);
    });
    
    

    define的第一个参数是依赖的模块, 必须是一个数组。 通过return来暴露接口

    通过 require() 来加载模块, 模块的名字默认为模块加载器请求的指定脚本的名字

    require(['main'],function(main){
        alert(main.foo());
    })
    

    require.js就是根据AMD规范来实现的, 优点是:

    1. 实现js文件的异步加载, 避免网页失去响应
    2. 管理模块之间的依赖性,便于代码的编写和维护
    3. CMD

    CMD也是异步模块定义
    CMD与AMD的区别:
    CMD相当于按需加载, 定义一个模块的时候不需要立即制定依赖模块,在需要的时候require就可以了,比较方便。
    而AMD则相反,定义模块的时候需要制定依赖模块, 并以形参方式引入回调函数中。

    // CMD 按需加载
            define(function(require,exports,module){
                var a = require('./helloWord');
                a.hello(); 
    
                var b = require('./counter');
                console.log(b.foo); 
                // 2111
                // aaa
            })
    
            // AMD 定义模块的时候需要制定依赖
            define(['./helloWord','./counter'],function(a,b){
                a.hello();
                console.log(b.foo);
            });
    
    image.png
    ES6

    ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单, 完全可以取代现有的CommonJS和AMD规范, 成为浏览器和服务器通用的模块解决方案。

    ES6模块主要有两个功能: exportimport

    export用于对外输出本模块(一个文件可以理解为一个模块)变量的接口

    import用于在一个模块中加载另一个含有exoport接口的模块

    参考:
    https://segmentfault.com/a/1190000010058955
    https://juejin.im/post/5aaa37c8f265da23945f365c

    https://segmentfault.com/a/1190000004100661
    http://es6.ruanyifeng.com/#docs/module

    深入系列:
    https://github.com/mqyqingfeng/Blog/issues/108
    https://zhuanlan.zhihu.com/p/33843378?group_id=947910338939686912

    面试题

    题目一:ES6与commonJS模块的差异

    1. commonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。

    • commonJS模块一旦输出一个值,模块内部的变化就影响不到这个值。
    • ES6模块如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,原始值变了,import加载的值也会跟着变。需要开发者自己保证,真正取值的时候能够取到值。

    **2. commonJS 模块是运行时加载, ES6模块是编辑时输出接口

    • 运行时加载: commonJS模块就是对象,即在输入时是加载整个模块,生成一个对象,然后再从整个对象上读取方法,这种加载称为”运行时加载“。 commonJS脚本代码在require的时候,就会全部执行。一旦出现某个模板被”循环加载“,就只能输出已经执行的部分,还未执行的部分不会输出。

    • 编译时加载: ES6模块不是对象,而是通过export命令显式指定输出的代码, import时指定加载某个输出值,而不是加载整个模块,这种加载称为”编译时加载“

    相关文章

      网友评论

        本文标题:es6模块

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