前端模块化的背景
js是为了页面动画+表单提交而设计的,无模块化+命名空间的概念。
js模块化演化过程
-
全局函数
将不同的功能封装成不同的函数
缺点:容易引起全局命名冲突
function m1() {}
funciton m2() {}
2.简单对象的方式
-
减少了全局变量,解决了命名冲突
缺点:数据不安全,外部可以直接修改内部模块的数据
let myModule ={
data:'测试123',
foo() {
console.log(this.data);
}
}
myModule.data = '123'
myModule.foo(); // 外部调用 123
3.IIFE模式
-
数据私有,外部只能通过暴露的方法使用
let getName = (function(dep1,dep2){ let data = '测试数据'; let obj = { data, } function test1 () { data = 'ffff' obj.data = '对象的更改' console.log(data+'1'); } return { test1, data, obj } })('name','age'); getName.test1(); // ffff1 , 只能动态操作数据返回 console.log(getName) // {data:测试数据,obj:{data:对象的更改}} // 简单值,不会更改; 对象的引用会更改
揭示模式 :上层无需了解底层实现,仅关注抽象
- 继续模块化展开
- 转向框架 :juery | vue | react
- 设计模式
4.script 标签引入
- 请求过多
- 依赖模糊
- 难以维护
问题:Script的标签的参数:normal async 和 defer 的区别?
总结:
-
normal:解析立即阻塞,立即执行当前的script,会阻塞渲染,结束之后再进行下一个script
-
Defer: 解析到开始异步加载,解析完成后开始执行,defer顺序执行;适合场景:主路径优先(先下载资源,让用户先看到页面,在执行逻辑)
-
Async::解析开始异步加载,下载完成后开始执行并阻塞渲染,执行完成之后继续渲染,async没有顺序。模块化体验优先(先下载资源,会重复执行导致页面重绘,最后执行绘制比较好)
什么是模块化
- 将一个复杂的程序依据一定的规则封装成几个块或者文件,并组合在一起
- 块的内部数据是私有的,只对外暴露一些接口与外部通信
模块化的好处
- 避免命名冲突
- 更好的代码分离,按需加载
- 更高的复用
- 更高的维护性
模块化规范
commonjs
nodejs的模块化规范,在服务端最先实现。简称cjs,也可以在浏览器环境使用
1.概念
- 任何一个文件就是一个模块;
- 一个文件中定义的变量、函数、类都是私有的,对其他文件不可见;
- 只导出接口与别的模块交互 2.基本语法
module.exports = value; // 导出
exports.xxx = value
const a = require('sdfsdf') // 引入
3.cjs特点
- 模块可以多次加载,但是只会在第一次加载时运行一次;运行结果会被缓存
- 顺序加载
- 模块加载机制,一旦输出一个值,内部变化就不会影响不到这个值
var counter = 3;
var obj ={
counter:1
}
function incCounter () {
counter++;
obj.counter = 2;
}
module.exports = {
counter,
obj,
incCounter
}
const { counter,obj,incCounter} = require('./testnode.js');
console.log('incCounter',incCounter())
console.log('counter',counter); // 3 ,有缓存
console.log('obj',obj); // 对象的引用 obj:{count:2}
4.CJS为什么要设计成同步
- Node的模块文件一般都是已经存在于本地硬盘,所以加载起来比较快,没必要采用非同步模式
AMD 规范
1.概念
一种异步加载模块+回调函数的思想实现,主要代表库是require.js。cjs是同步加载的,在浏览器环境中希望是异步加载的
2.语法
- define 定义代码为块;
- 通过require方法,实现代码的模块加载。
define('name',[dep,dep2],(dep1,dep2) => {
// 逻辑
return { // 暴露接口
}
})
CMD 规范
1.概念 专门用于浏览器端,模块加载都是异步的。代表sea.js AMD & CJS 的结合体
2.语法
define('module',[deps],function(require,exports,module){
})
ES6 规范
1.概念 js模块化的官方实现
2.语法
import a form 'module';
export const a = 'sfs'; // 必须知道加载的变量名字或者函数名字,否则无法加载
export default {} // 默认导出,是可以让用户不用阅读文档就能加载模块使用
3.特点 值的引用,动态的引用,不会缓存值,模块变量绑定其所在的模块
export let counter = 3;
export function incCounter() {
counter++;
}
import {counter,incCounter} from './lib';
console.log(counter) // 3
incCounter();
console.log(counter) // 4 没缓存,值的引用
CJS && ES6 Module
-
区别
cjs 模块输出是一个值的拷贝,es6是输出的值的引用
-
cjs运行时加载, es6是编译时输出接口
cjs导出的是一个对象,脚本运行时才完全生成,es6对外只是一种静态定义,在代码静态解析阶段就会生成
-
cjs运行时,加载整个模块,使用的时候再读取接口; es6 是可以指定加载某个值,而不是加载整个模块。
额外问题
为何一些开源框架,为何把全局、指针以及框架本身引用作为参数
(function(window,$,undefined){
Window.webshow = function(){}
})(window,jQuery)
// 阻断思路
Windon的全局作用域转化为局部作用域,提升执行效率 2.编译时优化
Jquery 1.独立定制和挂载 2.防止全局串扰
Undefined 防止重写
如果在AMD中兼容老代码
Define(‘amdMOdule’,[],require => {
})
手写兼容cjs&amd
cjs 和 es module 如何相互支持
- cjs 支持 es module
1.在node版本未支持之前,利用一些构建工具(webpack),把js文件打包,node环境,引入打包后的文件就行了
// webpack 打包文件
/dist/main.js
引入
import main from './dist/main.js';
2.node 官方支持,新建文件名字后缀为.mjs
main.mjs文件下
import a from './main.js';
function c() {
}
export defalut c;
- es module 支持 cjs esm 可以直接加载cjs,只能整体加载。因为esm是一个支持静态解析,而cjs导出是一个对象,只能全部一起加载。