返回专题

专题 2021

JS 模块化详解

模块化总结笔记

BlogJS ModuleNPMNodeJS前端工程必读系列
本文目录17
  1. 模块化详解
  2. 全局函数模式
  3. Namespace 模式
  4. IIFE 模式
  5. CommonJS
  6. 特点
  7. 代码
  8. AMD
  9. 特点
  10. 代码
  11. CMD
  12. 特点
  13. 代码
  14. ES6 Module
  15. 特点
  16. 代码
  17. Reference

模块化详解

大致对比:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
  • 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行
  • CMD 推崇依赖就近,AMD 推崇依赖前置

总结:

  • CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
  • CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

全局函数模式

将不同的功能封装成不同的全局函数

Namespace 模式

封装成简单的对象

JavaScript
let myModule = {
  data: 'www.baidu.com',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data

IIFE 模式

匿名函数自执行闭包, 早期 JS 插件, JQuery, Lowdash, 以及现在的 webpack 都在用

JavaScript
// module.js文件
(function(window, $) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar }
	// IIFE模式增强 : 引入依赖, 这就是现代模块实现的基石
})(window, jQuery)

CommonJS

在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

特点

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。
  • 输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
  • CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性

代码

JavaScript
// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter);  // 3
incCounter();
console.log(counter); // 3
// 这是因为counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

AMD

AMD规范则是非同步加载模块,允许指定回调函数, 一般使用 RequireJS

特点

  • 每个资源只会请求一次
  • 引入的js文件顺序固定, 能够清楚地显示依赖关系
  • 依赖到的资源会在第一时间加载

代码

AMD 定义:

JavaScript
//定义没有依赖的模块
define(function(){
   return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
   return 模块
})
require(['module1', 'module2'], function(m1, m2){
   使用m1/m2
})

RequireJS 库:

JavaScript
// dataService.js文件
// 定义没有依赖的模块
define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  return { getMsg } // 暴露模块
})

//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  // 暴露模块
  return { showMsg }
})

// main.js文件
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      //映射: 模块标识名: 路径
      alerter: './modules/alerter', //此处不能写成alerter.js,会报错
      dataService: './modules/dataService'
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()

// <!-- 引入require.js并指定js主文件的入口 -->
// <script data-main="js/main" src="js/libs/require.js"></script>

CMD

特点

  • CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行
  • CMD规范整合了CommonJS和AMD规范的特点

代码

CMD 定义:

JavaScript
//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})

//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

SeaJS 使用:

JavaScript
// module1.js文件
define(function (require, exports, module) {
  //内部变量数据
  var data = 'atguigu.com'
  //内部函数
  function show() {
    console.log('module1 show() ' + data)
  }
  //向外暴露
  exports.show = show
})

// module2.js文件
define(function (require, exports, module) {
  module.exports = {
    msg: 'I Will Back'
  }
})

// module3.js文件
define(function(require, exports, module) {
  const API_KEY = 'abc123'
  exports.API_KEY = API_KEY
})

// module4.js文件
define(function (require, exports, module) {
  //引入依赖模块(同步)
  var module2 = require('./module2')
  function show() {
    console.log('module4 show() ' + module2.msg)
  }
  exports.show = show
  //引入依赖模块(异步)
  require.async('./module3', function (m3) {
    console.log('异步引入依赖模块3  ' + m3.API_KEY)
  })
})

// main.js文件
define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

// 在index.html中引入
// <script type="text/javascript">
//   seajs.use('./js/modules/main')
// </script>

ES6 Module

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

特点

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
  • 因为静态化特性, 方便实现 Tree Shaking, 代码提示等功能

代码

JavaScript
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };

/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

Reference

前端模块化详解(完整版) · Issue #48 · ljianshu/Blog

You can't perform that action at this time.
https://github.com/ljianshu/Blog/issues/48

「万字进阶」深入浅出 Commonjs 和 Es Module

今天我们来深度分析一下 Commonjs 和 Es Module,希望通过本文的学习,能够让大家彻底明白 Commonjs 和 Es Module 原理,能够一次性搞定面试中遇到的大部分有关 Commonjs 和 Es Module 的问题。 老规矩我们带上疑问开始今天的分析🤔🤔🤔: 1 Commonjs 和 Es Module 有什么区别 ? 2 Commonjs 如何解决的循环引用问题 ? 3 既然有了 exports,为何又出了 module.
https://juejin.cn/post/6994224541312483336

GitHub - systemjs/systemjs: Dynamic ES module loader

Dynamic ES module loader.
https://github.com/systemjs/systemjs

返回首页
上一篇面试问题参考下一篇开源协议

Discussion

留言与讨论

想法、补充和不同意见都欢迎。