JavaScript 中存在一个特殊的全局对象,可以在任意位置被访问,通常用 globalThis 指代。在浏览器中,指向 window 这个全局对象,而 Node.js 中指向 global。当我们直接使用一些无需定义的方法时 (例如 console,setTimeout 等),它们都是 global (Global objects) 上的属性。
开篇
Node.js 中存在几个全局对象,global、globalThis、__filename、__dirname,其中 globalThis 在 Node.js 中指向 global,也就是 global === globalThis,而在浏览器中 window === globalThis。
我们要查看 Node.js 中 global 对象的所有方法可以用过执行下列代码列举出来:
console.log(Object.getOwnPropertyNames(global))
这个命令如果显示不全输出结果,可以用下列命令格式化输出:
console.table(Object.getOwnPropertyNames(global))
下列就是我们的输出结果,常见的不作描述:
| # | 名称 | 作用 |
|---|---|---|
| 0 | Object | |
| 1 | Function | |
| 2 | Array | |
| 3 | Number | |
| 4 | parseFloat | |
| 5 | parseInt | |
| 6 | Infinity | 无穷大 |
| 7 | NaN | 非数字 |
| 8 | undefined | |
| 9 | Boolean | |
| 10 | String | |
| 11 | Symbol | 用于创建唯一的标识符,Symbol('description') != Symbol('description') |
| 12 | Date | |
| 13 | Promise | 处理异步操作的一种方式 |
| 14 | RegExp | |
| 15 | Error | 其他错误类型的基类 |
| 16 | AggregateError | 多个错误 |
| 17 | EvalError | 兼容历史版本的Eval错误,目前已弃用 |
| 18 | RangeError | 数值不在给定的范围内 |
| 19 | ReferenceError | 未知引用 |
| 20 | SyntaxError | 解析的代码不合法 |
| 21 | TypeError | 在运行时尝试执行对于给定数据类型不合法的操作 |
| 22 | URIError | 处理统一资源标识符(URI)时发生了错误 |
| 23 | globalThis | globalThis 本身就是全局对象 global 的一个属性,这意味着它是自引用的。 |
| 24 | JSON | |
| 25 | Math | |
| 26 | Intl | 字符串比较、日期数字格式化 |
| 27 | ArrayBuffer | 一段固定长度的原始二进制数据缓冲区 |
| 28 | Uint8Array | 类型化数组,无符号8位整数数组,用于存储0到255之间的整数值 |
| 29 | Int8Array | 类型化数组,有符号8位整数数组,用于存储-128到127之间的整数值 |
| 30 | Uint16Array | 无符号16位整数数组,用于存储0到65535之间的整数值 |
| 31 | Int16Array | 有符号16位整数数组,用于存储-32768到32767之间的整数值 |
| 32 | Uint32Array | 无符号32位整数数组,用于存储0到4294967295之间的整数值 |
| 33 | Int32Array | 有符号32位整数数组,用于存储-2147483648到2147483647之间的整数值 |
| 34 | Float32Array | 32位浮点数数组,用于存储符合IEEE 754标准的单精度浮点数 |
| 35 | Float64Array | 64位浮点数数组,也称为双精度浮点数数组,用于存储符合IEEE 754标准的双精度浮点数 |
| 36 | Uint8ClampedArray | Uint8Array超出范围会阶段,Uint8ClampedArray超出范围会取最边缘的值,0或者255 |
| 37 | BigUint64Array | |
| 38 | BigInt64Array | |
| 39 | DataView | 一个低级接口,提供了一个更灵活的方式来读取和写入二进制数据,DataView 提供了对 ArrayBuffer 的字节级访问 |
| 40 | Map | Map 是有序键值对,键可以是任何类型的值 |
| 41 | BigInt | |
| 42 | Set | 集合,不是键值对,Map的键唯一,Set的对象唯一,插入重复值,Set会忽略,Map插入重复键值会更新 |
| 43 | WeakMap | 和Map相比,WeakMap的键一定是一个对象 |
| 44 | WeakSet | 参考 Set 和 WeakMap,值一定是一个对象,弱引用 |
| 45 | Proxy | 代理,和ajax的proxy没啥关系 |
| 46 | Reflect | 它提供了拦截 JavaScript 操作的方法,可以配合 Proxy 使用 |
| 47 | FinalizationRegistry | 它允许你在对象被垃圾回收时执行一些清理操作,可以理解成监听某对象的垃圾回收 |
| 48 | WeakRef | 弱引用对象 |
| 49 | decodeURI | |
| 50 | decodeURIComponent | |
| 51 | encodeURI | 对URL编码,保留URI本身有特殊意义的字符 |
| 52 | encodeURIComponent | 对URL编码,几乎所有非字母数字的字符都会被编码 |
| 53 | escape | 处理 URL 编码的函数,过时,不推荐,使用 encodeURIComponent 代替 |
| 54 | unescape | 处理 URL 解码的函数,过时,不推荐,使用 decodeURIComponent 代替 |
| 55 | eval | 用于执行一段字符串形式的 JavaScript 代码,不安全,应当避免使用 |
| 56 | isFinite | 检查传递给它的值是否为有限数值 |
| 57 | isNaN | 检查值是否为NaN |
| 58 | console | 提供控制台相关方法 |
| 59 | process | 提供当前 Node.js 进程的信息和控制能力 |
| 60 | global | 全局命名空间对象,Node.js中 globalThis 指向 global |
| 61 | Buffer | 用于处理二进制数据的类,在 Node.js 中用于直接操作内存中的数据 |
| 62 | clearImmediate | 取消由 setImmediate() 方法设置的定时执行操作 |
| 63 | setImmediate | 设定一个操作在当前事件循环周期完成后立即执行 |
| 64 | URL | |
| 65 | URLSearchParams | 用于处理 URL 的查询字符串 |
| 66 | DOMException | 在浏览器环境中表示一个异常,Node.js 环境一般不使用 |
| 67 | AbortController | 用于取消一些可取消的操作,如fetch请求 |
| 68 | AbortSignal | 由 AbortController 发出的信号,用于与可取消的操作通信 |
| 69 | Event | 在浏览器环境中表示一个事件,Node.js 环境一般不使用 |
| 70 | EventTarget | 一个可以接收事件的对象,并对它们进行处理 |
| 71 | TextEncoder | 用于将字符串编码为一个 Uint8Array |
| 72 | TextDecoder | 用于将 Uint8Array 解码为字符串 |
| 73 | TransformStream | 允许开发者创建一个转换流,用于在数据被读取和写入之间进行转换 |
| 74 | TransformStreamDefaultController | 用于控制转换流的行为 |
| 75 | WritableStream | 表示一个可以写入数据的流 |
| 76 | WritableStreamDefaultController | 用于控制可写流的状态和行为 |
| 77 | WritableStreamDefaultWriter | 用于向可写流写入数据 |
| 78 | ReadableStream | 表示一个可以读取数据的流 |
| 79 | ReadableStreamDefaultReader | 用于从可读流中读取数据 |
| 80 | ReadableStreamBYOBReader | “Bring Your Own Buffer”(自带缓冲区)的可读流读取器 |
| 81 | ReadableStreamBYOBRequest | 用于请求新的数据块填充到开发者提供的缓冲区中 |
| 82 | ReadableByteStreamController | 用于控制字节流的读取 |
| 83 | ReadableStreamDefaultController | 用于控制可读流的状态和行为 |
| 84 | ByteLengthQueuingStrategy | 一种队列策略,用于基于字节长度控制流的背压 |
| 85 | CountQueuingStrategy | 一种队列策略,用于基于块的数量控制流的背压 |
| 86 | TextEncoderStream | 用于将文本流式地编码为 UTF-8 格式的字节流 |
| 87 | TextDecoderStream | 用于将字节流流式地解码为文本 |
| 88 | CompressionStream | 用于创建一个将数据压缩的流 |
| 89 | DecompressionStream | 用于创建一个将数据解压的流 |
| 90 | clearInterval | 取消由 setInterval 设置的定时重复执行的操作 |
| 91 | clearTimeout | 取消由 setTimeout 设置的定时执行操作 |
| 92 | setInterval | 按照指定的周期(以毫秒计)来重复执行指定的函数 |
| 93 | setTimeout | 在指定的延迟后执行指定的函数 |
| 94 | queueMicrotask | 将一个任务排入微任务队列,它在当前执行栈清空后立即执行 |
| 95 | structuredClone | 提供了一种方法来深拷贝 JavaScript 结构,包括复杂对象如 Map, Set, ArrayBuffer, 等 |
| 96 | atob | 将 Base64 编码的字符串解码为原始字符串 |
| 97 | btoa | 将原始字符串编码为 Base64 编码的字符串 |
| 98 | BroadcastChannel | 允许同一浏览器的不同标签、iframe 或工作者之间进行简单的通信 |
| 99 | MessageChannel | 允许我们创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据 |
| 100 | MessagePort | 通过 MessageChannel 创建的双向通信端口 |
| 101 | MessageEvent | 表示通过 MessagePort 或其他方式(如 WebSocket)传递的消息事件 |
| 102 | Blob | 表示不可变的原始数据的类似文件对象 |
| 103 | File | 表示文件的构造函数,继承自 Blob |
| 104 | Performance | 提供了获取与当前页面相关的性能相关事件的方法 |
| 105 | PerformanceEntry | 表示单个性能度量的对象 |
| 106 | PerformanceMark | 用于创建自定义的性能标记 |
| 107 | PerformanceMeasure | 用于测量两个标记之间的时间 |
| 108 | PerformanceObserver | 用于异步观察性能度量事件 |
| 109 | PerformanceObserverEntryList | 包含一组 PerformanceEntry 对象 |
| 110 | PerformanceResourceTiming | 提供了网络请求的详细时间信息 |
| 111 | performance | 提供了获取性能相关数据的方法 |
| 112 | SharedArrayBuffer | 允许在 worker 之间共享内存 |
| 113 | Atomics | 提供了一组静态方法用于原子操作 SharedArrayBuffers |
| 114 | WebAssembly | 一种新的代码可以在现代 web 浏览器中运行的低级语言,用于性能关键任务 |
| 115 | fetch | 提供了一种简单、合理的方式来跨网络异步获取资源 |
| 116 | FormData | 用于构造一组表单数据,以模拟表单提交的数据 |
| 117 | Headers | 用于创建和管理 HTTP 请求和响应头 |
| 118 | Request | 用于表示资源请求的资源 |
| 119 | Response | 用于表示请求的响应 |
| 120 | crypto | 提供加密功能,包括对密码学的基本操作 |
| 121 | Crypto | crypto 的属性,提供加密功能 |
| 122 | CryptoKey | 表示加密密钥 |
| 123 | SubtleCrypto | 提供了一组低级加密算法的实现 |
| 124 | CustomEvent | 允许创建任意类型的事件,并且可以包含自定义数据 |
| 125 | $jsDebugIsRegistered | 特殊的标识符或方法,通常不是标准 API 的一部分,可能是特定环境或工具的扩展 |
__filename 与 __dirname
__filename 表示当前正在执行的脚本文件的绝对路径。
console.log(__filename)
//输出:d:\Temp\Nodejs\demo01\demo03.js
//只在 CJS 模块下存在,如果是 ESM 将会出现以下的报错信息。
//ReferenceError: __filename is not defined in ES module scope
__dirname 表示当前执行脚本所在目录的绝对路径。
console.log(__dirname)
//输出:d:\Temp\Nodejs\demo01
//只在 CJS 模块下存在,如果是 ESM 将会出现以下的报错信息。
//ReferenceError: __dirname is not defined in ES module scope
ESM 中如果获取这 2 个变量与 require 方法需要使用如下方式:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
//import { createRequire } from 'module'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log('__filename', __filename)
console.log('__dirname', __dirname)
//const require = createRequire(import.meta.url)
//console.log('name', require('./package.json').name)
Promise
在Node.js中,Promise 是处理异步操作的一种方式。Promise 是一个代表了异步操作最终完成或失败的对象。它允许你为异步操作的成功结果或失败原因分别绑定相应的处理方法。这样可以让异步代码更加清晰,避免了所谓的“回调地狱”。
创建Promise
一个Promise对象是通过Promise构造函数创建的,它接收一个函数作为参数,该函数有两个参数:resolve 和 reject。这两个参数也是函数,分别用于将promise对象的状态从“等待”(pending)转变为“已完成”(fulfilled)或“已失败”(rejected)。
const myPromise = new Promise((resolve, reject) => {
// 异步操作
const condition = true; // 假设这是异步操作的结果
if (condition) {
resolve('Promise is resolved successfully.');
} else {
reject('Promise is rejected.');
}
});
使用Promise
一旦Promise对象被创建,你可以通过.then()方法为Promise成功和失败的情况分别绑定处理函数。.then()方法接受两个参数,第一个参数是当Promise状态变为fulfilled时调用的函数,第二个参数(可选)是当Promise状态变为rejected时调用的函数。
myPromise.then(
(value) => { console.log(value); }, // 成功时执行
(error) => { console.log(error); } // 失败时执行
);
链式调用
.then()方法返回一个新的Promise,这使得Promise可以链式调用。
myPromise
.then((value) => {
console.log(value);
return 'Doing more work';
})
.then((newValue) => {
console.log(newValue); // "Doing more work"
})
.catch((error) => {
console.log('Caught an error:', error);
});
错误处理
.catch()方法用于Promise被拒绝(reject)的情况,它是.then(null, rejection)的别名,用于捕获Promise链中的错误。
myPromise
.then((value) => {
throw 'Oh no!';
})
.catch((error) => {
console.error(error); // "Oh no!"
});
async/await
在Node.js中,async/await是处理异步操作的另一种更加优雅的方式。async函数使得你可以以同步的方式写异步代码。
async function myAsyncFunction() {
try {
const resolvedValue = await myPromise;
console.log(resolvedValue);
} catch (error) {
console.error(error);
}
}
在这个例子中,await关键字使得JavaScript运行时等待myPromise解决,然后将解决的值存储在resolvedValue变量中,或者在Promise被拒绝时捕获错误。
JavaScript 标准错误类型
JavaScript 中定义了一些错误类型,如下。
Error
基本错误对象,其他错误类型的基类。
SyntaxError
当尝试解析不合法的 JavaScript 代码时抛出。这种例子很常见
function testFunc() {
console.log("Hello, world!"
}
EvalError
此错误类型已经不再使用,保留它是为了兼容性。在早期的 JavaScript 版本中,当 eval() 函数的使用有错误时,会抛出 EvalError。然而,在最新的 ECMAScript 规范中,EvalError 已经不再用于这个目的了,但它仍然存在于语言中,主要是为了保持向后兼容。
TypeError
TypeError 是 JavaScript 中的一种错误类型,它表示在运行时尝试执行对于给定数据类型不合法的操作。这种错误通常发生在对值进行操作时,该值的类型并不符合操作所期望的类型。下面是一些 TypeError 的示例:
示例 1: 调用非函数
尝试调用一个不是函数的值会导致 TypeError。
let someValue = "Hello, world!";
try {
someValue(); // 尝试调用一个字符串,这是不合法的操作
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e.message); // something like "someValue is not a function"
}
在这个例子中,someValue 是一个字符串,而不是一个函数。尝试像函数一样调用它(someValue())会抛出 TypeError。
示例 2: 访问 null 或 undefined 的属性
尝试访问 null 或 undefined 的属性或方法会导致 TypeError。
let obj = null;
try {
console.log(obj.property); // 尝试访问 null 的属性
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e.message); // something like "Cannot read properties of null (reading 'property')"
}
在这个例子中,obj 是 null,尝试访问 obj.property 会抛出 TypeError,因为 null 和 undefined 类型的值没有属性。
示例 3: 自定义错误,传递错误类型的参数
向期望特定类型参数的函数传递错误类型的参数,也会导致 TypeError。
function addNumbers(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('addNumbers expects two numbers as arguments');
}
return a + b;
}
try {
addNumbers(5, "not a number"); // 第二个参数不是数字
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e.message); // "addNumbers expects two numbers as arguments"
}
在这个例子中,addNumbers 函数期望接收两个数字作为参数。如果传递的参数类型不正确,函数内部会抛出 TypeError。
TypeError 表示尝试执行对于给定数据类型不合法的操作。这包括但不限于调用非函数、访问 null 或 undefined 的属性,以及向函数传递错误类型的参数。理解和正确处理 TypeError 对于编写健壮的 JavaScript 代码非常重要。
URIError
URIError 是 JavaScript 中的一个错误对象类型,它表示在处理统一资源标识符(URI)时发生了错误。这种错误通常出现在使用 encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent() 等与 URI 编码和解码相关的函数时,如果这些函数的参数不是有效的 URI 或 URI 组件,则可能抛出 URIError。
示例 1: 错误的 URI 解码
try {
// 假设这是一个错误的 URI 编码字符串,实际上并不是真正的 URI 编码
decodeURI('%');
} catch (e) {
console.log(e instanceof URIError); // true
console.log(e.message); // "URI malformed"
}
在这个例子中,decodeURI('%') 尝试解码一个不完整的百分比编码(% 后面应该跟随两位十六进制数字)。因为 % 后面没有跟随有效的编码,所以 JavaScript 引擎抛出了 URIError。
示例 2: 错误的 URI 组件解码
try {
// 同样的错误,但这次是在解码 URI 组件
decodeURIComponent('%E4');
} catch (e) {
console.log(e instanceof URIError); // true
console.log(e.message); // "URI malformed"
}
在这个例子中,decodeURIComponent('%E4') 尝试解码一个不完整的百分比编码。%E4 是一个开始于有效的 UTF-8 编码的序列,但它不完整,因此抛出了 URIError。
示例 3: 错误的 URI 编码
虽然 encodeURI() 和 encodeURIComponent() 函数较少直接抛出 URIError(因为大多数字符串都可以被编码),但理论上,如果这些函数以某种方式被提供了无法处理的参数,也可能导致 URIError。实践中,这种情况极为罕见,因为 URI 编码函数设计得足够健壮,能够编码几乎所有的字符序列。
URIError 主要与 URI 的编码和解码操作有关。当传递给 URI 处理函数的参数不是有效的 URI 或 URI 组件时,就可能抛出 URIError。理解何时以及如何正确使用 URI 编码和解码函数对于避免这类错误至关重要。
RangeError
RangeError 是 JavaScript 中的一个错误对象类型,它表示当一个值不在其允许的范围或者集合内时发生的错误。这种错误通常出现在尝试传递一个数字但是该数字超出了预期范围,或者在使用某些函数和构造函数时提供了不合理的参数值。下面是一些导致 RangeError 出现的常见情况:
示例 1: 数组长度为负
在 JavaScript 中,尝试设置数组的长度为一个负数会抛出 RangeError,因为数组长度必须是一个非负整数。
try {
let arr = [];
arr.length = -1; // 尝试将数组长度设置为负数
} catch (e) {
console.log(e instanceof RangeError); // true
console.log(e.message); // "Invalid array length"
}
示例 2: 数字精度问题
当使用 Number 对象的方法,如 toFixed(),toPrecision() 等,传递的参数超出了允许的范围时,会抛出 RangeError。例如,toFixed() 方法的参数必须在0到100之间(包括0和100)。
try {
let num = 10;
console.log(num.toFixed(101)); // 参数超出了允许的范围
} catch (e) {
console.log(e instanceof RangeError); // true
console.log(e.message); // "toFixed() digits argument must be between 0 and 100"
}
示例 3: 递归层数过深
在某些 JavaScript 引擎中,如果递归调用的层数过深,超出了引擎的最大调用栈大小,虽然这通常会导致 RangeError,但错误信息可能指明是 "Maximum call stack size exceeded"。
function deepRecursion() {
try {
deepRecursion(); // 无限递归调用
} catch (e) {
console.log(e instanceof RangeError); // true
console.log(e.message); // "Maximum call stack size exceeded" 或类似信息
}
}
deepRecursion();
示例 4: 无效的日期
尝试创建一个日期对象,但提供的参数超出了日期的有效范围,也会导致 RangeError。
try {
new Date(2023, 0, 32); // 月份的天数超出了范围
} catch (e) {
console.log(e instanceof RangeError); // 可能为 true,取决于具体实现
console.log(e.message); // 相关错误信息
}
请注意,最后一个示例中的行为可能依赖于 JavaScript 引擎的具体实现,某些引擎可能会自动调整日期而不是抛出错误。
RangeError 表示当一个值不在其允许的范围或者集合内时发生的错误。这通常涉及到数字范围、数组长度、数字格式化参数、递归深度,以及其他需要在特定范围内的参数或值。了解这些情况有助于预防和处理 RangeError。
AggregateError
用于表示一组错误。这种错误类型特别适用于那些可能会在多个点失败的操作,允许你将所有的错误收集起来,而不是在遇到第一个错误时就停止。这在进行并行操作或处理多个异步任务时特别有用,因为这些场景中可能会同时发生多个错误。
自定义 AggregateError
const error1 = new Error("错误1");
const error2 = new Error("错误2");
const aggregateError = new AggregateError([error1, error2], "两个错误发生了");
使用场景
AggregateError 在处理多个异步操作时特别有用。例如,当你使用 Promise.all 或 Promise.allSettled 方法等待多个异步操作完成时,如果其中多个操作失败,你可能希望将所有的错误信息收集起来,而不是只关注第一个错误。这时,AggregateError 就可以派上用场。
Promise.all([
Promise.reject(new Error("失败1")),
Promise.reject(new Error("失败2")),
]).catch(e => {
if (e instanceof AggregateError) {
console.log("发生了多个错误:");
for (const error of e.errors) {
console.log(error.message);
}
} else {
console.log(e.message);
}
});
在这个例子中,我们尝试使用 Promise.all 来处理两个会失败的异步操作。当这两个操作都失败时,catch 块会捕获到一个 AggregateError,我们可以遍历它的 errors 属性来访问所有的错误信息。
总的来说,AggregateError 提供了一种优雅的方式来处理和表示多个错误,使得错误处理更加灵活和全面。
ReferenceError
ReferenceError 是 JavaScript 中的一个错误对象类型,它表示当尝试引用一个不存在的变量时发生的错误。这种错误通常出现在尝试访问未定义的变量、函数或者是尝试访问一个未被声明的变量的属性时。
示例 1: 访问未定义的变量
try {
console.log(x); // x 没有被声明
} catch (e) {
console.log(e instanceof ReferenceError); // true
console.log(e.message); // "x is not defined"
}
在这个例子中,变量 x 没有被声明,尝试访问它会抛出 ReferenceError。
示例 2: 访问未声明的对象的属性
try {
console.log(obj.someProperty); // obj 没有被声明
} catch (e) {
console.log(e instanceof ReferenceError); // true
console.log(e.message); // "obj is not defined"
}
尽管通常尝试访问一个对象的未定义属性只会返回 undefined 而不是抛出错误,但如果这个对象本身未被声明,那么访问它的任何属性都会导致 ReferenceError。
示例 3: 使用 let 和 const 声明的变量在声明之前访问
try {
console.log(a); // a 使用 let 声明,但在声明前访问
let a = 5;
} catch (e) {
console.log(e instanceof ReferenceError); // true
console.log(e.message); // "Cannot access 'a' before initialization"
}
使用 let 和 const 声明的变量存在暂时性死区(Temporal Dead Zone, TDZ),意味着在声明之前的代码块中不能访问它们。尝试这样做会抛出 ReferenceError。
示例 4: 错误的 this 上下文
在严格模式下,this 的值可能未被定义,如果尝试访问其属性或方法可能会抛出 ReferenceError。然而,这种情况更多的是依赖于代码的上下文和编写方式,可能不一定会直接抛出 ReferenceError,但是是一个值得注意的相关情况。
ReferenceError 表示尝试引用一个不存在的变量时发生的错误,这包括但不限于访问未声明的变量、在声明之前访问使用 let 或 const 声明的变量,或者尝试访问一个未被声明的对象的属性。理解和避免这类错误对于编写健壮的 JavaScript 代码非常重要。
Node.js 错误类型
SystemError
在 Node.js 中,SystemError 是一种特定类型的错误,它表示由底层操作系统的系统调用失败引起的错误。这些错误通常与硬件操作、网络通信、文件系统操作等有关,而不是 JavaScript 代码的语法或运行时错误。SystemError 可以提供关于操作系统层面发生什么问题的详细信息,包括错误码、错误类型、相关的系统调用等。
SystemError 错误通常在以下几种情况下出现:
-
文件系统操作失败 当 Node.js 通过 fs 模块执行文件系统操作(如读取、写入、打开文件等)时,如果遇到底层文件系统错误,如权限不足、文件不存在、磁盘空间不足等,就会抛出 SystemError。
-
网络操作失败 在进行网络操作,如使用 http、https、net 等模块创建服务器或客户端、发送请求或数据时,如果底层网络调用失败(例如,端口已被占用、网络不可达、连接超时等),也会抛出 SystemError。
-
子进程操作失败 当使用 child_process 模块创建或管理子进程时,如果相关的系统调用失败(例如,命令不存在、没有执行权限等),会抛出 SystemError。
-
DNS 解析错误 使用 dns 模块进行域名解析时,如果底层的 DNS 查询失败(如域名不存在、DNS 服务器无响应等),也会抛出 SystemError。
属性
一个 SystemError 可能包含如下属性:
- code:一个标识特定错误的字符串错误码,如 EACCES、ENOENT。
- syscall:触发错误的系统调用名称,如 open、read。
- path:与错误相关的文件路径(如果适用)。
- address 和 port:与网络操作相关的错误中的地址和端口(如果适用)。
处理 SystemError
处理 SystemError 通常需要根据错误的具体类型和错误码来决定合适的响应措施,比如重试操作、提示用户、记录日志等。
const fs = require('fs');
fs.readFile('/path/to/nonexistent/file', (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
console.error('文件不存在');
} else {
console.error('发生了一个错误:', err);
}
} else {
console.log(data);
}
});
在这个例子中,如果尝试读取一个不存在的文件,就会捕获到一个 SystemError,其 code 属性为 ENOENT。通过检查这个错误码,我们可以提供一个更具体的错误消息。
AssertionError
AssertionError 是在 Node.js 中出现的一种错误类型,它表示断言失败。断言是一种在代码中检查某个条件是否为真的方式,如果条件不为真,则抛出错误。这通常用于开发过程中的测试,以确保代码的行为符合预期。当使用断言库或 Node.js 自带的 assert 模块进行条件检查时,如果条件评估结果为 false,则会抛出 AssertionError。
示例
使用 Node.js 的 assert 模块可以很容易地看到 AssertionError 是如何工作的:
const assert = require('assert');
// 假设我们有一个函数,它应该返回 true
function testFunction() {
return false; // 故意让这个函数返回 false 来模拟失败的情况
}
// 使用 assert 来检查 testFunction() 的返回值
try {
assert.strictEqual(testFunction(), true, 'testFunction() 应该返回 true');
} catch (err) {
console.error(err);
}
在这个例子中,testFunction 函数应该返回 true,但是它返回了 false。使用 assert.strictEqual 方法来检查这个函数的返回值是否严格等于(===)true。因为实际返回值是 false,所以条件检查失败,assert.strictEqual 抛出了一个 AssertionError,错误信息包含了提供的消息 'testFunction() 应该返回 true'。
属性
AssertionError 对象包含几个有用的属性,帮助开发者理解为什么断言失败:
- message:错误消息,描述了断言失败的原因。
- actual:实际的结果值。
- expected:断言期望的值。
- operator:用于比较 actual 和 expected 的操作符,如 ===、!== 等。
使用场景
AssertionError 主要用于测试和验证代码逻辑。在单元测试或集成测试中,断言用于验证代码的输出或行为是否符合预期。如果一个断言失败,即表示存在一个可能的 bug 或逻辑错误,需要开发者进一步调查和修复。
虽然断言在生产代码中也可以使用,但一般不推荐这样做,因为断言抛出的错误可能会导致应用程序崩溃或者出现不可预期的行为。相反,生产环境中的错误处理通常应该更加细致和健壮,能够优雅地处理错误情况。
其他错误
InternalError
InternalError 在 JavaScript 的某些实现中存在,用于表示 JavaScript 引擎内部错误,例如:当递归调用太深超出调用栈限制时。然而,需要注意的是,InternalError 并不是 ECMAScript 标准的一部分,它的支持可能因不同的 JavaScript 环境而异。在 Node.js 的文档中,InternalError 并不是明确列出的错误类型,它可能更多地与特定引擎的实现(如 SpiderMonkey)相关。 Chrome 浏览器是存在这种类型的。
globalThis
globalThis 本身就是全局对象的一个属性,这意味着它是自引用的。这样设计有几个好处:
-
一致性:globalThis 提供了一个环境无关的方式来访问全局对象。这意味着无论在哪种环境下编写代码,都可以使用 globalThis 来引用全局对象,而不必担心当前环境是什么。
-
便利性:由于 globalThis 是全局对象的一个属性,它可以直接被访问,无需任何特殊的语法或导入。这使得在任何环境中访问全局对象变得非常简单。
-
兼容性和安全性:在引入 globalThis 之前,开发者通常需要使用各种技巧和工作方式来兼容不同环境的全局对象。这些方法可能会导致代码更加复杂,甚至在某些情况下存在安全隐患。globalThis 的引入提供了一个标准和安全的方法来实现这一目的,减少了代码的复杂性和潜在的安全风险。
总之,globalThis 作为全局对象的属性,提供了一个简单、统一且安全的方式来访问全局对象,使得开发者可以更容易地编写兼容多种JavaScript环境的代码。
ArrayBuffer
ArrayBuffer 是 JavaScript 中的一个全局构造函数,它用来表示一段固定长度的原始二进制数据缓冲区。你不能直接操作 ArrayBuffer 的内容;相反,你需要通过类型数组对象或 DataView 对象来操作缓冲区内的数据。
创建 ArrayBuffer
要创建一个 ArrayBuffer,你可以使用其构造函数并传入缓冲区的长度(以字节为单位)作为参数:
let buffer = new ArrayBuffer(16); // 创建一个长度为16字节的ArrayBuffer
通过 Typed Array 操作 ArrayBuffer
类型数组(如 Uint8Array, Int16Array, Float32Array 等)提供了一种读写 ArrayBuffer 的方法。类型数组会将缓冲区视为特定类型的数组,让你可以按照那个类型来读写数据。
let buffer = new ArrayBuffer(16); // 创建一个长度为16字节的ArrayBuffer
// 创建一个视图来操作ArrayBuffer中的数据
let int32View = new Int32Array(buffer);
// 通过类型数组来读写数据
int32View[0] = 42;
console.log(int32View[0]); // 输出:42
通过 DataView 操作 ArrayBuffer
DataView 提供了一个更为灵活的方式来读写 ArrayBuffer,允许你以指定的格式来读写缓冲区内任意位置的数据,而不受平台字节序的影响。
let buffer = new ArrayBuffer(16);
let view = new DataView(buffer);
// 使用DataView来写入数据
view.setInt8(0, 127); // 在第一个字节写入8位整数,占用一个字节
view.setFloat32(4, 3.14); // 从第5个字节开始写入一个32位浮点数,占用4个字节
// 使用DataView来读取数据
console.log(view.getInt8(0)); // 输出:127
console.log(view.getFloat32(4)); // 输出:3.14
上述代码说明:setFloat32 插入的数值写入一个32位浮点数,占用4个字节,所以实际影响到了从第4个字节开始的 4/5/6/7 字节,如果修改了 5/6/7 会导致从第四字节获取的值变化
let buffer = new ArrayBuffer(16);
let view = new DataView(buffer);
// 使用DataView来写入数据
view.setFloat32(4, 3.14); // 从第5个字节开始写入一个32位浮点数,占用4个字节
view.setInt8(5, 127);
// 使用DataView来读取数据
console.log(view.getFloat32(4)); // 输出:3.999375104904175 并不是 3.14
ArrayBuffer 的用途
ArrayBuffer 通常用于处理二进制数据,例如:
- 与 Web APIs(如 File API, Fetch API)交互时处理二进制文件(例如图片或音频文件)。
- 通过 WebSocket 发送或接收二进制数据。
- 读取或操作来自网络或其他源的大型二进制数据集。
注意事项
- ArrayBuffer 一旦创建,其大小就不能改变。
- 你不能直接读写 ArrayBuffer 的内容。必须通过视图(类型数组或 DataView)来操作数据。
- 操作 ArrayBuffer 时要注意字节序(大端或小端)问题,特别是在进行网络通信或文件操作时。 通过上述方法,你可以有效地使用 ArrayBuffer 来处理 JavaScript 中的二进制数据。
WeakMap
ES 和 Node.js 中,Map用于存储有序键值对,键可以是任意值,而 WeakMap 的键则只能是对象,WeakMap 的键是“弱引用”的概念。
强引用
在 JavaScript 中,当你创建一个对象并将其赋值给一个变量时,这个变量就持有了对该对象的一个“强引用”。只要这个强引用存在,垃圾回收器就不会回收这个对象,即使它在程序的其他部分已经不再被需要了。这确保了只要你可以通过某个变量访问到这个对象,它就会一直存在。
let myObject = { name: "JavaScript" };
// myObject 变量持有对 { name: "JavaScript" } 对象的强引用
弱引用
相对地,“弱引用”则不会阻止垃圾回收器回收其引用的对象。如果一个对象只被弱引用所引用,那么垃圾回收器可以决定回收这个对象,释放内存空间。在 WeakMap 中,所有的键都是以弱引用的形式存储的。这意味着如果没有其他强引用指向这个键对象,它可能会被垃圾回收,而且当这个键对象被回收后,与之关联的键值对也会从 WeakMap 中自动移除。
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "some value");
// 此时,obj 是一个强引用
obj = null;
// 现在,没有强引用指向之前创建的对象了
// weakMap 中的 {obj: "some value"} 键值对可以被自动清除
在这个例子中,当我们将 obj 设置为 null 后,原来的对象(之前作为键存储在 WeakMap 中)就只剩下 WeakMap 中的弱引用了。由于这是一个弱引用,垃圾回收器可以决定何时回收这个对象。一旦这个对象被回收,它在 WeakMap 中的键值对也会消失。
let weakMap = new WeakMap();
function demo01() {
let obj = {};
weakMap.set(obj, "some value");
// 此时,obj 是一个强引用
// 做一些业务操作
}
demo01();
// obj 的生命周期在 demo01 内部,出栈后,没有强引用指向之前创建的对象了
// weakMap 中的 {obj: "some value"} 键值对可以被自动清除
在这个例子中,当 demo01 结束后,原来的对象(之前作为键存储在 WeakMap 中)就只剩下 WeakMap 中的弱引用了。由于这是一个弱引用,垃圾回收器可以决定何时回收这个对象。一旦这个对象被回收,它在 WeakMap 中的键值对也会消失。
WeakMap 实际场景
假设你正在开发一个网页应用,其中包含大量的用户交互元素(例如,按钮)。每个按钮点击时,你需要跟踪一些与之相关的元数据,比如点击次数。直接在 DOM 元素上设置属性或者使用全局对象来跟踪这些信息可能会导致内存泄漏,尤其是在元素被移除但全局引用仍然保留的情况下。但是如果使用 WeakMap 则不会有这样的问题。
// 创建一个 WeakMap 来存储每个按钮的点击次数
const clickCounts = new WeakMap();
// 为按钮添加点击事件处理函数
document.querySelectorAll('.clickable-button').forEach(button => {
// 初始化点击次数
clickCounts.set(button, 0);
// 监听点击事件
button.addEventListener('click', () => {
// 更新点击次数
const count = clickCounts.get(button) + 1;
clickCounts.set(button, count);
// 做一些其他的事情,比如更新 UI
console.log(`Button clicked ${count} times`);
});
});
// 假设某个时刻,某个按钮从 DOM 中移除了
// 由于使用了 WeakMap,一旦按钮元素被垃圾回收,相关的点击次数记录也会自动被清理
在这个例子中,clickCounts WeakMap 用于跟踪每个按钮的点击次数。由于 WeakMap 的键是弱引用,这意味着一旦某个按钮元素不再在 DOM 中(即被移除并且没有其他引用指向它),它就可以被垃圾回收,WeakMap 中对应的键值对也会自动被清除。这样,即使应用动态地添加和移除按钮,也不会导致内存泄漏。
这个例子展示了 WeakMap 在管理与临时或动态对象相关联的数据时的优势。它自动处理了内存管理的复杂性,使得开发者可以专注于业务逻辑,而不是内存或资源管理问题。此外,使用 WeakMap 还可以避免在 DOM 元素上直接附加数据,保持了结构的清洁和数据的隐私。
总结
WeakMap 通过弱引用提供了一种机制,使得开发者不需要担心因为持有对象引用而阻止垃圾回收,这对于管理内部缓存、跟踪对象的状态等场景特别有用,而且可以帮助防止内存泄露。
Proxy
在JavaScript中,Proxy 是一种创建对象的包装器(wrapper),它允许你拦截并自定义对象的基本操作,比如属性读取、赋值、枚举、函数调用等。通过使用Proxy,你可以在代码的底层操作上添加自定义行为,这在某些场景下非常有用,比如调试、数据绑定、访问控制、虚拟化等。这和设计模式中的代理模式功能表现一致。
创建 Proxy
Proxy 对象通过 new Proxy(target, handler) 构造函数创建,其中:
target 是要包装的目标对象,可以是任何类型的对象,包括数组、函数、甚至另一个代理。 handler 是一个包含“捕获器”(trap)的对象。捕获器是定义了拦截操作的方法。当操作被拦截时,这些方法就会被调用。
基本示例
let target = {};
let p = new Proxy(target, {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37; // 如果属性不存在,返回37
}
});
console.log(p.a); // 由于'a'属性不存在,因此返回37
常见的捕获器
- get(target, propKey, receiver):拦截对象属性的读取。
- set(target, propKey, value, receiver):拦截对象属性的设置。
- has(target, propKey):拦截 propKey in proxy (for in)的操作,返回一个布尔值。
- deleteProperty(target, propKey):拦截 delete proxy[propKey] 的操作,返回一个布尔值。
- apply(target, thisArg, argumentsList):拦截函数的调用。
- construct(target, argumentsList, newTarget):拦截 new 操作符。
示例
示例1:
let logger = {
get(target, prop) {
console.log(`读取 ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`设置 ${prop} 为 ${value}`);
target[prop] = value;
return true;
}
};
let obj = new Proxy({}, logger);
obj.a = 1; // 输出: 设置 a 为 1
console.log(obj.a); // 输出: 读取 a
// 1
示例2:
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Age is not an integer');
}
if (value < 0) {
throw new RangeError('Age cannot be negative');
}
}
obj[prop] = value;
return true; // 表示成功
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age); // 100
// person.age = 'young'; // 抛出TypeError
// person.age = -1; // 抛出RangeError
示例3:
function Person(name) {
this.name = name;
}
const PersonProxy = new Proxy(Person, {
construct(target, args) {
if (typeof args[0] !== 'string') {
throw new TypeError('Person name must be a string');
}
// 使用 new 运算符和 Reflect.construct 方法来调用原始构造函数
return Reflect.construct(target, args);
}
});
const john = new PersonProxy('John Doe');
console.log(john.name); // 输出: John Doe
// 下面的代码会抛出 TypeError,因为名字不是一个字符串
// const invalidPerson = new PersonProxy(123);
Reflect
Reflect 是一个内置的对象,它提供了拦截 JavaScript 操作的方法。这些方法与 Proxy handlers 的方法相同。Reflect 并不是一个函数对象,因此它不可被构造(即不能使用 new Reflect())或调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。
Reflect 对象的设计目的之一是使某些在语言内部的操作变得可被调用。在 ES6 之前,这些操作只能通过 JavaScript 语言构造来完成,比如 new 操作符可以通过 Reflect.construct() 来实现,delete 操作符可以通过 Reflect.deleteProperty() 来实现。
主要功能和方法
Reflect 提供了一系列静态方法,用于执行与语言内部相对应的操作。这些方法包括:
- Reflect.apply(target, thisArgument, argumentsList):相当于调用函数,等同于 Function.prototype.apply.call(target, thisArgument, argumentsList)。
- Reflect.construct(target, argumentsList, newTarget):相当于使用 new 操作符调用构造函数,创建一个新对象。
- Reflect.get(target, propertyKey, receiver):获取对象属性的值,等同于 target[propertyKey]。
- Reflect.set(target, propertyKey, value, receiver):设置对象属性的值,等同于 target[propertyKey] = value。
- Reflect.defineProperty(target, propertyKey, attributes):和 Object.defineProperty() 类似,用于定义或修改对象的属性。
- Reflect.deleteProperty(target, propertyKey):删除对象的属性,等同于 delete target[propertyKey]。
- Reflect.has(target, propertyKey):判断对象是否有该属性,等同于 propertyKey in target。
- Reflect.ownKeys(target):返回一个包含所有自身属性键的数组,等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
- Reflect.isExtensible(target):判断对象是否可扩展,等同于 Object.isExtensible(target)。
- Reflect.preventExtensions(target):防止对象扩展,等同于 Object.preventExtensions(target)。
- Reflect.getOwnPropertyDescriptor(target, propertyKey):获取对象指定属性的属性描述符,等同于 Object.getOwnPropertyDescriptor(target, propertyKey)。
- Reflect.getPrototypeOf(target):获取对象的原型,等同于 Object.getPrototypeOf(target)。
- Reflect.setPrototypeOf(target, prototype):设置对象的原型,等同于 Object.setPrototypeOf(target, prototype)。
使用示例
使用 Reflect.get() 获取对象属性的值:
const obj = { x: 1, y: 2 };
console.log(Reflect.get(obj, 'x')); // 输出: 1
使用 Reflect.set() 设置对象属性的值:
const obj = { x: 1 };
Reflect.set(obj, 'x', 2);
console.log(obj.x); // 输出: 2
使用 Reflect.construct() 以动态参数创建新对象:
function Greeting(name) {
this.name = name;
}
const instance = Reflect.construct(Greeting, ['John']);
console.log(instance.name); // 输出: John
Reflect 与 Proxy handlers 一起使用,可以提供一种方法,使得自定义行为与默认行为保持一致,同时也简化了某些操作的实现。
为什么使用
-
一致性与可读性 配合代理(Proxy)和反射(Reflection),代码操作一致性,方法结构相同或相似
-
显式的 receiver 参数 比如
Reflect.get(target, propertyKey, receiver),receiver 是就是当前的 this,如果 target 的 get 内部有使用 this,获取的就是 receiver -
与 Proxy 对象配合使用
const obj = {
a: 1
};
const proxy = new Proxy(obj, {
get(target, property, receiver) {
console.log(`Property ${property} has been accessed`);
return Reflect.get(...arguments); // 使用 Reflect.get 来转发操作
}
});
console.log(proxy.a); // 输出:Property a has been accessed
// 然后输出:1
在这个例子中,使用 Reflect.get 使得在 Proxy 的 get 陷阱内转发操作变得简单而且一致。这展示了 Reflect API 在特定场景下提供的额外灵活性和一致性,而不是在错误处理和返回值上与直接属性访问不一致。感谢你指出这个问题,希望这次解释更清楚了。
process
在 Node.js 中,process 是一个全局对象,提供了一系列属性和方法,用于与当前运行的 Node.js 进程进行交互。它允许开发者获取环境信息、管理进程状态、与控制台进行交互、处理命令行参数等。以下是一些 process 对象的重要属性和方法的概览:
属性
- process.argv: 一个数组,包含启动 Node.js 进程时传递的命令行参数。
- process.env: 一个对象,包含用户环境的键值对。
- process.execPath: 启动当前 Node.js 进程的可执行文件的绝对路径。
- process.pid: 当前进程的进程号。
- process.platform: 运行 Node.js 进程的操作系统平台。
- process.version: Node.js 的版本。
- process.versions: 一个包含 Node.js 和其依赖的详细版本信息的对象。
方法
- process.cwd(): 返回 Node.js 进程的当前工作目录。
- process.exit([code]): 使用指定的 code 结束进程。如果省略,使用成功代码 0。
- process.kill(pid[, signal]): 发送信号到指定进程ID的进程。常用于结束进程。
- process.nextTick(callback[, ...args]): 将 callback 添加到"next tick 队列"。一旦当前事件轮询队列的任务全部完成,在下一个事件轮询迭代中(即下一个tick),队列中的所有回调都会被依次调用。
- process.stdout: 一个指向标准输出流(stdout)的可写流(Writable Stream)。
- process.stderr: 一个指向标准错误流(stderr)的可写流(Writable Stream)。
- process.stdin: 一个指向标准输入流(stdin)的可读流(Readable Stream)。
事件
- exit: 当 Node.js 进程准备退出时触发此事件。
- uncaughtException: 当一个未捕获的异常冒泡回到事件循环时,触发此事件。
process 对象是 Node.js 应用程序的核心部分,允许开发者与操作系统进行交互以及控制应用程序的行为。了解和利用 process 对象,对于构建高效且可靠的 Node.js 应用至关重要。
Buffer
Buffer 类是一个全局可用的类,用于直接操作内存中的二进制数据。它是一个表示固定长度的字节序列的类。在 Node.js 的早期版本中,处理二进制数据非常困难,但 Buffer 类提供了一个与二进制数据交互的接口,使得读取和操作字节变得简单。
Buffer 的创建
Buffer 类的实例可以通过多种方式创建:
1、使用 Buffer.alloc(size) 方法:创建一个指定大小的新建的 Buffer 实例,其内容被初始化为零。
const buf = Buffer.alloc(10); // 创建一个长度为 10 个字节的 Buffer,初始化为 0
2、使用 Buffer.from(array) 方法:通过一个字节数组来创建 Buffer 实例。
const buf = Buffer.from([0x62, 0x75, 0x66]); // 创建一个包含 ASCII 码的 Buffer
3、使用 Buffer.from(string[, encoding]) 方法:通过一个字符串来创建 Buffer 实例,可以指定编码(默认是 'utf8')。
const buf = Buffer.from('hello', 'utf8');
4、使用 Buffer.from(buffer) 方法:通过复制另一个 Buffer 实例来创建新的 Buffer 实例。
const buf1 = Buffer.from('hello');
const buf2 = Buffer.from(buf1);
Buffer 的操作
Buffer 实例提供了多种方法来读取和操作内存中的数据:
- 写入数据:使用 buf.write(string[, offset[, length]][, encoding]) 方法写入数据。
- 读取数据:可以使用 buf.toString([encoding[, start[, end]]]) 方法将缓冲区的数据转换成字符串。
- 访问特定字节:可以通过索引(如 buf[2])直接访问和修改 Buffer 中特定的字节。
- 复制:buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]]) 方法可以将数据从一个 Buffer 复制到另一个 Buffer。
- 切片:buf.slice([start[, end]]) 方法创建一个指向相同内存,但在不同位置或范围的新 Buffer。
- 长度:Buffer 实例的 length 属性表示 Buffer 的大小,单位是字节。
注意事项
- Buffer 实例操作的是内存中的原始数据。因此,需要特别注意数据的编码和解码,以及可能的安全性问题。
- 由于 Buffer 创建的是固定大小的内存块,一旦创建,其大小就不能改变。
- 在 Node.js 中处理大量的二进制数据时,合理使用 Buffer 可以提高应用的性能。
Buffer 在处理如文件系统操作、网络通信等场景中非常有用,是 Node.js 应用中不可或缺的一部分。