ES6模块和Node模块

理解ES6模块和node模块及其用法

Posted by SkioFox on October 22, 2017

在前端开发中使用ES6的模块化和node模块化总会有所混淆,今天深度解析下它们之间的区别

node模块化

  • node模块中的exports对象,用来创建对象

          // rocker.js
    
          exports.name = function() {
              console.log('My name is Lemmy Kilmister');
          };
    
    
  • 引用rocker.js

          var rocker = require('./rocker.js');
          rocker.name(); // 'My name is Lemmy Kilmister'
    
  • exports 与 module.exports 测试

          // 修改rocker.js如下:
          module.exports = 'ROCK IT!';
          exports.name = function() {
              console.log('My name is Lemmy Kilmister');
          };
    
          // 再次引用执行rocker.js
          var rocker = require('./rocker.js');
          rocker.name(); // TypeError: Object ROCK IT! has no method 'name'
    
  • 上面报错,实际上Module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是Module.exports而不是exports。所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是Module.exports本身不具备任何属性和方法。如果,Module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。

  • exports 与 module.exports实质

    • 模块内部大概是这样:
          exports = module.exports = {};  
    
    • exports是module.exports的一个引用

    • require引用模块后,返回给调用者的是module.exports而不是exports

    • exports.xxx,相当于在导出对象上挂属性,该属性对调用模块直接可见

    • exports =相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性

  • 扩展

    • Node.js在模块编译的过程中会对模块进行包装,最终会返回类似下面的代码:
          (function (exports, require, module, __filename, __dirname) {
              // module code...
          });
          // 其中,module就是这个模块本身,require是对Node.js实现查找模块的模块Module._load实例的引用,__filename和__dirname是Node.js在查找该模块后找到的模块名称和模块绝对路径,这就是官方API里头这两个全局变量的来历。
    
    
  • 总结:

    1. 对于要导出的属性,可以简单直接挂到exports对象上
    2. 对于类,为了直接使导出的内容作为类的构造器可以让调用者使用new操作符创建实例对象,应该把构造函数挂到module.exports对象上, 不要和导出属性值混在一起

ES6模块

  1. export 命令

    • 出口,用于规定模块的对外接口(这就意味着数据必须被包装成对象的格式)。
         // 输出变量
         // profile.js
         var firstName = 'Michael';
         var lastName = 'Jackson';
         var year = 1958;
            
         export {firstName, lastName, year};
    
         // 输出函数
    
         function v1() { ... }
         function v2() { ... }
            
         export {
             v1 as streamV1,
             v2 as streamV2,
             v2 as streamLatestVersion
         };
    
    • export 输出时,只有三种固定格式,其他均会报错:
         // 写法一
         export var m = 1;    // 声明时输出
            
         // 写法二
         var m = 1;
         export {m};      // 包装成数据对象输出
            
         // 写法三
         var n = 1;
         export {n as m};    // 使用别名输出,基本上相当于第二种
    
  2. import 命令

    • 入口,加载 export 的模块中的变量、方法等。
         // main.js
         import {firstName, lastName, year} from './profile.js';
            
         function setName(element) {
         element.textContent = firstName + ' ' + lastName;
         }
         // 当然,引入的变量也可以修改变量名:
         import { lastName as surname } from './profile.js';
    
         // 如果 export 时未采用 {xxx,xxx} 方式,那么在 import 时,想要引入所有值,可以使用 * 来代替:
    
         // main.js
        
         import { area, circumference } from './circle';
            
         console.log('圆面积:' + area(4));
         console.log('圆周长:' + circumference(14));
            
            
         // 可以简写成
         import * as circle from './circle';
            
         console.log('圆面积:' + circle.area(4));
         console.log('圆周长:' + circle.circumference(14));
    
    
  3. export default 命令

    • 用于不知道需要加载模块的变量名和函数名
         // export-default.js
         export default function () {
             console.log('foo');
         }
            
            
         // import-default.js
         import customName from './export-default';
         customName(); // 'foo'
    
         // 通过 export default 出来的通常在 import 时候不需要 {},且 import 时可以任意命名。
    
  4. 总结

  • export与export default均可用于导出常量、函数、文件、模块等
  • 在一个文件或模块中,export、import可以有多个,export default仅有一个
  • 通过export方式导出,在导入时要加{ },export default则不需要如果此模块是一个类,则等同于node模块中给module.exports赋值,这样调用者就是一个类构造器,可以直接new实例

ES6模块化与commonjs、amd区别:

  • ES6 模块的设计思想是尽量的静态化,使得**编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。