全局对象
在前面关于node模块的学习过程中,我们已经知道在编译执行的过程中,通过闭包的方式,来将一个模块进行包裹,且追加自定义变量属性
require
、exports
、module
、__dirname
、__filename
,因此,每一个 在node环境下的模块都拥有这几个属性,因此也就成为了"模块中公共访问的变量:全局变量"(其实还是局部变量的感觉,只在各个模块中有效,然后各个模块在node模块中统一访问,指向同一个方法)
。 👇 来一一解读关于各个变量都有什么作用?如何使用的!!
模块件的全局变量
require
通过调用该方法,实现从node模块中导入对应的对象/方法,可以从自己编写的模块中导入模块,也可以从node库中导入。 这里附带上
require
的一个简写逻辑,帮助更好的理解关于module.exports
与exports
两者的关系
这里我们看出其实module.exports
与exports
都指向的是一个中间临时函数对象,在加载的时候,通通采用的这种逻辑来实现的!
这也就解释了为什么 的一个输出情况了:
function xxx(){}
const yy = 123;
const zz = 'abc';
module.exports = {
xxx,
yy
}
exports.zz = zz;
console.info('来自于第一个文件的引用');
console.info(module.exports);
console.info(exports);
exports
指向
module.exports
所指向的对象,而不是网上一些说法:exports指向module.exports对象,因为根本就不是同一个,而是指向的同一个对象,当在模块中定义了module.exports的时候,两者的区别就比较明显了, 一般通过在exports
对象上添加对象/方法来实现将模块中的部分对象/方法暴露出来,供给require()
使用。 关于module
、module.exports
、exports
三者的关系,傻傻分不清? 通过 这里可以将三者的关系有了一个清晰的认知!
module
代表当前js在node环境中的模块引用表示,使用
module.exports
可以用来定义模块导出的内容,并供给require()
使用!
__dirname
当前js模块所在的目录,与
path.dirname()
方法返回值一样
__filename
当前js模块的绝对路径,跟随者所在的系统而定。
程序运行时的上下文--process
作为当前node程序app的控制器,提供应用程序的上下文管理,这有点类似于
Android中的Application
,可以将一些全局的变量/方法/对象直接存储到全局对象process中,任何node模块都可以访问到 导入方式:
const process = require('node:process');
process对象也是
EventEmitter
事件对象的一个实例,也就是说该对象可以使用事件的相关api来进行异步I/O调用以及监听操作!!! 源码所在位置:lib/process.js
相关API一览:
监听事件
- 程序退出(beforeExit与exit): 当nodejs程序的事件循环队列为 时,没有待消费的事件被丢出来,程序将会回调该方法,而且,我们可以在该方法中进行一个同步调用,延长程序的生命周期
const process = require('node:process'); process.on('beforeExit', code => { console.info('程序即将要退出了,code=' + code); // 这里禁止在beforeExit一直加上这个动作,因为它将会让事件循环队列一直非空,导致程序一直处于消费最后一个事件的死循环中!!! // setTimeout(() => { // console.info('程序即将退出,额外再调用多一个动作'); // }, 500); }); process.on('exit', code => { console.debug('程序退出,code=', code); setTimeout(() => { console.debug('程序退出了,我将不会被执行到!!'); }, 0); }); console.info('程序启动了');
那么关于beforeExit
的使用,应该减少在回调中发起另外的一些异步调用动作,避免事件循环队列中一直非空,而且每消费最后一个事件后,又又又再怼入了一个一个事件,因此我们应当是在这个回调中执行一些同步性的操作!
而如果在exit
中执行异步调用的话,将会直接是放弃掉这个调用!
关于程序的退出code的情况,具体见底部 的一个描述
- 异步异常(unhandledRejection与rejectionHandled)
当一个promise被reject并且在下一次事件循环之前被处理,则会发起
rejectionHandled
事件,代表reject异常得到处理;
当一个promise被reject的时候,则会触发unhandledRejection事件;
const process = require('node:process');
const unhandledRejections = new Map();
process.on('unhandledRejection', (reason, promise) => {
console.info('unhandledRejection接收到了', reason, promise);
unhandledRejections.set(promise, reason);
});
process.on('rejectionHandled', promise => {
console.info('rejectionHandled接收到了', promise);
unhandledRejections.delete(promise);
});
new Promise((resolve, reject) => {
reject(123);
});
这里一个promise未被处理,因此unhandledRejection
事件会被触发,而如果我们将promise的发起与处理,进行一个
处理的话,那么rejectionHandled
事件将会被触发!
new Promise((resolve, reject) => {
reject(123);
}).then(null, err => {
setTimeout(() => {
console.info(err, '异常被处理了');
}, 3000);
});
从上述基本上我们可以知晓关于这两个事件的一个使用场景:当一个promise的reject被reject的时候,如果没有进行及时的catch,
那么,我们则可以使用这个unhandledRejection
来捕获,并记录当前异步调用的对象信息,而当reject的promise被及时处理的时候,我们标记为已
删除的promise,这可以用来监控整个程序在对于为捕获的异常是如何处理的,以此来提高自身编码能力!!!
- 异常监控(uncaughtException与uncaughtExceptionMonitor)
uncaughtException
事件有点类似于android中的application的全局异常捕获机制,当一个未知的异常发生的时候,将会触发这个事件, 而且让当前的程序的退出编码为1,而非正常退出, ```javascript const process = require('node:process');
process.on('uncaughtException', (err, origin) => { console.info('未知的错误发生了', err, origin); }); setTimeout(() => { console.info('在异常后执行的异步回调'); }, 0); xxx(); // 调用一个不存在的函数,将会报错 console.info('这行代码将不会被执行到!');
![捕获未知异常](捕获未知异常.png)
<img align='absmiddle' alt=':point_up_2:' class='emoji' src='/gitbook/gitbook-plugin-advanced-emoji/emojis/point_up_2.png' title=':point_up_2:' /> 这里调用了一个不存在的函数,则直接报错,并让程序直接异常退出,这个是常规node程序的一个运行过程,这里我们切忌
不能直接在这个回调事件中进行将程序自动重启,这 <img align='absmiddle' alt=':u6709:' class='emoji' src='/gitbook/gitbook-plugin-advanced-emoji/emojis/u6709.png' title=':u6709:' /> 可能会导致程序一些资源的异常,比如db的连接,文件的读写,我们应该是在
程序奔溃的时候,**让程序遵循其生命周期,而非强行控制**,:confused: 假如我们还是想要让程序在出现异常的时候,自动进行程序在异常
退出时自动重启,:six_pointed_star: 可通过外部程序来监控当前程序,让其当出现异常并且退出的情况下,**同步**记录异常信息后,自动重启
程序,简而言之,就是程序要退出了,我们就让它自动去退出,而不去干预,可以做的只是让其退出!!!
<img align='absmiddle' alt=':confused:' class='emoji' src='/gitbook/gitbook-plugin-advanced-emoji/emojis/confused.png' title=':confused:' /> <img align='absmiddle' alt=':u6709:' class='emoji' src='/gitbook/gitbook-plugin-advanced-emoji/emojis/u6709.png' title=':u6709:' /> 了`uncaughtException`还有这个`uncaughtExceptionMonitor`事件,这嘎嘎是用来作甚的吖?
<img align='absmiddle' alt=':point_right:' class='emoji' src='/gitbook/gitbook-plugin-advanced-emoji/emojis/point_right.png' title=':point_right:' /> 在发出`uncaughtException`事件或者通过调用process.setUncaughtExceptionCaptureCallback()安装程序的钩子函数之前,会调用`uncaughtExceptionMonitor`事件,
<img align='absmiddle' alt=':point_down:' class='emoji' src='/gitbook/gitbook-plugin-advanced-emoji/emojis/point_down.png' title=':point_down:' /> 在之前的例子加入几行代码:
```javascript
// .......此处省略代码
process.on('uncaughtExceptionMonitor', (err, origin) => {
console.info('我是uncaughtExceptionMonitor事件,被调用了我!');
console.info(origin);
});
// .......此处省略代码
警告(warning) 警告事件并不是nodejs与javascript错误处理流程的一部分,它仅仅是检测到代码中可能会导致程序性能、错误、或者安全漏洞的不良编码行为时,所发出的警告,其目的是更好地为了程序的执行, 默认情况下的warning信息是通过普通的
stderr
来输出的比如咱们的控制台const process = require('node:process'); const events = require('node:events'); process.on('warning', warning => { console.info('警告警告:', warning); }); const emitter = new events.EventEmitter(); emitter.setMaxListeners(1); emitter.on('foo', () => {}); emitter.on('foo', () => {});
这里我们创建了一个设置了一个对警告的监听,假如正常运行程序的话,将会直接报错,因为这个是node环境所发出的,将异常信息进行输出! 而如果追加了
--no-warning
参数给node的话,则将不会输出警告信息:进程通信(message与worker、disconnect) 当创建了一个worker的时候,这个
worker
事件将会被回调; 如果当前程序所在进程是通过IPC通道生成的,那么只要子进程接收到父进程使用childprocess.send()
发出的消息,就会发出message
事件 如果当前程序是用过IPC通道生成的,那么当IPC通道关闭的时候,disconnect
事件将会被调用!信号事件
属性成员
- 启动程序相关参数(execArgv、argv、argv0)
execArgv属于当node程序执行时候,传递给node的相关参数
argv表示启动node程序时候,传递的命令行参数,但不包括execArgv
argv0表示启动node程序时的argv的第一个值,也就是
argv[0]
是关于node命令行的一个组成
const process = require('node:process');
console.info('以下是argv');
console.info(process.argv);
console.info('以下是argv0');
console.info(process.argv0);
console.info('以下是execArgv');
console.info(process.execArgv);
- 程序运行时环境变量(env) 程序运行时所在的环境变量用env表示,我们可以往这个env中进行一个临时变量的存储,目前以及以后的版本将只允许丢字符串的变量进来了,比如我们目前用的vue项目,我们可以从process.env.ENVIROMENT变量中获取当前程序的运行环境, 因为提前将变量进行了赋值操作!!!
const process = require('node:process');
console.info(process.env);
process.env.foo = 'bar';
console.debug(process.env);
从 我们可以看出process.env允许我们将用户自定义变量,通过配置的方式传递进去,来达到动态地控制不同的变量执行不同的函数的过程,这里假如 这样子的一个场景:
如果我们想要运行一个程序,然后该程序在开发阶段采用开发阶段的host,在生产阶段使用生产阶段的host,这一个过程采用命令行 脚本化参数的方式来实现,是否可以的呢?
答案是肯定的,我们可以通过从命令行参数中获取到用户传递进来的所有的变量,然后将对应的变量存储到process.env对象中,那么我们则可以在程序的运行过程中从env对象中获取,这也许就是crossenv
库的工作原理吧!!!
实例函数
- exit,用于控制程序直接退出以怎样的一个退出状态码方式来退出,可用来标记是系统的退出还是自己编写的程序的异常退出,一般一旦执行,就直接等对应的
beforeExit
以及exit
回调执行后来退出!
const process = require('node:process');
process.on('exit', () => {
console.info('退出了');
});
process.exit(1);
console.info('exit后的同步调用');
setTimeout(() => {
console.debug('设置了exitCode=1后的动作');
}, 500);
上面这里再执行完成process.exit(1)
之后,程序就处于等待退出状态,后续的其他的操作都会被抛弃掉!!!
一般情况下,我们没有必要自行去调用这个process.exit()
,而是交由程序来自动的进行执行,并到了应该退出的时候,不编写对应的执行操作即可
- 抛出自定义异常(emitWarning)
通过自定义异常,可以实现一个全局的自定义异常捕获,可以将异常进行自定义,比如账号错误、密码错误、操作异常、数据库异常等等,然后通过对应的工厂来创建对应的自定义异常对象, 这里可以采用将异常进行自定义封装,然后在需要调用的时候,就抛出这个自定义异常,实现业务异常的统一处理。const process = require('node:process'); process.emitWarning(warning, { type: '代表warnging的名字描述', code: '一个自定义的唯一code', ctor: '当warning是一个字符串的时候,该函数用来限制生成的执行堆栈', detail: '额外的异常描述字符串' });
const process = require('node:process');
process.on('warning', warning => {
console.info(warning);
});
process.emitWarning("我的自定义异常", {
code: 'myWarning',
detail: '这个是来自xxx的异常'
});
拦截未捕获的异常(setUncaughtExceptionCaptureCallback与hasUncaughtExceptionCaptureCallback) 当我们通过
setUncaughtExceptionCaptureCallback
方法来设置异常捕获的时候,原本的uncaughtException
回调将不会被调用, 也就是它与回调方法基本上是属于互斥的状态!!异步调用方法(nextTick) 在经过之前的学习,我们已经知道关于node程序的一个事件循环以及事件消费的过程原理,这里通过
process.nextTick
的方式,往循环队列中添加一个 待被执行的回调,然后消费者自动从中获取事件来执行的过程。
nextTick发生在每一次事件循环之前,假如这个时候我们采用的在nextTick中不断的回调的话,那么这个时候js执行的CPU时间将会被它消耗殆尽!
记住了关于nextTick的一个触发机制,我们则可以在创建一个新的对象之后,立马安排它在资源可用
的情况进行一个执行操作,这有点类似于前端的编程模式,原本需要获取到某个节点才进行对这个节点
执行某个操作,通过this.$nextTick
的方式,来等待到资源准备好之后,触发这个动作!
为什么要使用这个process.nextTick
呢
一切都为了更及时
地来使用资源,看下面的例子就 一个比较深刻的印象了
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor() {
super();
this.emit('event');
}
}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
一般情况下,我们创建一个自定义的事件监听器对象,可以通过继承于EventEmitter
对象,然后来创建对事件的监听,假如我们想要
在一创建就想立马监听并触发一个响应动作的话,正常情况下,我们是new出一个对象之后,通过显式地调用emit来触发,而不能通过简单地在其构造函数中进行触发,
那么这里时候我们可以采用process.nextTick
的方式,让系统告知程序,我资源准备好了,你可以触发事件了
,通过nextTick将其进行一个包裹
// ...这里省略相关的代码
process.nextTick(() => {
this.emit('event');
});
// ...这里省略相关的代码
exitCode一览
一般一个node正常执行的时候,其process.exitCode默认为0,然而有时候,程序在意想不到的情况下,出现了异常并退出了,这个时候,process.exitCode将 不同的取值,:point_down: 列举一下不同场景下的exitCode值以及其对应的含义:
exitCode值 | 异常名称 | 描述或场景 |
---|---|---|
1 | uncaughtFatalException | 未捕获的致命异常,未能预先在程序中处理到的异常,将会导致该异常退出的情况,这个一般比较常见 |
2 | Unused | 没用过 |
3 | Internal JavaScript Parse Error | js解析错误,一般在开发编码阶段就会发生了,比较少见 |
4 | Internal Javascript Evaluation Failure | js内部引导错误,比较少见 |
5 | Fatal Error | v8引擎中出现的不可恢复的致命错误 |
6 | Non-function Internal Exception Handler | 函数内部异常,到时原本应该是函数来调用编程非函数调用 |
7 | Internal Exception Handler Run-Time Failure | 捕获到异常错误信息后,处理异常错误时又报了异常,编写处理异常代码有异常 |
8 | Unused | |
9 | Invalid Argument | 原本函数调用应该传递的一个参数,但在实际调用的过程中没有传递 |
10 | Internal JavaScript Run-Time Failure | 同Internal JavaScript Parse Error |
12 | Invalid Debug Argument | 传递了无效的调试参数 |
14 | Snapshot Failure | 启动v8引起快照,但由于某个原因导致其失败 |
>128 |
SignalExits | 当nodejs接收到SIGKILL 或者SIGHUP 的致命信号时发出的 |
process总结
通过学习了关于process,可以更加深入的理解自己的程序,以及借助于process所提供的属性和api以及事件监听,更好地来编写自己的程序,从 异常的捕获、未知错误的抛出监听,到像linux中控制进程般来控制自己所编写的程序,像android中编写的继承于Application的方式来理解process的 生命周期,来编写更加健壮、稳定的代码,结合vue这种借助于webpack打包来实现的env共享,可以更好地来理解vue程序是如何做到共享运行时变量的!!