模块化
模块化解决的问题:
- 加载顺序
- 污染全局作用域
- 模块独立并且可以相互依赖
早期的JavaScript模块化
早期使用模块化,都是将使用的模块写为一个 js 的文件,然后通过在 HTML的 script 的 src 属性引入该JS文件
// a.jsvar a = "testa"// b.jsvar b = "testb"// c.jsvar c = "testc"// index.jsconsole.log(a)console.log(b)console.log(c)// index.html<script src="a.js" /><script src="b.js" /><script src="c.js" /><script src="index.js" />
这样写的缺陷: 变量覆盖 -> 变量重名 -> 污染全局
立即执行函数的模块化
使用了立即执行函数, 对模块内容进行封装, 解决了 污染全局的问题 , 模块于模块之间可以依赖了
// a.jsvar ModuleA = (function () {// 作用域 独立// 模块化的独立作用域var a = [1,2,3,4,5]// 引出去return {a: a}})();// b.jsvar ModuleB = (function (ModuleA) { // 使用 ModuleA 注入参数var b = ModuleA.a.concat([6,7,8,9,10])// 引出去return {b: b}})(ModuelA);// c.jsvar ModuleC = (function (ModuleB) {var c = ModuleB.b.join("-")return {c: c}})(ModuleB);// index.js;(function (ModuleA,ModuleB,ModuleC) {console.log(ModuleA.a)console.log(ModuleB.b)console.log(ModuleC.c)})(ModuleA,ModuleB,ModuleC);// index.html<script src="a.js" /><script src="b.js" /><script src="c.js" /><script src="index.js" />
立即执行函数的模块化,不能够解决 顺序问题 , 只能解决污染全局和模块的相互依赖
NodeJS出现CommonJS 规范的模块化
// 引入模块require('xxx');// 导出模块module.exports
NodeJS的出现可以正式的对JS文件,进行导入和导出, 不依赖 HTML页面了, 必须要运行在Node环境中
CommonJS 规范的模块化体验
// 基于webpack// 省略初始化 和 webpack 的配置// a.jsvar a = (function () {return [1,2,3,4,5].reverse();})();// 引出 ModuleAmodule.exports = {a: a}// b.jsvar ModuleA = require("./a.js") // 引入A 模块var b = (function () {return ModuleA.a.concat([6,7,8,9,10])})();// 引出 B 模块module.exports = {b: b}// c.jsvar ModuleB = require("./b.js") // 引入A 模块var c = (function () {return ModuleB.b.join("-")})();// 引出 C 模块module.exports = {c: c}// index.js // webpack 的入口文件var ModuleA = require("./a.js"),ModuleB = require("./b.js"),ModuleC = require("./c.js");console.log(ModuleA.a)console.log(ModuleB.b)console.log(ModuleC.c)// index.html<script src="index.js" />
NodeJS 相关的CommonJS 规范
CommonJS它是不是一种JS, 而是一种规范,模块化的规范, 来源于NodeJS 。 使用同步的方法。服务端使用的
koa和express都是使用require()的方式引入
使用CommonJS 的好处
CommonJS->require,
- 只要使用了
require引入模块, 那么就会创建一个模块的实例 -> 实例化- 因为Node运行的服务端, 使用
**CommonJS**引入的模块, 就具备 缓存机制 ; 只要使用了一次**require**引入的模块,接下来就是使用缓存- 使用
CommonJS的规范, 就一定是在NodeJS上运行, 客户端上运行不了; 也就是说不依赖webpack解析node的话,require就使用不了
require() 引入的本质
require不是一个全局变量, 本质是一个 立即执行函数; 模块之间的引入,导出其实是使用了提前设置好的变量, 这些变量组成 require 引入的方法
exports, require, module, __filename, __dirname
// require() 引入的方法 本质;(function (exports, require, module, __filename, __dirname) {})()
客户端的 CommonJS —— AMD 规范
AMD (Asynchronous Module Definition ) 异步模块定义
// AMD 的语法// 定义模块// 参数: 1. 模块名, 2. 需要引入的模块名,3.本身的回调函数define(moduleName, [module], factory);// 引入模块require([module], callback);
为什么会出现 AMD, 因为 CommonJS的规范在客户端运行不了, 出现 AMD 为了 解决 在客户端 (浏览器) 也能实现 模块之间的相互引用;
AMD 与 CommonJS 的 区别:
- AMD 是异步模块化定义
- CommonJS 是同步
目前需要实现 AMD 的方式引入模块化 : 使用 RequireJS
RequireJS 是一个能够实现了AMD规范的库; 用到需要下载 require.js
AMD 的规范使用 : defind() require()
// index.html// 引入 AMD 的规范 require.js<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js" /><script src="./index.js" />// a.jsdefine(ModuleA, function () {var a = [1,2,3,4,5]return {a: a.reverse()}});// b.jsdefine(ModuleB, ['ModuleA'], function (ModuleA) {var b = [6,7,8,9]return {b: ModuleA.a.concat(b)}});// c.jsdefine(ModuleC, ['ModuleB'], function (ModuleB) {return {c: ModuleB.b.join('-')}});// index.js// 需要配置路径require.config({paths: {ModuleA: './a.js',ModuleB: './b.js',ModuleC: './c.js',}})require(['ModuleA', 'ModuleB', 'ModuleC'], function (ModuleA, ModuleB,ModuleC) {console.log(ModuleA.a)console.log(ModuleB.b)console.log(ModuleC.c)})
CMD 规范
CMD 的规范与AMD 类似, 由 阿里开发 seajs
CMD (Common Module Definition) 通用模块化定义
// CMD 的语法使用// 定义模块define(function (require, exports, module) {})// require 加载 define 定义 exports 导出 module 操作模块// 使用模块seajs.use([modu路径], function (moduelA, moduleB, moduleC) {})
使用 CMD 与 AMD 相同, 需要使用 seajs 这个库
CMD 规范的使用
// index.html// 引入 seajs<script src="https://cdn.jsdelivr.net/npm/seajs@3.0.3/lib/sea.min.js"></script>// 其他的步骤和 AMD 基本一样 使用就去查
AMD 和 CMD 的区别 :
AMD : 先导入, 先加载 , 再执行
CMD : 依赖就近 , 按需加载
需要查 文档
ES6 模块化
语法 :
// 导入模块import module from "xxx"// 导出模块export module
使用 es6 模块化
// a.jsexport default {a: [1,2,3,4,5].reverse()}// b.jsimport ModuleA from "./a.js"export default {b: ModuleA.a.concat([6,7,8,9,10])}// c.jsimport ModuleB from "./b.js"export default {c: ModuleB.b.join('-')}// index.jsimport ModuleA from "./a.js"import ModuleB from "./b.js"import ModuleC from "./c.js"console.log(ModuleA.a)console.log(ModuleB.b)console.log(ModuleC.c)
为什么有时候 import {} / import xxx ;
因为 使用了
default关键字 , 在使用export default表示默认导出一个对象使用一个 变量接收就行, 所以import ModuleA from "xxx"在使用
export const ModuleA = {}, 就使用import {ModuelA} from 'xxx'进行引入,{ModuelA}表示解构出 ModuleA如果有 default 就直接引入, 没有没有 default 就需要通过解构方式引入
ES6 模块化 和 CommonJS 的区别
- ES6 使用
import / export, 而 CommonJS 使用require / module.export - 二者的加载结果不同,CommonJS 输出的是一个值的拷贝(初始化时就拷贝), 而ES6 输出的是值的引用 (重点) -> 在讲题目时的案例: 在用
**_import|require_**引入一个值,而这个值被定时器函数过几秒钟会被修改,在项目运行时,es6Module 和 Commonjs 引用这个值的文件 打印这个值;得到不同的结果(es6得到定时器修改后的值,Cmj得到的是初始时的这个值);说明了CommonJS 输出的是一个值的拷贝(初始化时就拷贝), 而ES6 输出的是值的引用 - commonJS 模块是在运行时加载 (程序运行时存在了模块),ES6模块是在编译时加载(在编译时就是一个模块了)
这里感觉讲不清楚
ES6 Module和CommonJS模块的区别:
- CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,也就是指针指向不能变,类似const;
- import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。
YUI : 扩展知识
总结
https://www.bilibili.com/video/BV1K54y1S7zx
1、从JS诞生依赖的开发讲起,走JS文件调用越来越复杂,人们开始试图通过html文件来初步组织“模块化”的代码,但是为啥我JS模块化要依赖于你HTML,同时,对于程序的加载顺序和全局污染也带来了迫切的需求;(这个全局污染是不是被Java的封装降维打击了?)
2、于是,nodejs创新的搞出了模块化,给开发者带来了前所未有的体验;CommonJS规范应运而生!但是,nodejs是服务器端,受限于过去生态中浏览器各家厂商谁看谁不顺眼,浏览器端怎么办?模块化多好啊
3、于是,AMD YES!而RequireJS就是实现这个AMD规范的最佳范例,看代码的组织逻辑,确实过去的技术并不代表落后,我还是很喜欢RequireJS的代码组织逻辑和结构,感觉很像JAVA。(规范足够强,工程性就足够强!)
4、同时,阿里也为程序员搞出了CMD规划,通用模块定义,实现方式是seajs。但是和requirejs相比,cmd是依赖就近,而AMD是依赖前置,这是个效率问题的改进。
5、最终,ECMA“迫于压力”,为了程序员的幸福工作,推出了ES6模块化规范,import export!
6、但是CommonJS和ES6模块由本质的区别,CommonJS是拷贝,类似于类的对象;而ES6是直接的引用;所以,一个是运行时加载,一个是编译时加载(当然这句话的理解还是停留在表面);
总结:
模块化其实本质解决的是加载顺序和全局污染,当然还有代码复用等程序员开发遇到的现实问题;
模块化最初尝试由nodejs提出,但是你不能只服务端啊,浏览器端也需要啊,于是AMD和CMD规范诞生;
模块化这个工作,其实更应该是EMCA来搞啊,可惜你没想到web发展如此之快,也没想到浏览器霸主ie掉落神坛了,于是迫于压力,推出了ES6模块化(甚至想搞类Class也搞的不伦不类)。
总结2
CommonJS 总结:
- 使用
require()方式引入模块 和module.exports导出模块 - CJS 的加载原理 : CJS的一个模块,就是一个脚本js文件,执行
require方式 第一次加载脚本时,会执行真个脚本, 然后在内存中生成一个Module对象 - 模块缓存: CJS 模块无论加载多少次, 都只会在第一次加载时运行一次, 并在内存中生成
Module对象, 以后再加载相同的模块, 就返回第一次运行的结果(Module)对象 , 可以在通过输出require.chache查看你缓存内容。 - CJS 模块输出的是值得拷贝, 即一旦输出一个值 ,模块内部的变化就影响不到这个值
- CJS 模块的重要特定是 加载时执行 , 即脚本代码在进行
require时, 就会全部执行。 一旦出现某个模块被 “循环加载”, 只输出已经执行部分, 未执行部分不会输出
ESM总结:
- 使用
import和export方式导入导出 - ESM加载原理: ESM 模块的运行机制与CJS 不一样, 当加载遇到ESM时, 就会生成一个 只读引用, 等到脚本真正执行时, 再根据这个 只读引用, 去被加载的模块中 取值, (简单来说 ESM 引入只是拿到对值得引用)
- 在模块缓存方面,ESM加载器有自己独立的缓存
- ESM模块输出的是 值的引用, 因为 ESM模块 是动态引用, 并不会缓存值,模块里的变量绑定所在的模块
