跳到主要内容

1 篇博文 含有标签「commonjs」

查看所有标签

· 阅读需 8 分钟
function require(modulePath) {
// 根据传递的模块路径,得到模块完整的绝对路径作为id
var moduleId = getModuleIds(modulePath)

// 辅助函数
function _require(exports, require, module, __filename, __dirname) {
// 我们的模块代码都是在一个函数环境中执行的
// 这也就解释了为什么 commonjs 能够隔离变量,不会造成全局污染

// 我们在模块中可以直接使用 exports module __dirname __filename require 等属性或方法
// 原因就在于他们都是这个函数的参数
}

// 初始化 module 对象和 exports 对象
var module = {
exports: {}
}
var exports = module.exports
// 得到文件模块的绝对路径
var __filename = moduleId
// 得到模块所在目录的绝对路径
var __dirname = getDirname(__filename)
// 使用 call 执行函数,绑定上下文为 exports
// 这就意味着在模块中,this === exports === module.exports
__require.call(exports, exports, require, module, __filename, __dirname)

// 最后返回 module.exports
return module.exports
}

整个 commonjs 的运行都依托于 require 函数的执行机制,所以研究 commonjs 的关键就在于理解 require 函数的本质

require 函数的实现

require 函数接收模块路径作为参数

根据模块路径,得到模块完整的绝对路径,将其标识为该模块的id

根据 id 判断缓存中是否存在,如果存在直接返回缓存的模块数据

模块内部代码实际是在一个辅助函数中执行的,辅助函数接收 exportsrequire函数自身、module__filename 以及 __dirname 作为参数

这也就解释了为什么模块能够隔离变量,不会造成全局污染;并且在模块中可以直接使用 exports module __dirname __filename require 等属性或方法

准备这个辅助函数所需的参数后,使用 call 执行辅助函数,并将 module 对象的 exports 属性作为上下文传入辅助函数

这就意味着在模块代码中,this === exports === module.exports,三者默认指向同一个对象

最后缓存并返回 module.exports 即可

  • 伪代码
function require(modulePath) {
// 1. 根据传递的模块路径,得到模块完整的绝对路径作为id
var moduleId = getModuleIds(modulePath)
// 2. 判断缓存
if (cache[moduleId]) {
return cache[moduleId]
}
// 3. 真正运行模块代码的辅助函数
function _require(exports, require, module, __filename, __dirname) {
// 目标模块的代码在这里
// 也就是说我们的模块代码都是在一个函数环境中执行的
// 这也就解释了为什么 commonjs 能够隔离变量,不会造成全局污染

// 我们在模块中可以直接使用 exports module __dirname __filename require 等属性或方法
// 原因就在于他们都是这个函数的参数
}
// 4. 准备并运行辅助函数
var module = {
exports: {}
}
var exports = module.exports
// 得到文件模块的绝对路径
var __filename = moduleId
// 得到模块所在目录的绝对路径
var __dirname = getDirname(__filename)
// 使用 call 执行函数,绑定上下文为 exports
// 这就意味着在模块中,this === exports === module.exports
__require.call(exports, exports, require, module, __filename, __dirname)

// 5. 缓存 module.exports
cache[moduleId] = module.exports
// 6. 返回 module.exports
return module.exports
}

问题:2.js 中 m 的值为?

  • 牢记模块代码中的 this、exports 和 module.exports 默认指向同一个对象
// 1.js
this.a = 1
exports.b = 2
exports = {
c: 3
}
module.exports = {
d: 4
}
exports.e = 5
this.f = 6

// 2.js
const m = require('./1.js')
Codethismodule.exportsexports
this.a = 1{ a: 1 }{ a: 1 }{ a: 1 }
exports.b = 2{ a: 1, b: 2 }{ a: 1, b: 2 }{ a: 1, b: 2 }
exports = { c: 3 }{ a: 1, b: 2 }{ a: 1, b: 2 }{ c: 3 }
module.exports = { d: 4 }{ a: 1, b: 2 }{ d: 4 }{ c: 3 }
exports.e = 5{ a: 1, b: 2 }{ d: 4 }{ c: 3, e: 5 }
this.f = 6{ a: 1, b: 2, f: 6 }{ d: 4 }{ c: 3, e: 5 }

require 函数最终返回的是 module.exports 对象,因此变量 m 的值为 { d: 4 }

require 与 import 区别

  • import
  1. import 是 ES6 (ECMAScript 2015) 中引入的,用于静态导入模块

  2. 它允许使用解构赋值的方式来引入模块中的特定部分。

  3. import具有提升效果,无论在文件的哪个部分使用,都会被提升到文件顶部

  4. import语句只能在模块的顶层作用域中使用,不能在条件语句中。

  5. 它与ES6模块系统配合使用,支持模块的静态分析树摇操作(tree-shaking),有助于实现按需加载

  • require
  1. require是 CommonJS 规范中引入的,用于在 Node.js 中同步导入模块

  2. 使用require时,你直接获取到一个模块的导出对象

  3. require 在运行时调用,意味着可以根据条件的不同动态地加载不同的模块

  4. 可以在代码的任何地方使用require,包括在函数或条件语句中。

  5. require 通常不支持无用代码移除按需加载,因为它是动态加载的。

  • 区别
  1. import 是静态的,支持编译时优化;require是动态的,适用于条件加载和运行时的计算路径。

  2. import需要在模块顶部,且不能在代码块中使用;require则可以在模块的任何地方使用。

  3. import语句可以让现代的JS打包工具和引擎优化导入的模块;require则不支持这些优化功能。

  4. import语句主要用于前端JavaScript模块化,而require则常用于Node.js的模块系统。

在实际开发中,如果使用的是Node.js,并且没有使用工具如Babel或Webpack对代码进行转换和打包,通常会使用require。如果开发的是现代的前端应用程序,并且项目配置了相应的JS打包工具,则推荐使用import语法来引入模块。随着Node.js对ES6模块的支持日益完善,import语句的使用变得越来越广泛。