js速记--前端模块化

83 阅读3分钟

前端开发,经历了最初的全局fuction,然后到命名空间,然后是IIFE,再然后是commonJs规范,AMD规范,CMD规范,ESModule。现在主流使用的是commonJs规范和ESModule规范,其中commonJs规范主要是使用在node环境下,ESModule规范主要是使用在浏览器端,现在新版本node也已经支持ESModule规范,需要在package.json中将type设置为"module"。

一、commonJs规范

NodeJs环境下默认使用的就是commonJs规范,每一个文件就是一个模块,有以下特点:

  • 模块导出内容,使用module.exports 或者 exports,导入模块使用require()
  • 导入模块是同步执行
  • 导入模块获得的对象是导出对象的拷贝
  • 模块第一次被别的模块导入时,会默认执行模块代码,再次被导入时,使用的是缓存的模块对象
  • exports 是对 module.exports 的引用,所以使用exports只能通过exports.name的形式,如果exports = {}这种,则不能实现导出
  • 模块的加载,在服务器端是运行时同步加载,再浏览器端是异步加载

二、ADM规范

由于commonJs规范是同步执行的,适合在服务的执行,因为服务端模块文件都在本地磁盘,访问比较快,而浏览器端访问模块,需要提前将模块打包编译下载到浏览器端,所以浏览器端更适合异步的模块访问方式,需要用到哪个模块,就发送请求获取,于是就有了AMD规范

AMD规范最佳实践就是 require.js。每个文件就是一个模块,通过define()声明模块定义,第一个参数是当前模块依赖的其他模块,可省略,程序的入口是require()

// a 模块
define(function() {
    // ...
})
// b模块
define([a], function() {
    // ...
})

require.config({
    baseUrl: '', // 模块的基本路径
    paths: {
        'a': './modules/a',
        'b': './modules/b'
    }
})

// 执行入口
require(['a'], function() {
    // ...
})

在执行require()时,会先异步加载依赖资源并执行依赖的代码,待所有资源都加载完成,会执行回调函数。如果依赖代码还依赖了其他依赖,则会先加载其他依赖资源

特点:

  • 异步加载依赖,加载完依赖,会立即执行
  • 依赖是并行加载,不能保证依赖间的执行顺序

三、CMD规范

CMD规范的最佳实践是seqjs,是对AMD规范的优化,解决了ADM中依赖加载就执行的问题,依赖是先加载,不会立刻执行,只有通过require()引用了依赖,才会执行依赖的代码。

通过define(function(require, exports, module) {})声明模块,其中require、exports、module遵循commonJs规范

// a.js
define(function(require, exports) {
    exports.name = 'a'
})

// b.js
define(function(require) {
    var a = require('./a')
    console.log(a.name)
})

// 入口
seajs.use('./b')

require()导入模块是同步执行的,如果要异步导入模块,可以使用require.async()

需要注意的是,CMD规范规定了依赖资源是先加载,所以对于一个个依赖,如果单独加载,可能会有很多请求,一般可以搭配构建工具解决。

四、ESModule

es6模块化是在编译时解析模块依赖关系,确定输入输出。导入模块获取的是导出对象的引用,修改依赖的导出内容,会影响到其他模块。

根据规范,使用export导出,import导入

// a.js
let name = 'a'

setTimeout(() => name = 'b', 1000)

export {name}

// b.js
// 这里{}不是结构,是和export {} 组合使用的
import {name} from './a.js'

console.log(name) // a
setTimeout(() => {
    console.log(name) // b
}, 1500)

导出模块内容时,也可以使用 export default 导出一个默认对象,那么在导入时也就不需要{}

// a.js
var name = 'a'
export default name
// b.js
import name from './a.js'

异步导入模块

import('./a.js') // 返回的是promise对象

浏览器支持使用ESModule,只需要在script标签上加type=module

<script type="module"></script>

nodeJs环境下使用ESModule

// package.json
{
    type: 'module'
}

与commonJs差异:

  • commonJs解析模块是在运行时,ESModule是在编译时
  • commonJs输出的是值的拷贝,ESModule输出的是值的引用
  • commonJs是同步导入模块,ESModule即支持同步也支持异步