Skip to content

CommonJs ESM

CommonJS

关键

  • 社区标准而非官方标准
  • 核心是用 require 函数实现
  • 仅 node 环境支持
  • 动态依赖,需要代码执行到 require 函数才能确定依赖,并且是同步引入执行的

原理(简单理解)

子模块中就相当于一个函数空间

在 require()后,子模块中有 this arguments

并且子模块函数空间 this 指向 module.exports exports 与 module.exports 一致

所以 this === exports === module.exports

javascript
console.log(this, exports, module.exports);

console.log(this === exports); // true   {}
console.log(this === module.exports); // true   {}
console.log(exports === module.exports); // true   {}

exports.name = "zhangsan";
exports.age = 18;

module.exports.name = "lisi";
module.exports.age = 20;
module.exports.sex = "男";

module.exports = {
    name: "zhangsan",
    age: 18,
    sex: "男",
};

ES Module

关键

  • 官方标准 所有环境都支持
  • 使用新语法实现
  • 同时支持静态依赖 动态依赖
  • 静态依赖是代码运行前就确定的
  • 动态依赖是异步的
javascript
import("./a.js");
  • 符号绑定(违背习惯的实现, 参考)
javascript
import { count, addCountFn } from "./count.js";

console.log(count); // 0
addCountFn();
console.log(count); // 1
javascript
export let count = 0;
export function addCountFn() {
    count++;
}

面试

CMJ ESM 区别

CMJ 与 ESM 标准不同 CMJ 是社区标准 ESM 官方标准

CMJ 是同步动态导入 ESM 有静态 有动态 动态是异步的

运行环境不同 CMJ 通常运行在 Node

ESM 存在一个符号绑定的问题 CMJ 没

export export default 区别

export default 默认导出,可以重新用变量接收,并且一个文件只能有一个默认导出

export 具名导出, 导出的必须是已经定义好的,导出的模块对象中,命名即为模块对象的属性名,具名导出可以有多个

下边模块结果

javascript
exports.a = "a";
module.exports.b = "b";
this.c = "c";
module.exports = {
    d: "d",
};

分析以下题目

核心点:在****时存在符号绑定,解构 重新用变量赋值都会开辟新的内存空间

javascript
import { count, addCountFn } from "./count.js";

console.log(count); // 0
addCountFn();
console.log(count); // 1   可以看到 count 变量的值已经被改变了 由此可以看出引入时的count变量并不是一个副本,而是一个引用  指向同一个内存地址

// =======================

import { count as count2, addCountFn as addCountFn2 } from "./count.js";

console.log(count2); // 0
addCountFn2();
console.log(count2); // 1

//  ========================

import * as countObj from "./count.js";

console.log(countObj.count); // 0
countObj.addCountFn();
console.log(countObj.count); // 1

// ======================
import * as countObj from "./count.js";
const { count, addCountFn } = countObj; // 解构

console.log(count); // 0
addCountFn();
console.log(count); // 0

// =========================
import { count, addCountFn } from "./count.js";

let count2 = count; // 这里是值拷贝,count2 是 count 的副本,不会影响 count 的值
console.log(count2); // 0
addCountFn();
console.log(count2); // 0
javascript
import { count, addCountFn, logCountFn } from "./count.js";
count.value++; // 0  // { value: 1 }

console.log(count); // { value: 1 }  // count 是一个对象,引用类型的变量可以修改其属性值
addCountFn(); // 1
console.log(count); // { value: 2 }  // count 的值被修改了

logCountFn(); // 2

由此可见,count 指向的确是 count.js 中的内存地址, 当 count 是一个引用对象时,对其属性的修改都会反映在两个模块 js 文件,是简单类型时,会报错 Uncaught TypeError: Assignment to constant variable.

made with ❤️ by ankang