node CommonJS modules (二)

https://nodejs.org/docs/latest-v14.x/api/modules.html

Modules:CommonJS modules
>node模块系统会将每个文件都是为一个单独的模块。比如
foo.js:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

circle.js:

const { PI } = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r;

运行:
node foo.js
注意:模块中的变量是私有的,如PI是circle模块的私有变量
模块可以为导出属性分配新值,如方法或对象,如:
square.js:

// 把对象分配到导出属性不改变 module, 必须使用 module.exports
module.exports = class Square {
  constructor(width) {
    this.width = width;
  }
  area() {
    return this.width ** 2;
  }
};

bar.js

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`The area of mySquare is ${mySquare.area()}`);

>访问主模块
直接从node.js运行 require.main === module
对于一个文件运行: node foo.js
module 提供了一个filename属性,app的入口点可以从检车 require.main.filename 获得
>包管理器提示
Node.js 的 require() 方法足够通用,使用包管理工具如dpkg,rpm,npm从node.js模块构建初始包不需要修改
下面给出一个可行的建议目录结构:
如果我们希望在 /usr/lib/node/<some-package>/<some-version> 保存一个特定版本的文件夹
包可以相互依赖,如果希望安装foo包,就需要安装一个特定版本的bar包,bar包也许会有自己的依赖,某些情况下可能会产生冲突或依赖循环
因为node.js 查找所有加载模块的真实路径(解析软链接),然后查找在node_modules文件夹中查询他们的依赖,这种情况可以被以下结构(架构)解决:
1,/usr/lib/node/foo/1.2.3/: foo包, 版本1.2.3.
2,/usr/lib/node/bar/4.3.2/: foo依赖的bar包
3,/usr/lib/node/foo/1.2.3/node_modules/bar: 软链接到 /usr/lib/node/bar/4.3.2/.
4,/usr/lib/node/bar/4.3.2/node_modules/*: 软链接到bar依赖的包
这样,即使遇到循环或依赖冲突,每个模块都可以用到它可以使用的版本
为了使模块查找更优化,我们将包放在 /usr/lib/node_modules/<name>/<version>
为使模块对Node.js的REPL可用,我们把 /usr/lib/node_modules添加到 $node_PATH环境变量中
>.mjs扩展
ECMAScript的保留模块无法通过require()加载
>总之...
如果需要被加载的确定的文件名,需要使用require.resolve()函数
综上所述,下面是require()伪代码的高级算法
关于require的加载详细步骤,太长了,自己看文档
>缓存
模块会在第一次加载后被缓存
提供的require.cache 不会被修改,多次调用require('foo')不会被多次执行
要多次执行模块代码,export方法并多次调用
>模块缓存注意事项
模块根据解析的文件名缓存,但是如果路径不同,如果解析不同的路径,相同的文件会解析为不同的对象
不区分大小写的文件系统或操作系统上,不同的解析文件名可以指向同一个文件,但是会多次加载,返回不同对象
>核心模块
node.js 有几个模块被编译为二进制文件
核心模块在lib/文件夹中,在node.js源码中定义
如果使用require()加载核心模块,即使有同名文件,优先加载核心模块
可以使用node:prefix标识核心模块,这样会绕过require缓存,如 require('node:http'),始终返回内置http模块
>周期
当存在循环的require()调用,模块返回时可能尚未完成执行
考虑下面的情况:
a.js:

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b.js

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

main.js

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);

main.js加载a.js,a.js加载b.js,b.js加载a.js,为了避免循环调用,a.js提供了一个未完成的复制导出对象给了b.js,b.js完成了加载,然后将自身对象提供给a.js,main.js 加载完了两个模块。
需要仔细规划,然后再循环模块依赖中程序才能正常工作
>文件模块
如果文件名没找到,node.js将会查找 .js .json 和 .node的后缀文件,如果是特殊的应该用全名 require('./file.cjs')
.json被解析为json文本,.node被解析为加载了process.dlopen()的编译插件模块,其他的被解析为js文本文件
/ 是绝对路径, ./ 和 ../ 是相对路径, 如果没有这三个前导,则模块必须是核心模块或从node_modules文件夹加载
>从node_modules文件夹加载
如果传递给require()的没有斜杠前导,并不是核心模块,就从当前目录开始添加/node_modules,然后尝试加载模块
如果没有找到,就移动到上级目录,一直到文件系统根目录
>从全局文件夹加载
如果其他地方没找到模块,将会找NODE_PATH中用冒号分隔的环境变量,windows用分号分隔
NODE_PATH仍然受支持,但现在不太需要了,建议将依赖放在本地node_modules文件夹中
>模块包装器
在执行模块代码之前,node.js将使用一个函数包装器包装它:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});

这样,它将顶级变量(var,const,let)作用域定义为模块,而不是全局对象
它有助于提供一些实际特定于模块的全局变量:
1,模块和导出对象,实验者可以使用这些对象从模块导出值
2,方便__filename和__dirname,包含模块的绝对文件名和目录路径
>模块范围
1,__dirname 相当于 path.dirname(__filename)
2,__filename 当前模块的绝对路径
>exports 导出
是 module.exports的简写,关于何时使用 module.exports 详见 exports shortcut 部分
>module
见 module.exports 部分,使用 require() 导入
>require(id)
id 是模块名或者路径,返回模块内容
用来导入模块,json,和本地文件,可以使用相对路径,根据 __dirname,

const myLocalModule = require('./path/myLocalModule');
const jsonData = require('./path/filename.json');
const crypto = require('node:crypto');

>require.cache
模块在请求时缓存在此对象中
如果有和内置模块同名的模块添加到缓存中,则要访问内置模块需要加签注 node:
const assert = require('node:assert');
>require.main
标识node的入库脚本模块对象
entry.js 中 console.log(require.main)
node entry.js
>require.resolve(request[, options])
request 需解析的模块路径
options.path 从中解析模块位置的路径,也就是node_modules的位置路径
使用内如require() ,查找模块位置,返回解析的文件名字符串,但不加载模块
require.resolve.paths(request)
request 模块路径,返回字符串数组,如果是核心模块返回null
>module object
>module.children
>module.exports
module.exports是Module系统创建的,有时候不是必须
>module shortcut
module.exports.hello = true; // 从模块请求导出
exports = {hello:false}; // 重新绑定到exports,不能导出,在模块内使用
>module.filename
模块的完全解析文件名
>module.id
模块标识符,通常是完全解析的文件名
>module.isPreloading
boolean,模块是否在node.js预加载期间运行
>module.loaded
boolean,模块是否已完成加载
>module.path
模块的目录名,通常和apth.dirname(module.id)相同
>module.paths
模块的检索路径 arr
>module.require(id)
一种加载模块的方法,和require()相似,返回模块内容

 

posted @ 2022-09-08 16:29  jqynr  阅读(56)  评论(0)    收藏  举报