浅谈Node中的模块化

时间:2021-05-23 16:30:53

浅谈Node中的模块化

  关于这篇文章早在去年年初的时候我就想写一片关于模块化的文章,但是推到现在才来完成也有很多好处,巩固之前对Node的理解。

一、什么是模块化

现实生活中的模块化例子, 手机组装: 摄像头、话筒、听筒、显示器、电池。。。。。。

非模块化开发的问题 :

1、命名冲突,比如全局污染。。。;

2、文件依赖,团队变大后,维护大量的文件依赖关系非常困难,公共模块的维护、升级很不方便团队变大后,维护大量的文件依赖关系非常困难,公共模块的维护、升级很不方便;

3、可维护性低等等。

二、模块化的演变过程

  从最简单的加减乘除运算来举例说明,为了方便理解这里都没有采用ES6的语法。

1、全局函数的方式——最原始的写法

    // 早期的开发过程中就是将重复使用的代码封装到函数中
// 再将一系列的函数放到一个文件中,称之为模块
// 缺点:存在命名冲突,可维护性也不高的问题
// 仅仅从代码角度来说:没有任何模块的概念
function convertor(a) {
return parseFloat(a);
} function add(a, b) {
return convertor(a) + convertor(b);
}

2、封装对象的方式

    // 有了传统编程语言中的命名空间
// 从代码层面就已经有了模块的感觉
// 避免了多处全局污染
// 缺点:没有私有空间,没有抽离私有成员
var calculator = {
add: function (a, b) {
return this.convertor(a) + this.convertor(b);
},
convertor:function(a){
return parseFloat(a)
}
};

3、私有空间的划分

    // 这里形成一个单独的私有的空间
// 高内聚,低耦合:模块内部相关性强,模块之间没有过多相互牵连,如convertor和add
// 缺点:可扩展性低
var calculator = (function () {
// 将一个成员私有化,外部无法访问和修改
function convertor(a) {
return parseFloat(a);
}
// 抽象公共方法(其他成员中都会用到的)
function add(a, b) {
return convertor(a) + convertor(b);
}
return {
add:add
}
})();

4、模块的扩展

  // calc_v2016.js
(function (window,calculator) {
function convert(input) {
return parseFloat(input);
}
calculator = {
add: function (a, b) {
return convert(a) + convert(b);
}
}
window.calculator = calculator;
})(window, {}); // 新增需求 remain
// calc_v2017.js
// 开闭原则:对新增开放,对修改关闭
(function (calculator) {
function convert(input) {
return parseInt(input);
}
// calculator 如果存在的话,我就是扩展,不存在我就是新加
calculator.remain = function (a, b) {
return convert(a) % convert(b);
}
window.calculator = calculator;
})(window.calculator || {});

5、第三方依赖

  // calc_v2016.js
(function (window,calculator) {
//对全局产生依赖,不能这样用
console.log(document);
function convert(input) {
return parseFloat(input);
}
calculator = {
add: function (a, b) {
return convert(a) + convert(b);
}
}
window.calculator = calculator;
})(window, {}); // 新增需求
// calc_v2017.js
(function (calculator,document) {
// 依赖函数的参数,是属于模块内部
console.log(document);
function convert(input) {
return parseInt(input);
}
calculator.remain = function (a, b) {
return convert(a) % convert(b);
}
window.calculator = calculator;
})(window.calculator || {},document);

  以上通过一些简短的代码介绍了模块化发展大致情况。

三、模块化规范

1、服务器端规范

   CommonJS---nodejs

2、浏览器端规范

  AMD---RequireJS 国外相对流行(http://www.requirejs.cn/)

  CMD---SeaJS 国内相对流行(http://seajs.org/)

3、ES6的module规范

在这里就不具体展示每种规范的具体写法了。

四、CommonJS规范

1、Node 采用的模块化结构是按照 CommonJS 规范

模块与文件是一一对应关系,即加载一个模块,就是加载对应的一个模块文件;

CommonJS 就是一套约定标准,不是技术; 用于约定我们的代码应该是怎样的一种结构;

2、CommonJS 模块的特点

所有代码都运行在模块作用域,不会污染全局作用域;

模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。module.exports不会再次执行该模块;

模块加载的顺序按照其在代码中出现的顺序;

3、模块的分类

自定义模块:就是我们自己写的功能模块文件。

核心模块:Node 平台自带的一套基本的功能模块。

第三方模块:社区或第三方开发好的功能模块,可以直接拿回来用。

4、模块的定义

(1)Node 内部提供一个 Module 构建函数。所有模块都是 Module 的实例,属性如下:

  module.id 模块的识别符,通常是带有绝对路径的模块文件名。

  module.filename 模块定义的文件的绝对路径。

  module.loaded 返回一个布尔值,表示模块是否已经完成加载。

  module.parent 返回一个对象,表示调用该模块的模块。

  module.children 返回一个数组,表示该模块要用到的其他模块。

  module.exports 表示模块对外输出的值。

(2)载入一个模块就是构建一个 Module 实例,一个新的 JS 文件就是一个模块

  导出方式:module.exports 和 exports

  exports.name = value;

  module.exports = { name: value };

  module.exports 是用于为模块导出成员的接口;

  exports 是指向 module.exports 的别名,相当于在模块开始的时候执行:var exports = module.exports。

5、用Node手写一个简单的require

function $require(files) {
const fs = require('fs');
const path = require('path');
let filename = path.join(__dirname, files);
   //注意,这里实现的缓存不是Node的缓存机制
$require.cache=$require.cache||{};
if($require.cache[filename]) return $require.cache[filename].exports; let dirname=path.dirname(filename);
let file = fs.readFileSync(filename);
let module = {
id:filname,
exports: {}
};
//保存module.exports重新赋值前的值
let exports = module.exports;
let code = `(function (module,exports,__dirname,__filename) {
${file}
})(module,exports,dirname,filename)`;
eval(code); $require.cache[filename]=module;
return module.exports;
}

五、Node加载文件规则

Node 使用 CommonJS 模块规范,内置的 require 函数用于加载模块文件。

require 的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象。 如果没有发现指定模块,会报错。

require 加载文件规则:

(1)require 加载js文件时可以省略扩展名;可以直接加载json文件;

(2)通过 ./ 或 ../ 开头:则按照相对路径从当前文件所在文件夹开始寻找模块;

    require('../file.js'); => 上级目录下找 file.js 文件

(3)通过 / 开头:则以系统根目录开始寻找模块;

    require(‘/Web/Vue/vue-full/src/main.js'); => 以绝对路径的方式找;

(4)如果 require 传入的是一个目录的路径,会自动查看该目录的 package.json 文件,然后加载 main 字段指定的入口文件;

(5)如果package.json文件没有main字段,或者根本就没有package.json文件,则默认找目录下的 index.js 文件作为模块;

(6)如果参数字符串不以“./“ 或 ”/“ 开头,则表示加载的是一个默认提供的核心模块(位于 Node 的系统安装目录(node_modules)中;

(7)缓存文件的加载优先级最高,同名的系统模块要比自定义模块优先级高;

(8)Node在加载系统模块的时候,如果当前文件夹里面没有node_modules文件夹就会去逐层向上查找至项目根目录直到找到为止,如果没有就会报错。

六、总结

以上就是我对模块化加载的理解,当然这里没有去过多的写案例,目的是为了以后回来查阅方便,方便自己记忆。