这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
模块化
模块化及其优点:
模块化就是将系统分离成独立功能的模块,这样我们需要什么功能,就加载什么功能。
模块化的优点:
- 避免命名空间的冲突(减少命名空间的污染)
- 更好的分离,实现按需加载
- 提高可代码的复用性
- 提高了代码的维护性
JS模块化大致发展过程
CommonJS(服务端) =>AMD(浏览器) => CMD => ES6 Module模块化
模块化规范的种类
模块化规范的发展趋势
前端模块规范有三种:CommonJs,AMD和CMD。
CommonJs用在服务器端,AMD和CMD用在浏览器环境
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。\
AMD:提前执行(异步加载:依赖先执行)+延迟执行
CMD:延迟执行(运行到需加载,根据顺序执行)
CommonJS 规范
CommonJS 的概念
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个独立模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
- 每个文件都可以作为一个模块(这里的文件指的是js文件)
- 在服务器端:模块的加载是运行时同步加载的
- 在浏览去端:模块需要提前编译打包处理,不然浏览器不能识别require语法
CommonJS 的使用
主要分为定义模块和引入模块两个步骤。
定义模块
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
// 通过`module.exports`输出变量`x`和函数`addX`
let x = 1;
let addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
引入模块
require方法用于加载模块。
var example = require('./example.js');
CommonJS 的特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载是一项阻塞操作,也就是同步加载。模块加载的顺序,按照其在代码中出现的顺序。
模块的加载机制
CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
AMD 规范
ADM 概念
AMD规范全称是Asynchronous Module Definition,即异步模块加载机制,主要用于浏览器。它完整描述了模块的定义,依赖关系,引用关系以及加载机制。由于该规范不是原生js支持的,使用AMD规范进行开发的时候需要引入第三方的库函数,AMD对应的就是鼎鼎大名的RequireJS。
- CommonJS规范出现后,在Node开发中产生了非常好的效果,开发者希望借鉴这个经验来解决浏览器JS的模块化
- 但是大部分人认为浏览器和服务器的环境差别太大,毕竟浏览器JS是通过网络动态以此加载的,而服务器的JS是保存在本地磁盘中。因此浏览器需要实现异步加载,模块在定义的时候就必须先知名它所需要依赖的模块,然后把本模块的代码写在回调函数中执行,最终衍生出了AMD规范
- AMD的主要思想时异步模块,主逻辑在函数回调中执行。
AMD 的使用
define和require这两个定义模块、调用模块的方法,合称为AMD模式。它的模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。
使用define用来暴露模块,使用require用来引入模块;require()异步加载模块,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
AMD模式可以用于浏览器环境,并且允许异步加载模块,也可以根据需要动态加载模块。
define方法:定义模块
define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。
按照是否依赖其他模块,可以分成两种情况讨论。第一种情况是定义独立模块,即所定义的模块不依赖其他模块;第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。
require方法:调用模块
require方法用于调用模块。它的参数与define方法类似。
RequireJS主要解决了两个问题:
- 多个
js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器 js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
AMD规范与CommonJS规范的兼容性
CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。
CMD 规范
CMD 的概念
CMD(Common Module Definition) ,通用模块定义,它解决的问题和AMD规范是一样的,只不过在模块定义方式和模块加载时机上不同,CMD也需要额外的引入第三方的库文件,SeaJS,SeaJS推崇一个模块一个文件
- AMD/RequireJS的JS模块实现有很多不优雅的地方,主要原因不能以一种更好的管理模块的依赖加载和执行;
- 那么就出现了SeaJS,SeaJs遵循的是CMD规范,CMD规范在AMD的基础上改进的一种规范,解决了AMD对依赖模块的执行时机的问题;
- SeaJS模块化的顺序是:模块化预加载=》主逻辑调用模块时才执行模块中的代码;
- SeaJS的用法和AMD基本相同,并且融合了CommonJS的写法;
CMD 的使用
(对于模块的引入,具有同步和异步两中方式)
define 是一个全局函数,用来定义模块
SeaJS提供了seajs.use来加载模块
SeaJS的出现,是CommonJS在浏览器的践行者,并吸收了RequireJS的优点
UMD(AMD + CommonJS)
CommonJS 适用于服务端,AMD、CMD 适用于web端,那么需要同时运行在这两端的模块就可以采用 UMD 的方法,使用该模块化方案,可以很好地兼容AMD、CommonJS语法。UMD 先判断是否支持Node.js的模块(exports)是否存在,存在则使用 node.js 模块模式。再判断是否支持 AMD(define是否存在),存在则使用AMD方式加载模块。由于这种通用模块解决方案的适用性强,很多JS框架和类库都会打包成这种形式的代码。
ES6中的Module模块
仅支持静态导入导出
ES6 规范只支持静态的导入和导出,ES Module 需要在编译时期进行模块静态优化,也就是必须要在编译时就能确定。
为什么要这么做,主要是两点:
- 性能,在编译阶段即完成所有模块导入,如果在运行时进行会降低速度
- 更好的检查错误,比如对变量类型进行检查
ES6 Module 使用
- 模块功能主要由两个命令构成:
export和import export用于暴露接口,import用于引入模块
在使用 ES Module 值得注意的是:import 和 export 命令只能在模块的顶层,在代码块中将会报错,这是因为 ES Module 需要在编译时期进行模块静态优化,import 和 export 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,这种设计有利于编译器提高效率,但也导致无法在运行时加载模块(动态加载)。
对于这个缺点,TC39 有了一个新的提案 – Dynamic Import,提案的内容是建议引入 import()方法,实现模块动态加载。
// specifier: 指定所要加载的模块的位置
import(specifier)
-
import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。 它是运行时执行,也就是说,什么时候运行到这句话,就会加载到指定的模块。另外,import()函数所加载的模块没有静态链接关系,这点也是与import语法不同
-
注意的时ES6 的Module语法有些浏览器是不支持的,因此需要Babel先进性转码,将import和export命令转成ES5语法才能被浏览器解析。
CommonJS、AMD、CMD、ES6 Module的区别
AMD与CMD区别
-
模块定义时对依赖的处理不同
AMD推崇前置依赖,在定义模块时就要声明其依赖的模块;而CMD推从就近依赖,只有在用到某个模块时再使用require导入;
-
对依赖模块的处理机制不同
首先AMD和CMD对模块的加载方式都是异步的 不过区别在于AMD当加载了依赖模块之后立即执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致; 而CMD加载完依赖模块之后,并不会立即执行,等所有的依赖模块都加载好之后,进入回到函数逻辑,遇到require语句的时候,才执行对应的模块,这样模块的执行顺序就和我们书写的时候一致了
ES6模块与CommonJS模块加载的区别
CommonJS时运行时加载,因为ComminJS加载是先加载整个模块,生成一个对象(这个对象包含了path这个模块的所有API),然后再从这个对象上面读取方法-----运行时加载
ES6是编译时加载,ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态定义阶段就会生成-----编译时加载
//ES6模块
import { basename, dirname, parse } from 'path';
//CommonJS模块
let { basename, dirname, parse } = require('path');
以上这种写法与CommonJS的模块加载有什么不同?
- 当require path 时,CommonJS会将path模块运行一遍,并返回一个对象,这个对象包含path模块的所有API。
总结
模块化就是将系统分离成独立功能的模块,这样我们需要什么功能,就加载什么功能。
模块化的优点:
- 避免命名空间的冲突(减少命名空间的污染)
- 更好的分离,实现按需加载
- 提高可代码的复用性
- 提高了代码的维护性
JS模块化发展产物:CommonJS(服务端) =>AMD(浏览器) => CMD => ES6 Module模块化
CommonJs主要用在服务器端,AMD和CMD用在浏览器环境
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
ES6 规范只支持静态的导入和导出,与CommonJS 的运行时加载不同的是ES6是编译时加载,ES Module 是在编译时期进行模块静态优化。
如果这篇文章帮到了你,记得点赞👍收藏加关注哦😊,希望点赞多多多多...
文中如有错误,欢迎在评论区指正