CommonJS

CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来体现它的广泛性,修改为CommonJS(简称CJS)

  • Node是CommonJS在服务器端一个具有代表性的实现
  • Browserify是CommonJS在浏览器中的一种实现
  • webpack打包工具具备对CommonJS的支持和转换

    CommonJS的基本使用

    other.js ```javascript const dataList = [1, 3, 6, 3, 2]

function sum(a, b) { return a + b }

module.exports = { dataList, sum }

  1. `main.js`
  2. ```javascript
  3. const other = require('./other.js')
  4. console.log(other.dataList); // [ 1, 3, 6, 3, 2 ]
  5. console.log(other.sum(10, 20)); // 30

内部原理: 在other.js中,有一个变量module,可以给module.exports赋值一个对象。 当外部文件通过require()调用时,会获取module.exportsmain.js中的other和other.js中的module.exports是同一个对象。

通过exports导出

这种方式不建议使用
other.js

  1. const dataList = [1, 3, 6, 3, 2]
  2. function sum(a, b) {
  3. return a + b
  4. }
  5. exports.dataList = dataList
  6. exports.sum = sum

:::danger 注意:不能用exports = { ... } :::

内部(源码)原理 module.exports = {} exports = module.exports 所以使用exports = { ... }不会影响module.exports

require()

require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。
基本格式:require(X)

require查找规则

情况一:X为核心模块

// fs和path为node的核心模块
const fs = require('fs')
const path = require('path')

情况二:X以’./‘、’../‘、’/‘开头

  1. 把X当作是一个文件
    1. 如果X有后缀名,按后缀名的格式查找对应的文件
    2. 如果X没有后缀名
      1. 查找文件X
      2. 查找X.js文件
      3. 查找X.json文件
      4. 查找X.node文件
  2. 把X当作是一个目录
    1. 查找目录下的index文件
      1. 查找X/index.js文件
      2. 查找X/index.json文件
      3. 查找X/index.node文件
  3. 如果没有找到,就报错not find

    情况三:一个不是核心模块的普通字符串

    会在当前目录下的node_modules下查找,没有会查找上一级node_modules,直到查找到根目录
    image.png

    模块加载细节

  • 模块在被第一次引入时,模块中的js代码会被运行一次
  • 模块被多次引入时,会缓存,最终只加载(运行)一次 :::info 为什么只能被载一次?
    因为模块对象module都有哦一个属性loaded。为true时表示已经被加载过了。 :::

  • 循环引入采用深度优先遍历算法加载

    image.png 执行顺序为:main-A-B-C-G-D-E-F

ES Module

JavaScript没有模块化一直是它的痛点,所以才会产生我们前面学习的社区规范:CommonJS、AMD、CMD等。
ES Module和CommonJS的模块化有一些区别:

  • ES Module采用了import和export来导入导出。
  • 采用编译器的静态分析和动态引入的方式。

    export:负责将模块内的内容导出 import:负责将其他模块的内容导入

ES Module的基本使用

other.js

// syntax: export <declaration statement>
export const msg = "hello"
export function foo() {
  console.log("foo")
}

main.js

// 导入外部文件的内容
import {msg, foo} from "./other.js";

console.log(msg)
foo()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <!-- 需要加上type="module" -->
  <script src="./main.js" type="module"></script>
</body>
</html>

错误案例分析

  • 缺少type="module"的情况:

image.png

  • 直接在本地打开文件(用浏览器运行文件,而不是服务)

image.png

这个在MDN上面有给出解释: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules 你需要注意本地测试 — 如果你通过本地加载Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为 Javascript 模块安全性需要。 你需要通过一个服务器来测试。

  • VSCode,VSCode中有一个插件:Live Server
  • WebStorm:以浏览器打开时会自动启动一个服务器

ES Module 导出

export关键字将一个模块中的变量函数等导出。

export的多种用法

  1. 在语句声明的前面直接加上export关键字export const msg = "hello"
  2. 将所有需要导出的标识符,放到export后面的 { }中。\ :::danger 注意:这里的{ }里面不是ES6的对象字面量的增强写法,{ }也不是表示一个对象的;
    所以: export {name: name},是错误的写法;
    :::

  3. 导出时给标识符起一个别名 。

    const name = "foo"
    export {
    name as newName
    }
    

    ES Module 导入

    import 关键字将一个模块中的变量函数等导入。

    import的多种用法

  4. 导入所需的 import {b} from 'a'

  5. 通过 * 导入一个模块中所有导出的内容 import * as b from 'a'
  6. 导出时可以起别名import { b as c } from 'a'

    ES Module 导入和导出的结合使用

    在开发和封装一个库时, 通常我们希望将暴露的所有接口放到一个文件中;这样方便指定统一的接口规范,也方便阅读; 这个时候,我们就可以使用export和import结合使用。 ```javascript // 将模块a的内容全部导出 export * from ‘a’

// 将模块b的c导出 export { c } from ‘b’

// 将模块d中的e重命名为f并导出 export { e as f} from ‘d’


<a name="xp48M"></a>
### ES Module default关键字
 默认导出export时可以不需要指定名字;在导入时不需要使用 {},并且可以自己来指定名字; 它也方便我们和现有的CommonJS等规范相互操作;
<a name="dg9JX"></a>
#### 默认导出方式一:export default
`other.js`
```javascript
export const msg = "hello"
export function foo() {
  console.log("foo")
}

function bar() {
  console.log("bar")
}

// 默认导出
export default bar

main.js

import {msg, foo} from "./other.js";
// 默认导出,可以自己起名
import baz from './other.js'

console.log(msg)
foo()
baz()

结果:
image.png :::warning 注意:在一个模块中,只能有一个默认导出(default export);
:::

默认导出方式二:export { a as default }

const msg = "hello"
function foo() {
  console.log("foo")
}
function bar() {
  console.log("bar")
}

// 效果同方式一
export {
    msg,
  foo,
  bar as default
}

import()函数

通过import加载一个模块,是不可以在其放到逻辑代码中的

为什么会出现这个情况呢?

  • import是同步加载
  • 这是因为ES Module在被JS引擎解析时,就必须知道它的依赖关系;
  • 由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况;

:::info import()函数是异步加载,返回Promise :::

const flag = true

if (flag) {
  import('./a.js').then(res => {
    // res为Module对象,可以通过res.property获取模块内容
    console.log(res)
  })
} else {
  import('./b.js').then(res => {
    console.log(res)
  })
}

import meta

import.meta是ES11中新增的特性。 一个给JavaScript模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的URL;