前端模块及各个标准梳理

233 阅读5分钟

之前写过一篇模块化博客,那是掘金的第一篇,还是对各个规范之间的差异不懂,查阅了一些资料,再次总结下。在各个标准规范出来之前,手写的模块系统无非就是使用闭包,然后暴露出来接口,上一篇中归纳过,就暂不赘述,接下来总结一些各个规范。

CommonJS

CommonJS规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组织,如果想要使用在浏览器端,需要转译下。

CommonJS模块定义需要使用require()指定依赖,exports对象定义公开的API。

module.exports = {
    name:'nick'
}

const a = require('./a')

其主要有以下几个特点

  • 无论一个模块在require()中引用多少次,模块永远是单例。

  • 模块第一次加载后会被缓存,后续加载的只是其缓存

  • require()加载是同步执行操作,可以包含在语句中

    if(true){
    	require('./a')
    }
    
  • exports与module.exports

    exports只是module.exports的一个引用,

    • exports

      可以一次导出多个实体

      exports.name = 'nick'
      exports.age = '18'
      //等同于
      module.exports = {
          name:'nick',
          age:'18'
      }
      
    • module.exports

      如果一次只想导出一个实体,可以直接给module.exports赋值

      module.exports = {
          name:'nick'
      }
      

AMD

AMD( Asynchronous Module Definition ),以浏览器为目标执行环境。AMD的一般策略是让模块申明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖完成加载后立即执行依赖的模块。

AMD模块实现的核心是用函数包装模块定义,这样可以防止申明全局变量,并允许加载器控制何时加载模块。包装模块的函数也便于模块代码的移植,因为包装函数内部的所有模块代码使用的都是原生JavaScript结构。包装模块的函数是全局define的参数,譬如:

//id为moduleA的模块定义
//moduleA依赖moduleB
define('moduleA',['moduleB'],function(moduleB){
	return{...}
})

其内部也支持require和exports对象

define('moduleA',['moduleB'],function(require,exports){
	let moduleB = require('moduleB')
	exports.stuff = moduleB.dostuff()
})

UMD

为了统一CommonJS和AMD生态,通用模块(UMD)应用而生。UMD可以用于创建这两个系统都可以使用的模块代码。UMD定义的模块会在启动时检测要使用的模块系统然后进行适配。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else {
        // Browser globals
        root.amdWeb = factory(root.b);
    }
}(typeof self !== 'undefined' ? self : this, function (b) {
    // Use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

CMD

CMD规范整合了CommonJS和AMD规范的特点,其全称为 Common Module Definition ,与require.js类似,其规范的实现为sea.js。

AMD和CMD的两个主要的差别:

  • AMD需要异步加载模块,而CMD在加载模块时,可以通过同步的的形式(require),也可以通过异步的形式(require.async)。
  • CMD遵循依赖就近原则,AMD遵循依赖前置原则。在AMD中,我们需要把模块所需要的依赖都提前申明在依赖数组中;而CMD中,我们只需要在具体代码逻辑内,在使用前引入依赖即可。
//AMD
define(['./a','./b'], function (a, b) {
 
    //依赖一开始就写好
    a.test();
    b.test();
});
 
//CMD
define(function (requie, exports, module) {
     
    //依赖可以就近书写
    var a = require('./a');
    a.test();
     
    ...
    //软依赖
    if (status) {
     
        var b = requie('./b');
        b.test();
    }
});

ES6模块

ES6最大的一个改进就是引入了模块规范,这个规范全方位简化了之前出现的模块加载器,原生浏览器支持就意味着加载器及其预处理器都不在必要,可以看是AMD和CommonJS的集大成者。

模块标签及定义

带有type='module'属性的的标签后会立即下载模块文件,但执行会延迟到文档解析完成。与

如果给模块标签添加async属性,不仅模块执行顺序不再与

模块行为

ES6模块借用了CommonJS和AMD很多优秀的特性

  • 模块代码只在加载后执行
  • 模块是单例的
  • 模块只会加载一次
  • 模块可以请求加载其他模块
  • 支持循环依赖

也添加了一些新的行为

  • 默认只在严格模式下执行
  • 模块不共享全局命名空间
  • 模块顶级this值是undefined
  • 模块异步加载执行
模块导出

ES6模块导出支持命令导出和默认导出。

  • 命令导出

    命令导出就好像模块是被导出的容器

    const foo = 'foo'
    export {foo}
    export {foo as f} //提供别名
    
  • 默认导出

    默认导出就好像模块与被导出的值是一回事。使用default关键字将一个值的申明为默认导出,每个模块只能有一个默认导出。

    const foo = 'foo'
    export default foo
    

    命令导出与默认导出并不会冲突

    const foo = 'foo'
    const bar = 'bar'
    export default foo
    export {bar}
    
    //等同于
    export {bar,foo as default}
    
模块导入

使用import关键字来导入模块,而且改关键字必须出现在模块的顶级。

if(true){
	import ...
	export ...
}
//都会报错

import后面跟的必须是纯字符串,可以是相对路径,也可以是绝对路径

import ... from '/a.js'

导入的模块是只读的,相当于const申明的变量一样,只能更改其属性。

import foo ,* as Foo from '/foo.js'
foo = 'boo' //报错
Foo.name = 'nick' //允许

命令导出可以使用*批量获取并赋值给相应的变量

export {bar,foo}
import * as Foo from '/foo.js'
Foo.bar
Foo.foo

默认导出就好像整个模块就是导出的值

import {default as foo} from './foo.js'
//等同与
import foo from './foo.js'