模块化
什么是模块化?
在这之前我们先认识一下我们早期开发是怎样使用的
早期在开发过程中通常我们是这样进行引用的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src=".111.js"></script>
<script src=".222.js"></script>
<script src=".333.js"></script>
</body>
</html>
通常我们是将在外部的js文件通过 <script>标签放入到HTML结构体中进行引用,其实这样看起来没什么不好,我们只需要把每个需要使用的js文件放入到目标html文件中就行了.
在最开始对于简单的动画实现来说还是可以的,但随着JS的快速发展,JS愈发复杂且功能及需求也越来越多,以往的这种开发模式这就会导致很多问题,比如:命名冲突的问题、作用域的问题...
那么什么是模块化与模块化开发呢?
维基百科定义:
---模组化设计(英语:Modular design),也称模块化设计,是门设计理论与实作方法,其旨在于将一个系统细分为许多小单元,称为模组(module)或模块(block),可以独立的于不同的系统中被建立与使用。模组化设计的特征为将功能切分为抽象的、可扩充的、可重复使用的模组;模组需严格使用定义明确的模块化接口,并以行业间标准作为接口规范。在这种情况下,模组化在组件级别实作,并且具有一个单一、固定且特殊的组件插槽。这种由组件单元组合而成的模组化系统,通常称为使用模组化组件的平台系统。例如个人电脑上的USB。
通常使用主程式、子程式和子过程等架构将软件的主要结构和流程描述出来,而非一般程式的编写──逐条输入计算机程序和指令。
---模块化(modular)编程,是强调将计算机程序的功能分离成独立的、可相互改变的“模块”(module)的软件设计技术,它使得每个模块都包含着执行预期功能的一个唯一方面(aspect)所必需的所有东西。
以上条条框框的太多,按照我自己的理解来说模块化就是:
- 模块化开发是将程序划分成一个个小结构;
- 结构中可以编写自己的代码逻辑且有自己的作用域,并不会影响到其他结构
- 该结构可以将自己希望对外暴露的东西(函数、变量、对象...)对外暴露给其他结构使用,同时也可以导入另外的结构中的东西(函数、变量、对象...)
这种结构实际上就是模块,那么按照上述过程进行程序开发的过程就是模块化开发。
认识一下JS中几种模块化规范
1)CommonJS---(CJS)
其中Node是对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发:
---在Node中每一个js文件都是一个单独的模块;
---这个模块中包括CommonJS规范的核心变量:exports、module.exports、require;
---我们可以使用这些变量来方便的进行模块化开发;
其中:
exports和module.exports可以负责对模块中的内容进行导出;
require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
基本使用:
//index.js
const name = "小明"
const age = 18
function sum(num1, num2) {
return num1 + num2
}
// 1.导出方案 module.exports
module.exports = {
name,
age,
sum
}
------------------------------------------
//main.js
//使用另外一个模块导出的对象, 那么就要进行导入 require
const { name, age, sum } = require("./index.js")
console.log(name)
console.log(age)
console.log(sum(20, 30))
(node)内部原理:类似于对象引用的赋值
exports:(用内存地址指向解释)
exports是一个对象,可以添加多个属性,添加的属性会被导出(node 中使用)
const name = "小明"
const age = 18
function sum(num1, num2) {
return num1 + num2
}
********************************************
//使用exports导出
exports.name = name
exports.age = age
exports.sum = sum
//内部实现源码,最终导出的一用是module.exports
module.exports = {}
exports = module.exports
//这种情况下不会被导出
exports = {
name,
age,
sum
}
require(X):
require是一个函数,可以引入一个模块中导出的对象
- 情况一:X是一个Node核心模块,比如path、http....会直接返回核心模块,并停止查找
- 情况二:X是以 ./ 或 ../ 或 /(根目录)开头的
第一步:将X当做一个文件在对应的目录下查找;
1.如果有后缀名,按照后缀名的格式查找对应的文件
2.如果没有后缀名,会按照如下顺序:
1> 直接查找文件X
2> 查找X.js文件
3> 查找X.json文件
4> 查找X.node文件
第二步:没有找到对应的文件,将X作为一个目录
查找目录下面的index文件
1> 查找X/index.js文件
2> 查找X/index.json文件
3> 查找X/index.node文件
---如果没有找到,那么报错:not found
3. X不是一个模块,也不是路径
会以当前文件夹进行逐级往上查找,若上面路径没有找到则会报错
模块加载过程(运行时加载)
-
结论一:模块在被第一次引入时,模块中的js代码会被运行一次
-
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次
为什么只会加载运行一次呢?
这是因为每个模块对象module都有一个属性:loaded。
false表示还没有加载,为true表示已经加载;
-
结论三:如果有循环引入,那么加载顺序是什么?
如果出现下图模块的引用关系,那么加载顺序是什么呢?
---这个其实是一种数据结构:图结构;
图结构在遍历的过程中,有深度优先搜索(DFS, depth first search)和广度优先搜索(BFS, breadth first search);
Node采用的是**深度优先**算法:main -> aaa -> ccc -> ddd -> eee ->bbb
2)AMD规范
AMD主要是应用于浏览器的一种模块化规范:
- AMD是Asynchronous Module Definition(异步模块定义)的缩写;
- 它采用的是异步加载模块;
- 事实上AMD的规范还要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的较少了;
- AMD实现的比较常用的库是require.js和curl.js;
3)CMD规范
- CMD规范也是应用于浏览器的一种模块化规范:
- CMD 是Common Module Definition(通用模块定义)的缩写;
- 它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来;
- AMD实现的比较常用的是SeaJS(sea.js)
AMD和CMD了解一下就行,目前都是用较少
4)ES Module
了解:
-
它使用了import和export关键字实现模块化
export负责将模块内的内容导出;
import负责从其他模块导入内容;
-
采用编译期的静态分析,并且也加入了动态引用的方式
-
采用ES Module将自动采用严格模式:use strict
基本使用:
//导出
export const name = "小明"
export const age = 18
//导入
import { name, age } from "./foo.js"
console.log(name)
console.log(age)
//html文件中引用main.js需要在type属性声明为"module"
<script src="./main.js" type="module"></script>
export与import
分别使用:
// 1.第一种方式: export 声明语句
export const name = "小明"
export const age = 18
export function foo() {
console.log("foo function")
}
export class Person {
}
// 2.第二种方式: export 导出 和 声明分开
const name = "小明"
const age = 18
function foo() {
console.log("foo function")
}
export {
name,
age,
foo
}
// 3.第三种方式: 第二种导出时起别名
export {
name as fName,
age as fAge,
foo as fFoo
}
// 1.导入方式一: 普通的导入
import { name, age, foo } from "./foo.js"
import { fName, fAge, fFoo } from './foo.js'
// 2.导入方式二: 起别名
import { name as fName, age as fAge, foo as fFoo } from './foo.js'
// 3.导入方式三: 将导出的所有内容放到一个标识符中
import * as foo from './foo.js'
结合使用:
在工具库函数文件夹中设置index.js将所有其他所有希望暴露的接口放入其中
// 1.导出方式一:
import { add, sub } from './math.js'
import { timeFormat, priceFormat } from './format.js'
export {
add,
sub,
timeFormat,
priceFormat
}
// 2.导出方式二:
export { add, sub } from './math.js'
export { timeFormat, priceFormat } from './format.js'
// 3.导出方式三:
export * from './math.js'
export * from './format.js'
default
const name = "小明"
const age = 18
const foo = "foo value"
//默认导出只能有一个
export default foo
//导入语句: 导入的默认的导出,此时xixi导入的是foo
import xixi from './foo.js'
import函数
某些情况下希望动态的加载某一个模块。这个时候需要使用import()函数来进行动态加载
---import()函数返回值是一个Promise
// import函数返回的结果是一个Promise
import("./foo.js").then(res => {
console.log("res:", res.name)
})
ES Module的解析流程
ESModule的解析过程可以划分为三个阶段:
- 阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(ModuleRecord);
- 阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向 对应的内存地址。
- 阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中;