Nodejs 知识体系(五): Node 标准模块化本质上是一种架构约定,是架构师和开发人员共同定义的对外接口标准。 使
模块化本质上是一种架构约定,是架构师和开发人员共同定义的对外接口标准。
使用闭包与揭示模块模式管理私有变量
在前端开发中,我们可以利用闭包和揭示模块模式(Revealing Module Pattern) 来实现私有变量的管理与使用。这种模式可以将变量封装在函数作用域内,防止外部直接访问,同时通过返回对象的方法来公开所需的接口,安全且灵活。
function foo(){
var bar = 1; // 私有变量
var baz = 2; // 私有变量
// 公开接口,通过 Revealing 模式管理私有变量的访问
return {};
}
这种方法能够确保 bar 和 baz 仅在 foo 函数内部存在,避免了外部的直接篡改,提升了代码的可维护性与安全性,同时保留了对外部必要的功能暴露。
模块封装与属性管理
我们还可以进一步封装模块,例如:
var student = {
name: "Coco",
getScore: function() {
return 99;
}
};
// 访问模块内部属性和方法
student.name; // 获取名字 "Coco"
student.getScore(); // 获取分数 99
通过这种方式,模块的属性和方法可以得到更好的管理和使用,避免全局污染。但是这种封装会暴露所有内部属性和方法,缺乏对私有数据的保护,容易导致外部修改对象状态。
改进封装模式:使用闭包和立即执行函数
为了解决直接暴露内部属性和方法的问题,可以使用闭包和立即执行函数(IIFE),将内部变量私有化,并只暴露必要的接口。以下是通过这种方式改进的封装:
(function(global) {
// 私有变量和方法
var name = "Coco";
function getStore() {
return 99;
}
// 仅暴露需要的接口
global.student = {
name,
getStore
};
})(window);
通过这种模式,name 和 getStore 仍然可以在 student 对象上访问,但其内部实现细节被封装在闭包中,从而提高了数据的保护性和模块化。
解决依赖问题:结合闭包、IIFE 和 CommonJS 模块化
虽然通过闭包和 IIFE 方式解决了私有变量的封装问题,但依赖外部库如 jQuery 的场景中,单独使用这种方法仍未解决依赖管理的问题。因此,可以结合不同模块化方案进行处理,如 CommonJS 模块化。在这里,我们展示了两种方式,第一种是 IIFE 方式传入依赖,第二种是 CommonJS 规范的实现。
IIFE 方式传入依赖
// 使用 IIFE 传递依赖
(function(global, $) {
// 内部变量和方法
var name = "luyi";
function getStore() {
// 调用 jQuery 相关方法
// $.xxx
return 60;
}
// 暴露接口
global.student = {
name,
getStore
};
})(window, jQuery); // 传入 jQuery 依赖
CommonJS 模块化的实现
在 Node.js 或类似的环境中,常用的是 CommonJS 规范来管理模块和依赖:
// CommonJS 的实现
var module = {
exports: {}
};
(function (module, exports, require) {
// 通过 require 导入 jQuery 模块
const $ = require('../jquery.min.js');
// 定义私有变量和方法
var name = 'damowang';
var getStore = function () {
// 使用 jQuery 相关功能
return 60;
};
// 暴露模块的公共接口
exports.name = name;
exports.getStore = getStore;
// 也可以通过 module.exports 导出
module.exports = { name, getStore };
})(module, module.exports, require);
理解 CommonJS 模块的实现逻辑
CommonJS 是 Node.js 中使用的一种模块化规范,它的核心理念是每个文件都是一个独立的模块。通过 require 可以引入模块,module.exports 可以导出模块。咱们来一步一步拆解代码,看看它是怎么运作的。
定义一个 module 对象:
var module = {
exports: {}
};
这里 module 就是一个简单的对象,它有一个 exports 属性,默认是个空对象。这是为了保存你想要暴露给外部的内容,外部模块才能使用。
立即执行函数 (IIFE) :
(function (module, exports, require) {})(module, module.exports, require);
这个函数是立刻执行的。它接收三个参数:module,exports,和 require。
- module 是我们刚才定义的对象,用来管理模块的导出内容。
- exports 是 module.exports 的一个快捷方式,方便你直接通过 exports 来挂载你想暴露的内容。
- require 是一个函数,用来导入其他模块,比如我们在下面会用到它来导入 jQuery。
通过 require 导入 jQuery:
const $ = require('../jquery.min.js');
这行代码通过 require 方法引入了 jQuery 库。CommonJS 的 require 是用来加载外部模块的。路径 ../jquery.min.js 代表你要加载的 jQuery 文件。
定义私有变量和方法:
var name = 'damowang';
var getStore = function () {
return 60;
};
这里定义了两个私有变量:name 和 getStore 函数。它们只能在这个模块内部使用,外部是访问不到的,类似闭包里的局部变量。
使用 exports 导出想要暴露的内容:
exports.name = name;
exports.getStore = getStore;
通过 exports 对象,我们把 name 和 getStore 暴露给外部模块。这样,其他文件在 require 这个模块时,就可以使用这两个值。
导出整个模块:
module.exports = { name, getStore };
最后,我们也可以直接通过 module.exports 导出整个对象。这个对象包含 name 和 getStore。当外部使用 require 加载这个模块时,module.exports 的值就是导出的内容。
- require:用来导入其他模块。
- exports:用来挂载你想让别人用的东西。
- module.exports:模块最终导出的东西。当你使用 require 加载模块时,返回的就是 module.exports 的内容。
所以,CommonJS 模块通过 require 引入外部依赖,通过 exports 或 module.exports 把想要暴露的功能共享给其他模块。这套机制在 Node.js 中非常常见,也是模块化开发的重要基础。
手动实现 require:探索模块加载机制
在 Node.js 中,require 是加载模块的关键方法,负责将其他 JavaScript 文件中的 exports 暴露给当前模块使用。我们可以通过一个简化版的 require 来理解其内部工作原理。以下是手动实现 require 的过程。
// index.js
const name = "damowang"
const foo = () => {}
exports.name = name
exports.foo = foo
module.exports = {name, foo}
const { readFileSync } = require('fs');
const path = require('path');
const { Script } = require('vm');
// 自定义 require 实现
function my_require(filename) {
// 读取文件内容
const file = readFileSync(path.resolve(__dirname, filename), 'utf-8');
console.log(file); // 输出文件内容,方便调试
// 将文件内容包装成函数
const content = `(function(require, module, exports){ ${file}; })`;
// 在当前上下文中运行脚本
const res = new Script(content).runInThisContext();
// 初始化 module 对象,用于存储模块的导出内容
const module = {
exports: {}
};
// 执行包装后的函数,传入 require、module 和 exports
res(my_require, module, module.exports);
// 返回模块的导出内容
return module . exports ;
}
// 将 my_require 添加到全局对象中,方便使用
global.my_require = my_require;
// 测试 my_require 函数
console.log(my_require('./index.js')); // { name: 'damowang', foo: [Function: foo] }
const { name, foo } = my_require('./index.js');
console.log(name, foo); // damowang [Function: foo]
require 是 Node.js 中用于加载模块的核心方法,它的关键作用是将其他 JavaScript 文件中的 exports 暴露给当前模块。在手动实现 require 的过程中,我们首先通过 readFileSync 从磁盘读取指定的 JavaScript 文件内容。接着,将文件内容包装成一个自执行函数,以模拟 Node.js 的模块加载机制。然后,我们使用 vm 模块的 Script 类在当前上下文中执行这个包装后的函数,并初始化一个 module 对象来存储模块的导出内容。最后,通过 module.exports 返回模块的导出内容,从而完成模块的加载和执行。这个实现展示了如何通过自定义的 require 函数来加载和使用外部模块的内容。
结语
在本篇文章中,我们深入探讨了模块化在前端开发中的应用,重点介绍了如何使用闭包和揭示模块模式(Revealing Module Pattern)来管理私有变量,以实现更好的数据封装和接口暴露。通过对传统模块封装方法的分析,我们指出了它们的不足之处,并展示了如何通过闭包和立即执行函数(IIFE)来改进模块封装,提升数据保护性。进一步,我们探讨了如何在依赖管理中结合 IIFE 和 CommonJS 模块化规范,以解决依赖问题。我们还实现了一个简化版的 require 函数,演示了 Node.js 中模块加载机制的核心原理。总体而言,这些技术和方法为前端开发中的模块化提供了有效的解决方案,提高了代码的可维护性和模块的重用性。
转载自:https://juejin.cn/post/7415914023278116903