# 31、模块化历程

模块化的引入主要是用于解决命名冲突、代码复用、代码可读性、依赖管理等,可以提高项目的维护性和开发效率

# AMD 异步模块定义

AMD

AMD全称 Asynchronous Module Definition 异步模块定义,是为浏览器环境设计的,因为 CommonJS 模块系统是同步加载的,当前浏览器环境还没有准备好同步加载模块的条件。 AMD定义了一套 JavaScript 模块依赖异步加载标准,来解决同步加载的问题。

AMD并非原生js支持,是RequireJs模块化开发当中的产物,AMD依赖于RequireJs函数库,打包生成对应效果的js代码

主要由require.config()define()require 三个函数实现。

  • require.config():用于声明基本路径和模块名称;
  • define(id?,dependencies?,factory):用于定义模块对象,参数如下
    • id:可选,为定义模块的标识,默认为模块文件名不包括后缀
    • dependencies:可选,是 当前模块依赖的模块路径数组
    • factory:为工厂方法,初始化模块的函数或者对象,如果为函数将会只执行一次,如果是对象将作为模块的输出
// 定义math.js 模块
define(function(){
  var basicNum = 0;
  var add = function(x,y){
    return x + y
  }
  return {
    add : add,
    basicNum : basicNum
  }
})  
  • require():则用于加载模块并使用。

AMD规范处于浏览器环境中,是一种异步模块加载规范,在使用时,首先要加载模块化规范实现文件require.jsJS主文件,示例如下:

<!-- js/require.js 是实现AMD规范的类库文件,是任何使用AMD规范的网页都需要加载的; -->
<!-- js/main 是开发者的代码主文件,在这个文件中加载并使用自定义模块 -->
<script src="js/require.js" data-main="js/main"></script>
// main.js 入口文件/主模块
// 首先用config()指定各模块路径和引用名
require.config({
  baseUrl:'js/lib',
  paths: {
    'jquery': 'jquery.min', // 实际路径为js/lib/jquery.min.js
    'underscore': 'underscore.min'
  }
});
// 执行基本操作
require(['jquery','underscore','math'],function($,_,math){
  // $代表jquery  _代表underscore
  var sum = math.add(10,20);
  $('#sum').html(sum);
})

优点:

  • 解决了模块同步加载的问题
  • 可以并行加载多个模块

缺点:

  • 对于依赖的模块无论实际需要与否,都会先加载并执行
  • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅

为了解决AMD规范强制 前置加载并执行的问题,CMD规范应运而生

# CMD 通用模块定义

CMD

CMD全称Common Module Definition通用模块定义,CMDAMD规范一样,同样是一种 JS 语言自实现的模块化方案。

AMDCMD也有一个函数库SeaJSRequireJS类似的功能

AMD的不同:

  • AMD规范是依赖前置、模块提前加载并执行;
  • CMD规范是依赖后置、模块懒加载再执行;

CMD推崇一个文件一个模块,使用定义模块define(factory)factory可以是函数,可以是其他有效的值,如果factory是函数,则该函数的参数依次为require,exports,module

  • require:内部引入模块的时候调用
  • exports:用来向外部导出本模块的API
  • module:一个对象,上面存储了当前模块相关的属性和方法
// CMD写法
define(function(require,exports,module){
  // 在需要的时候声明、加载
  var a = require('./a'),
  c = require('./c');
  a.doSomething();
  if(false){
    var b = require('./b');
    b.doSomething();
  }
})

/* sea.js:模块加载器 */

// 定义模块 math.js
define(function(require,exports,module){
  var $ = require('jquery.js');
  var add = function(a,b){
    return a + b;
  }
  exports.add = add;
})
// 加载模块
seajs.use(['math.js'],function(math){
  var sun = math.add(1,2)
})

# CommonJS

CommonJS模块规范(同步),通常用于Nodejs中的模块化, 服务器端的 Node.js遵循 CommonJS 规范,该规范的核心思想是允许模块通过 require方法来同步加载所要依赖的其他模块,然后通过 exportsmodule.exports 来导出需要暴露的接口。

# CommonJS 模块的加载原理

原理

CommonJS 模块都是一个脚本文件,require 命令第一次加载该模块时就会执行整个脚本,然后在内存中生成该模块的一个说明对象。

{  id: "", //模块名,唯一  exports: { //模块输出的各个接口    ...  },  loaded: true, //模块的脚本是否执行完毕  ...}

以后用到这个模块时,就会到对象的exports属性中取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。

CommonJS模块是加载时执行,即脚本代码在require时就全部执行了。一旦出现某个模块被‘循环加载’,就只输出已经执行的部分,没有执行的部分不会输出 CommonJS的模块是动态加载的。如果你 require 了一个模块,那就相当于你执行了该文件的代码并最终获取到模块输出的 module.exports对象的一份浅拷贝,并且重复引入的模块并不会重复加载,再次获取模块只会获取之前加载的模块的拷贝。

拥有4个环境变量modul、exports、require、global

// 模块定义: lib.js
var num = 3
function incNum(){
  num++
}
module.exports = {
  num:num,
  incNum:incNum
}

// lib.js的 exports对象 在加载后已经成了
module.exports = {
  num:3,
  incNum: incNum // 这里是incNum函数的引用
}
// 在main.js中 使用lib模块
var mod = require('./lib') 
// 而 mod 从 module.exports对象浅拷贝而来,调用 mod.incNum() 相当与调用了lib.js模块中的 incNum函数,更改的是lib.js中 num 的值,与mod对象无关

console.log(mod.num) // 3
mod.incNum() // 这将改变模块lib.js中的count值;
console.log(mod.num) // 3
var mod2 = require('./lib)
console.log(mod2.num) // 3
// 为什么输出都是 3

缺陷CommonJS是一种动态加载、拷贝值对象执行的模块规范。每个模块在被使用时,都是在运行时被动态拉取并拷贝使用,模块定义是唯一的,但是输出的都是拷贝对象。

# ES6 模块化

js模块化

ES6中提供了js的模块化规范:esmoduleES6 esmodule规范是使用import语句导入模块,export语句导出模块,输出的是对值的引用 客户端可以基于ES6来使用一些模块(要使用谷歌60+版本以上的浏览器) esmodule是一个规范:规定了如下规范

  • 如何定义模块( 一个JS就是一个模块)
  • 如何导出模块(使用export)
  • 如何导入模块(import)

# ES6 module 加载原理

原理

ES6 moduleCommonJS有本质上的区别。ES6 module 是静态编译(在编译时就能够确定模块的依赖关系,以及输入和输出的变量,完成模块的加载),动态引用(导出和导入的都是对值的引用,模块中的值变了之后,引用到的值的地方也会改变),遇到模块加载命令import时不会立即执行模块,只是生成一个指向被加载模块的引用。

//odd.js
import {even} from "./even.js";

export function odd(n){  
  return n != 0 && even(n-1);
}

//even.js
import {odd} from "./odd";

var counter = 0;

export function even(n){  
  counter ++;  
  console.log(counter);    
  return n == 0 || odd(n-1);
}


//index.js
import * as m from "./even.js";

var x = m.even(5);
console.log(x);
var y = m.even(4);
console.log(y);


// 运行index.js 结果
1
2
3
false
4
5
6
true

# 如何在HTML中引入一个模块

依然用script标签导入,不过需要规定type类型

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--需要type为模块类型-->
<script src="main.js" type="module"></script>
</body>
</html>

# 如何导出一个模块

导出

一个JS就相当于一个模块,把需要用的导出用export

导出也有两种方式:

  • 1、以默认的形式导出(必须加default
let str='我很帅';
let str1='我很英俊';
export default {
    str,str1
}
  • 2、分别导出
//此模块为a.js(下面导入时会用到)
//会将str和str1放到一个对象内导出{str:'我很帅',str1:'我很英俊'}
export let str='我很帅';
export let str1='我很英俊';

# 如何导入一个模块

导入

使用import来导入一个模块,最好在需要模块的顶部导入 import具有声明作用(之后再重复声明会报错),而且声明会进行变量提升。 两种写法:

  • 1、用ES6的解构赋值:把需要的解构出来
console.log(str);//我很帅(最好不要这样用)
//导入时可用ES6中解构赋值的形式从另一个模块中解构出需要用的内容(一个个的导入)
import {str,str1} from './a.js';//自定义模块路径前必须要加 ./ ,后缀必须要加.js,不然会报错
console.log(str,str1);//我很帅 我很英俊
  • 2、用* as obj form xxx把另一个模块中的东西批量导入到我需要的文件中
//批量导入
import * as obj from './a.js';
console.log(obj.str, obj.str1);//我很帅 我很英俊
  • 3、把默认的导入(只有以默认的形式导出才能这样导入)
import val from './a.js';
console.log(val);//{str: "我很帅", str1: "我很英俊"}

# ES6:export default 和 export 区别

区别

  • 1.exportexport default均可用于导出常量、函数、文件、模块等
  • 2.你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
  • 3.在一个文件或模块中,export、import可以有多个,export default仅有一个
  • 4.通过export方式导出,在导入时要加{ }export default则不需要
  • 5、当使用export导出多个的时候,导入时可以使用import * as xxx form './aaa';
// a.js 模块
//存放action的类型
export const ADD_TODO='ADD_TODO';
export const CHANGE_SELECTED='CHANGE_SELECTED';
export const DELETE='DELETE';
export const CHANEG_TYPE='CHANEG_TYPE';

// b.js 模块要引入a模块导出的内容
//把从a模块中导出的所有内容作为一个对象赋值给obj
import * as obj form './a'
//用的时候可以用obj.ADD_TODO来调用
obj.ADD_TODO

# ES6 模块规范 和 CommonJS 规范的区别

区别

  • ES6 module 是解析(是解析 不是编译)时静态加载、运行时动态引用,所有引用出去的模块均指向同一个模块对象。
  • CommonJS规范是运行时动态加载、拷贝值对象使用,每一个引用出去的模块都是一个独立的对象。

ES6 module的运行机制和CommonJS不一样,遇到模块加载命令import时不去执行这个模块,只会生成一个动态的只读引用,等到需要用到这个值时,再到模块中取值,也就是说模块中的值变了,那输入的值也会发生变化

上次更新: 3/17/2021, 7:41:02 PM