六月学习记录
前言
在六月份,由于在一次需要项目打包的时候我完全没有头绪,因为之前没有特意去了解过,只是知道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。
- 在