likes
comments
collection
share

Nodejs 知识体系(五): Node 标准模块化本质上是一种架构约定,是架构师和开发人员共同定义的对外接口标准。 使

作者站长头像
站长
· 阅读数 45

模块化本质上是一种架构约定,是架构师和开发人员共同定义的对外接口标准。

使用闭包与揭示模块模式管理私有变量

在前端开发中,我们可以利用闭包揭示模块模式(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
评论
请登录