模块化

模块化解决的问题:

  1. 加载顺序
  2. 污染全局作用域
  3. 模块独立并且可以相互依赖

早期的JavaScript模块化

早期使用模块化,都是将使用的模块写为一个 js 的文件,然后通过在 HTML的 scriptsrc 属性引入该JS文件

  1. // a.js
  2. var a = "testa"
  3. // b.js
  4. var b = "testb"
  5. // c.js
  6. var c = "testc"
  7. // index.js
  8. console.log(a)
  9. console.log(b)
  10. console.log(c)
  11. // index.html
  12. <script src="a.js" />
  13. <script src="b.js" />
  14. <script src="c.js" />
  15. <script src="index.js" />

这样写的缺陷: 变量覆盖 -> 变量重名 -> 污染全局

立即执行函数的模块化

使用了立即执行函数, 对模块内容进行封装, 解决了 污染全局的问题模块于模块之间可以依赖了

  1. // a.js
  2. var ModuleA = (function () {
  3. // 作用域 独立
  4. // 模块化的独立作用域
  5. var a = [1,2,3,4,5]
  6. // 引出去
  7. return {
  8. a: a
  9. }
  10. })();
  11. // b.js
  12. var ModuleB = (function (ModuleA) { // 使用 ModuleA 注入参数
  13. var b = ModuleA.a.concat([6,7,8,9,10])
  14. // 引出去
  15. return {
  16. b: b
  17. }
  18. })(ModuelA);
  19. // c.js
  20. var ModuleC = (function (ModuleB) {
  21. var c = ModuleB.b.join("-")
  22. return {
  23. c: c
  24. }
  25. })(ModuleB);
  26. // index.js
  27. ;(function (ModuleA,ModuleB,ModuleC) {
  28. console.log(ModuleA.a)
  29. console.log(ModuleB.b)
  30. console.log(ModuleC.c)
  31. })(ModuleA,ModuleB,ModuleC);
  32. // index.html
  33. <script src="a.js" />
  34. <script src="b.js" />
  35. <script src="c.js" />
  36. <script src="index.js" />

立即执行函数的模块化,不能够解决 顺序问题 , 只能解决污染全局模块的相互依赖

NodeJS出现CommonJS 规范的模块化

  1. // 引入模块
  2. require('xxx');
  3. // 导出模块
  4. module.exports

NodeJS的出现可以正式的对JS文件,进行导入和导出, 不依赖 HTML页面了, 必须要运行在Node环境中

CommonJS 规范的模块化体验

  1. // 基于webpack
  2. // 省略初始化 和 webpack 的配置
  3. // a.js
  4. var a = (function () {
  5. return [1,2,3,4,5].reverse();
  6. })();
  7. // 引出 ModuleA
  8. module.exports = {
  9. a: a
  10. }
  11. // b.js
  12. var ModuleA = require("./a.js") // 引入A 模块
  13. var b = (function () {
  14. return ModuleA.a.concat([6,7,8,9,10])
  15. })();
  16. // 引出 B 模块
  17. module.exports = {
  18. b: b
  19. }
  20. // c.js
  21. var ModuleB = require("./b.js") // 引入A 模块
  22. var c = (function () {
  23. return ModuleB.b.join("-")
  24. })();
  25. // 引出 C 模块
  26. module.exports = {
  27. c: c
  28. }
  29. // index.js // webpack 的入口文件
  30. var ModuleA = require("./a.js"),
  31. ModuleB = require("./b.js"),
  32. ModuleC = require("./c.js");
  33. console.log(ModuleA.a)
  34. console.log(ModuleB.b)
  35. console.log(ModuleC.c)
  36. // index.html
  37. <script src="index.js" />

NodeJS 相关的CommonJS 规范

CommonJS 它是不是一种JS, 而是一种规范,模块化的规范, 来源于NodeJS 。 使用同步的方法。

服务端使用的 koaexpress 都是使用 require() 的方式引入

使用CommonJS 的好处

CommonJS -> require

  1. 只要使用了 require 引入模块, 那么就会创建一个模块的实例 -> 实例化
  2. 因为Node运行的服务端, 使用**CommonJS**引入的模块, 就具备 缓存机制 ; 只要使用了一次 **require** 引入的模块,接下来就是使用缓存
  3. 使用 CommonJS的规范, 就一定是在 NodeJS上运行, 客户端上运行不了; 也就是说不依赖 webpack 解析node的话, require 就使用不了

require() 引入的本质

require 不是一个全局变量, 本质是一个 立即执行函数模块之间的引入,导出其实是使用了提前设置好的变量, 这些变量组成 require 引入的方法

exports, require, module, __filename, __dirname

  1. // require() 引入的方法 本质
  2. ;(function (exports, require, module, __filename, __dirname) {
  3. })()

客户端的 CommonJS —— AMD 规范

AMD (Asynchronous Module Definition ) 异步模块定义

  1. // AMD 的语法
  2. // 定义模块
  3. // 参数: 1. 模块名, 2. 需要引入的模块名,3.本身的回调函数
  4. define(moduleName, [module], factory);
  5. // 引入模块
  6. require([module], callback);

为什么会出现 AMD, 因为 CommonJS的规范在客户端运行不了, 出现 AMD 为了 解决 在客户端 (浏览器) 也能实现 模块之间的相互引用;

AMD 与 CommonJS 的 区别:

  • AMD 是异步模块化定义
  • CommonJS 是同步

目前需要实现 AMD 的方式引入模块化 : 使用 RequireJS

RequireJS 是一个能够实现了AMD规范的库; 用到需要下载 require.js

AMD 的规范使用 : defind() require()

  1. // index.html
  2. // 引入 AMD 的规范 require.js
  3. <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js" />
  4. <script src="./index.js" />
  5. // a.js
  6. define(ModuleA, function () {
  7. var a = [1,2,3,4,5]
  8. return {
  9. a: a.reverse()
  10. }
  11. });
  12. // b.js
  13. define(ModuleB, ['ModuleA'], function (ModuleA) {
  14. var b = [6,7,8,9]
  15. return {
  16. b: ModuleA.a.concat(b)
  17. }
  18. });
  19. // c.js
  20. define(ModuleC, ['ModuleB'], function (ModuleB) {
  21. return {
  22. c: ModuleB.b.join('-')
  23. }
  24. });
  25. // index.js
  26. // 需要配置路径
  27. require.config({
  28. paths: {
  29. ModuleA: './a.js',
  30. ModuleB: './b.js',
  31. ModuleC: './c.js',
  32. }
  33. })
  34. require(['ModuleA', 'ModuleB', 'ModuleC'], function (ModuleA, ModuleB,ModuleC) {
  35. console.log(ModuleA.a)
  36. console.log(ModuleB.b)
  37. console.log(ModuleC.c)
  38. })

CMD 规范

CMD 的规范与AMD 类似, 由 阿里开发 seajs

CMD (Common Module Definition) 通用模块化定义

  1. // CMD 的语法使用
  2. // 定义模块
  3. define(function (require, exports, module) {})
  4. // require 加载 define 定义 exports 导出 module 操作模块
  5. // 使用模块
  6. seajs.use([modu路径], function (moduelA, moduleB, moduleC) {})

使用 CMD 与 AMD 相同, 需要使用 seajs 这个库

CMD 规范的使用

  1. // index.html
  2. // 引入 seajs
  3. <script src="https://cdn.jsdelivr.net/npm/seajs@3.0.3/lib/sea.min.js"></script>
  4. // 其他的步骤和 AMD 基本一样 使用就去查

AMD 和 CMD 的区别 :

AMD : 先导入, 先加载 , 再执行

CMD : 依赖就近 , 按需加载

需要查 文档

ES6 模块化

语法 :

  1. // 导入模块
  2. import module from "xxx"
  3. // 导出模块
  4. export module

使用 es6 模块化

  1. // a.js
  2. export default {
  3. a: [1,2,3,4,5].reverse()
  4. }
  5. // b.js
  6. import ModuleA from "./a.js"
  7. export default {
  8. b: ModuleA.a.concat([6,7,8,9,10])
  9. }
  10. // c.js
  11. import ModuleB from "./b.js"
  12. export default {
  13. c: ModuleB.b.join('-')
  14. }
  15. // index.js
  16. import ModuleA from "./a.js"
  17. import ModuleB from "./b.js"
  18. import ModuleC from "./c.js"
  19. console.log(ModuleA.a)
  20. console.log(ModuleB.b)
  21. 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 的区别

  1. ES6 使用 import / export , 而 CommonJS 使用 require / module.export
  2. 二者的加载结果不同,CommonJS 输出的是一个值的拷贝(初始化时就拷贝), 而ES6 输出的是值的引用 (重点) -> 在讲题目时的案例: 在用**_import|require_**引入一个值,而这个值被定时器函数过几秒钟会被修改,在项目运行时,es6Module 和 Commonjs 引用这个值的文件 打印这个值;得到不同的结果(es6得到定时器修改后的值,Cmj得到的是初始时的这个值);说明了CommonJS 输出的是一个值的拷贝(初始化时就拷贝), 而ES6 输出的是值的引用
  3. 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 总结:

  1. 使用 require()方式引入模块 和 module.exports 导出模块
  2. CJS 的加载原理 : CJS的一个模块,就是一个脚本js文件,执行require方式 第一次加载脚本时,会执行真个脚本, 然后在内存中生成一个 Module对象
  3. 模块缓存: CJS 模块无论加载多少次, 都只会在第一次加载时运行一次, 并在内存中生成 Module对象, 以后再加载相同的模块, 就返回第一次运行的结果(Module)对象 , 可以在通过输出 require.chache查看你缓存内容。
  4. CJS 模块输出的是值得拷贝, 即一旦输出一个值 ,模块内部的变化就影响不到这个值
  5. CJS 模块的重要特定是 加载时执行 , 即脚本代码在进行 require时, 就会全部执行。 一旦出现某个模块被 “循环加载”, 只输出已经执行部分, 未执行部分不会输出

ESM总结:

  1. 使用 importexport 方式导入导出
  2. ESM加载原理: ESM 模块的运行机制与CJS 不一样, 当加载遇到ESM时, 就会生成一个 只读引用, 等到脚本真正执行时, 再根据这个 只读引用, 去被加载的模块中 取值, (简单来说 ESM 引入只是拿到对值得引用)
  3. 在模块缓存方面,ESM加载器有自己独立的缓存
  4. ESM模块输出的是 值的引用, 因为 ESM模块 是动态引用, 并不会缓存值,模块里的变量绑定所在的模块