在前端模块化中,目前主流的两种规范是CommonJS规范以及ES6的module规范。虽然平时项目经常使用到这些规范,但是对于这两者的区别以及更深层的理解还是不够,所以自己在网上查找资料补下这块的知识盲点。

首先先从ES6模块说起

ES6 module

ES6 module模块主要分为以下几个命令:

  • import: 用于输入其他模块提供的功能,也可以说是用于模块的导入
  • export:用于规定模块的对外接口,也可以说是用于模块的导出
  • export default: 用于规定模块的默认导出

export

export 命令主要用于导出模块接口,使用方法如下:


    export const name = 'zs'  //输出变量

    export function add(x,y){  //输出函数
        return x + y;
    }

    function t1(){...}
    export {
        t1 as test1
    }
    //可以使用as关键字重新定义输入函数变量名

import

import 指令主要是用于导入其他接口,主要用法如下

    import { firstName, lastName } from './profile.js';

    function setName(element) {
    element.textContent = firstName + ' ' + lastName;
    }

除此之外,我还总结了import指令的其他知识点:

  • import多次导出同一个接口只会调用一次
  • import as 可以将导入的变量重命名
  • import * 可以指定一个对象整体加载引入的方法或变量
  • import引入的变量都是只读,除非引入的是对象
    //如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次
    import 'lodash'
    import 'lodash'

    //import as 可以重命名引入的变量 ,以下就是引入的变量n改写为变量m
    import { n as m } from './profile.js'

    //import * 可以指定一个变量接受引入的方法或变量
    export function area(radius) {
    return Math.PI * radius * radius;
    }

    export function circumference(radius) {
    return 2 * Math.PI * radius;
    }
    //常规引入接口提供的方法
    import { area, circumference } from './circle';
    console.log('圆面积:' + area(4));
    console.log('圆周长:' + circumference(14));

    //import * 整体加载
    import * as circle from './circle';
    console.log('圆面积:' + circle.area(4));
    console.log('圆周长:' + circle.circumference(14));

export default

export default 用于接口的默认导出

    //默认导出一个匿名函数
    export default function () {
        console.log('foo');
    }

    //下面import可以使用任意变量名定义export输出的方法
    import customName from './export-default';
    customName();   

export default 和export的区别

  • export default 命令用于指定模块默认导出,一个模块只能有一个默认输出,因此export default只能有一次
  • export default 对应的 import不需要使用大括号, export 对应的 import需要使用大括号

CommonJS module

CommonJS规范中,每个文件就是一个模块,有自己的作用域。一个文件里面定义的类、函数、变量都是私有的,对其他文件不可见。CommonJS主要有以下几个关键对象:

  • exports:Node内置对象,用于模块中的变量、函数等导出。实际指向module.exports。
  • module.exports: 表示当前模块对外输出的接口,其他文件加载模块,实际就是读取module.exports变量。
  • require: 读取并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有则报错。

exports

exports 其实就是指向module.exports

    var exports = module.exports

所以如果将 exports 变量指向一个值的时候,其实就是像module.exports赋值。所以最终导致可能会导出接口错误等问题,如下:

    exports = function(){}

上面这种写法改变了默认exports的指向,导出的时候module对象中的exports属性则为空对象。

    const name = 'zs'
    const fn = function(){console.log('ls')}
    exports.name = name;
    exports.fn = fn;
    module.exports = 'hello world'

像上面这种写法也不行,首先exports导出了一个变量和一个函数,其实是在module.exports对象里面添加了两个对象,最后module.exports = ‘hello world’重置了module.exports对象。最终只能返回一个字符串。

module exports

module.exports其实是module对象的一个属性,module对象其实还包括其他一些属性,比如:

module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。

举个例子:

    const name = 'zs'
    const fn = function (a,b) {
        return a + b;
    }
    exports.name = name;
    exports.fn = fn;
    console.log(module)

打印module得出:

 Module {
  id: '.',
  exports: { name: 'zs', fn: [Function: fn] },
  parent: null,
  filename: 'D:\\demo\\JS\\t2.js',
  loaded: false,
  children: [],
  paths:
   [ 'D:\\demo\\JS\\node_modules',
     'D:\\demo\\node_modules',
     'D:\\node_modules'
   ]
}

require

require指令是读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。

    //  example.js
    exports.message = "hi";
    exports.say = function () {
       console.log(message);
    }

通过require引入可以输出exports对象

    var example = require('./example.js');
    example
    // {
    //   message: "hi",
    //   say: [Function]
    // }

CommonJS模块和ES6模块的区别

  1. CommonJS可以动态引入文件,如require(${path}/main),而ES6模块是编译时就确定好的。
  2. CommonJS模块的加载机制是,输入的是被输出的值的拷贝,模块内部的变化不会影响输出的值。而ES6模块输出的是值的引用,当模块内的值发生改变,引用也会改变。