前言
在六月份,由于在一次需要项目打包的时候我完全没有头绪,因为之前没有特意去了解过,只是知道webpack
等一些工具是用来打包项目的,至于打包的格式、原理都一概不知(可能有看过,只是都忘了)。于是我对自己做了反思,拓展知识的时候一定要去了解它的历史和原理,不要觉得记住几个api
就会了,只要知道了原理,基于该知识的任何框架和工具都是手到擒来!
一开始打包的需求只是把我的脚本ES6
转成ES5
,后面顺藤摸瓜,查找了好多跟打包有关的知识,同时我又在看红宝书,让我的脑子一下子有点吸收不来,所以写下这篇文章记录一下自己脑中尚存的知识碎片。写文章的重点是,让自己散乱的知识点有意识去寻找关系,从而能够给自己梳理,查漏补缺o(╥﹏╥)o。
- 模块化 (
CommonJS
,AMD
,CMD
,UMD
,ES6
,IIFE
) - JavaScript 编译器(
bable
) - 打包工具(
rollup
,webpack
,father
) - 环境(
node环境
,浏览器环境
编译时
,运行时
)
这篇文章先对模块化进行梳理。
一. 模块化的理解
1. 什么是模块化?
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起。
- 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。
2. 模块化的发展历程
- js一开始并没有模块化的概念,直到
AJAX
被提出,前端能够像后端请求数据,前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理
。 - 当时使用
自执行函数(IIFE)
来解决这些问题,比如经典的jquery
就使用了匿名自执行函数封装代码,将他们全都挂载到全局变量jquery
下边,$(document).ready(function(){}
。 - 后来随着js的出场机会变多,逻辑交互变得更加复杂,功能越来越多,就需要拆分成多个文件,这就导致一个
HTML
页面需要加载多个script
,而且还可能出现命名冲突,同步加载导致页面卡顿,还要确保加载js
依赖正确。So。 - 2009 年 1 月,
Node.js
横空出世,它采用CommonJS 模块化规范
,同时还带来了npm
(全球最大的模块仓库) ,随后出现了AMD
、CMD
、UMD
、再到现在最流行的ESM
,都是根据JS
推出的模块化规范。
AJAX
不是JavaScript
的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML
,意思就是用JavaScript执行异步网络请求
。其实就是XMLHttpRequest
,通过onreadystatechange
异步回调获取数据达到更新效果。主要问题:不同浏览器需要写不同代码,并且状态和错误处理写起来很麻烦。但是jquery
把ajax
封装了一遍,不但不需要考虑浏览器问题,代码也能大大简化,所以我一度以为ajax
就是jquery
框架伟大的api
。o(╥﹏╥)o
二. 模块化规范
1. CommonJS
1)描述
- 它是
Node
环境中的JS
模块化规范,在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。 - 在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
2)特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
3)基本语法
module.exports = {}
或exports.XX = XX
导入,实际上每个文件都被视为独立的模块,每个模块的全局作用域就是module
(module 实际上不是全局的,而是每个模块的局部。),exports
就是一个导出的对象,require("文件路径")
导入模块。
// a.js
let value = "a";
let add = () => {
return value + 1
}
module.exports = {value, add}
// or
// exports.value = value
// exports.add = add
复制代码
// main.js
const a = require(./a.js) // “./”是相对路径
const test = a.add();
console.log(test) // "a" + 1 = "a1"
console.log(a.value) // "a"
复制代码
4) 加载机制
CommonJS
模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值(正在研究原理)。require
是按顺序同步加载的,而且是运行时加载。
5) 缓存机制
- 模块在第一次加载后被缓存。 这意味着(类似其他缓存)每次调用
require('foo')
都会返回完全相同的对象(如果解析为相同的文件)。 - 如果 require.cache 没有被修改,则多次调用
require('foo')
不会导致模块代码被多次执行。
6)运行
- node环境可以同步加载运行
- 浏览器环境要借助打包工具编译后运行
2. AMD
1)描述
AMD
是”Asynchronous Module Definition
“的缩写,意思就是”异步模块定义
“。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
2)特点
- 异步加载依赖,预先加载所有依赖。
- 使用require.js库,去官网下载导入项目。
3)基本语法
- 用
require.config()
指定引用路径等,用define()
定义模块,用require()
加载模块。
/** 网页中引入require.js及main.js **/
<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"],function($,_){
// some code here
});
复制代码
- 自己定义的模块
// math.js
// 如果需要引入其他依赖,第一个参数为["依赖名"]
define(function () {
var basicNum = 0;
var add = function (x, y) {
return x + y;
};
return {
add: add,
basicNum :basicNum
};
});
// main.js
require(['jquery', 'math'],function($, math){
var sum = math.add(10,20);
$("#sum").html(sum);
});
复制代码
3. CMD
1)描述
CMD
是另一种模块化方案,它与AMD
很类似,不同点在于:AMD
推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js
推广过程中产生的。CMD
规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD
规范整合了CommonJS
和AMD
规范的特点。在Sea.js
中,所有JavaScript
模块都遵循CMD
模块定义规范。
2)特点
3)基本语法
// module1.js
define(function(require, exports, module) {
module.exports = {
msg: 'I Will Back'
}
})
// main.js文件
define(function (require) {
var m1 = require('./module1')
})
复制代码
// html中引入sea.js和入口文件
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/modules/main')
</script>
复制代码
4. UMD
1)描述
UMD (Universal Module Definition)
, 希望提供一个前后端跨平台的解决方案(支持AMD
与CommonJS
模块方式),。
2)实现原理
- 先判断是否支持
Node.js
模块格式(exports
是否存在),存在则使用Node.js
模块格式。 - 再判断是否支持
AMD
(define
是否存在),存在则使用AMD
方式加载模块。 - 前两个都不存在,则将模块公开到全局(
window
或global
),也就是IIFE
(立即执行函数)。 UMD
的github
源码(jQuery
使用)
// 这里是没有模块依赖的简化代码
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// 当前环境有define方法,使用AMD规范.
define([], factory);
} else if (typeof exports === 'object') {
// Node环境.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(this, function () {
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
复制代码
5. ES6 Module
1)描述
- 在
ES6
前,实现模块化使用的是RequireJS
或者seaJS
(分别是基于AMD
规范的模块化库和基于CMD
规范的模块化库)。 ES6
引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
2)特点
- 编译时加载,在代码静态解析阶段就会生成。
ES6
的模块自动开启严格模式,不管你有没有在模块头部加上use strict
;- 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
- 每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
3)基本语法
export
命令可以出现在模块的任何位置,但必需处于模块顶层。import
命令会提升到整个模块的头部,首先执行。
// export1.js
const addV = (v) => {
return v + value
}
export const value = 123
export default addV
复制代码
// as 重新命名
import addV as anything, {value} from "./export1"
console.log(value) // 123;
console.log(anything(1)) // 124;
复制代码
三. 总结
CommonJS
服务器端和浏览器端都可以用,服务器端是动态同步加载
模块的,浏览器端需要先编译打包
再引入。AMD
和CMD
都专供浏览器端,动态异步加载模块,分别需要引入Require.js
和Sea.js
。UMD
是兼容CommonJS
和AMD
,以及IIFE
的打包格式。ES6
服务器端和浏览器端都可以用- 在
node
环境使用ES6模块化规范
有两种方式,后缀改为.mjs
或者在package.json
里面加type: module
。 - 在浏览器使用需要用打包工具编译打包引入。
- 在
end:)有新发现再补充,下一篇文章主要总结一下自己使用过的打包工具,查漏补缺,be better。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END