现在的前端开发中根本离不开组件化、模块化的开发方式,这些模块有各种各样的规范,比如:

1.CommonJS规范

主要用在与Nodejs服务端,在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。模块本身是输出的是一个值的拷贝,意思就是说拿到模块后,对其中exports的变量进行修改的时候,是不会影响到原来的模块的

1.1.导出模块

导出模块的方式有两种,两种写法都可以,module.exports是对exports的一个引用

module.exports = {}

exports.xxx = 'xxx'

1.2.引入模块

指的说的一点就是这个路径可以是一个js表达式计算的结果

const module = requore('路径')

2.AMD规范

出现AMD规范的原因是因为CommonJS以服务端同步的方式加载本地的模块(文件),服务器端所有的模块都存放在本地硬盘中,可以同步加载完成,等待时间就是硬盘的读取时间;而如果在客户端使用这个规范的去请求服务器上的模块资源的时候,请求的时间就取决于当前的网络状态了,加上CommonJS是一个同步的加载,那页面就会阻塞、卡顿,所以也就有了AMD(Asynchronous Module Definition)规范,主要用在客户端

2.1定义(导出模块)

定义模块使用define(id?, dependencies?, factory)方法,id为你自定义模块的名称,dependencies为你当前模块所依赖的其他模块集合的路径数组,factory则是实际的定义模块的方法。也就是说一个简单AMD模块甚至直接传个factory函数就行了,这个回调函数里面的入参是dependencies数组里面加载成功的依赖

define(function () {
  return
})

2.2引入模块

require(['module1', 'module2'], function (m1, m2) {
  // 使用module1、module2
})

需要注意的地方浏览器本身是不支持这个规范的,要使用的话必须依赖于require.js

3.CMD规范

CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近执行,延迟执行

3.1定义(导出模块)

define(id?, dependencies?, factory),关于定义它和AMD规范很像,包括入参,不同地方在于factory回调函数的入参不同require, exports, module,可以通过这些关键字再导出或者引入相关依赖,然后dependencies一般不会去使用,通常会在factory的回调函数中用require关键字去加载其他的依赖。

它和AMD规范最大的不同,就是定义模块时,AMD会先去加载dependencies的内容,然后加载模块本身的内容,CMD的话会加载就近的依赖,意思是遇到了require关键字的时候才会去加载模块。所以使用CMD规范的话,可以将require关键字写在模块的顶层,当然浏览器环境也要依赖与sea.js

define(function (require, exports, module) {
  // require对象可以在模块中引入其他模块
  exports.xxx = value
  module.exports = value
})

4.ES6的模块规范

1.导出:ES6模块有两种模块导出方式:命名式导出(名称导出)、默认导出(自定义导出)

命名式导出:

// 最基本的
export const a = '1'

// 导出使用别名
const a = '1'
export { a as A }

默认导出通常会在export后面加上一个default:

const a = '1'
export default a

2.import导入模块

export const a = '1'
import { a } from '路径'


export default const a = '1'
import a from './module'

上述的Commone.jsCMDAMD规范的特点就是运行时加载、模块值的拷贝

ES6模块的特点则是静态编译时加载、按需加载、模块值的引用,每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域

requireJS会先尽早地执行(依赖)模块, 相当于所有的require都被提前了, 而且模块执行的顺序也不一定100%就是先mod1再mod2,所以使用requireJS的时候模块与模块之间应当尽量没有业务或者逻辑的交叉

5.UMD规范

所谓UMD (Universal Module Definition),就是一种javascript通用模块定义规范,让你的模块能在javascript所有运行环境中发挥作用。顾名思义,就是兼容上述所有规范,实现方式是通过判断当前脚本运行环境是否支持相应的关键字来实现的

(function (root, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
    define(factory())
  } else if (typeof define === 'function' && define.cmd) {
    define(function (require, exports, module) {
      module.exports = factory()
    })
  } else {
    root.curModule = factory();
  }
}(this, function () {
  return {
    name: '我是一个umd模块'
  }
}))

// 然后在使用的地方通过require或者根节点.curModule的形式引用

实际应用场景1:

Webpack -> Output选项中,可以配置library库,我们可以设置打包后的文件暴露出去的模块名,并且可以指定生成支持某个规范的

output: {
  library: 'MyLibrary', // 暴露出的模块名
  libraryTarget: 'amd', // 指定的规范
},

打包代码生成,即生成一个兼容的UMD模块

(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if (typeof define === 'function' && define.amd)
    define([], factory);
  else if (typeof exports === 'object')
    exports["MyLibrary"] = factory();
  else
    root["MyLibrary"] = factory();
})(typeof self !== 'undefined' ? self : this, function () {
 
});

实际应用场景2:

对于一个大型的应用来说,其复杂的应用以及功能用户只会用到一部分,所以,我们可以在适当的时候去请求相应的模块,这些模块的资源放在服务端,从而减轻项目的体量以及减少项目的开发编译时间。对于这些远程的模块,就可以使用UMD模块封装,兼容不同环境,然后本地去请求。

要做一个umd-loader,通过传入的url、name去加载第三方的模块,然后创建script标签插入到模板文件的head当中,再就是视图里面自己处理逻辑,首先我在本地定义好这个class,确定好相应的加载逻辑

export class UMDLoader {
  static isExist (name) {
    return window.hasOwnProperty(name)
  }
  static getModule (name) {
    return window[name]
  }
  static getRemoteModule ({ name, url }) {
    if (UMDLoader.isExist(name)) {
      return Promise.resolve(UMDLoader.getModule(name))
    }
    return new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.type = 'module'
      script.url = url
      script.onload = () => {
        return resolve(UMDLoader.getModule(name))
      }
      script.onerror = () => {
        return reject(new Error('加载失败'))
      }
      document.head.appendChild(script)
    })
  }
}

在视图代码中使用的时候

import React, { useEffect } from 'react
import { UMDLoader } from '路径'

const TestView = () => {
  useEffect(() => {
    UMDLoader.getRemoteModule({ url: 'js模块地址', name: '名字' })
      .then((module) => {
        console.log(module)
      }, (e) => {
        console.log(e)
      })
  }, [])
}