JS/TS
错误处理方式
Section titled “错误处理方式”try/catch | window.onerror | window.addEventListener('error') | window.addEventListener('unhandledrejection') | |
|---|---|---|---|---|
| 同步错误 | Y | Y | Y | |
| 异步回调错误 | Y | Y | ||
| 未处理的 Promise rejection | Y | |||
| async/await 异步错误 | Y | Y (未 try/catch 的 async/await 异步错误 ) | ||
| 资源加载错误 | Y (事件捕获阶段) | |||
| 语法错误 | Y | Y |
try/catch可以捕获同步错误, async/await 异步错误window.onerror可以捕获同步错误, 异步回调错误, 语法错误; 不能捕获资源加载错误window.addEventListener('error')资源加载失败时, 会触发 error 事件, 对比window.onerror,window.addEventListener('error')在事件捕获阶段可以捕获资源加载错误window.addEventListener('unhandledrejection')可以捕获未处理的 Promise rejection, 和未 try/catch 的 async/await 异步错误
| 阶段 | |
|---|---|
| 词法分析 (Lexical Analysis) | tsx/vue 源代码字符流 => Lexer/Tokenizer 词法分析器 => token 流 |
| 语法分析 (Syntax Analysis) | token 流 => Parser 语法分析器 => AST 抽象语法树 |
| 语义分析 (Semantic Analysis) | AST 抽象语法树 => TypeChecker 等 => 类型检查等 |
| 转换, 优化 (Transformation/Optimization) | AST 抽象语法树 => Transformer/Optimizer => 新 AST, 例如 tsc 擦除类型注解, jsx 转换为 React.createElement |
| 代码生成 (Code Generation) | 新 AST => CodeGenerator => js 代码 |
npm 命令
Section titled “npm 命令”npm config list
npm get registrynpm config set registry https://registry.npmmirror.com
npm config get script-shellnpm config set script-shell "C:/Program Files/Git/bin/bash.exe"Promise
Section titled “Promise”| CN | v | adj | n |
|---|---|---|---|
| 解决 (兑现) | resolve | resolved (fulfilled) | result, value |
| 拒绝 | reject | rejected | reason, error |
| 静态方法 | resolved | rejected |
|---|---|---|
Promise.all() | 全部 resolved, 返回 aggregatedValues 数组 | 第一个 reject |
Promise.allSettled() | 全部 settled (resolved 或 rejected), 返回 ({ status: "fulfilled", value: unknown } | { status: "rejected", reason: unknown })[] 数组 | 始终 resolved, 不存在 rejected |
Promise.any() | 第一个 resolved | 全部 rejected, 抛出 AggregateError (errors) |
Promise.race() | 第一个 settled (resolved 或 rejected) 是 resolved | 第一个 settled (resolved 或 rejected) 是 rejected |
Promise.reject() | — | 返回一个 rejected 的 Promise 对象 |
Promise.resolve() | 返回一个已 resolved 的 Promise 对象 | — |
Promise.try()接受一个回调函数和参数, 将回调函数的返回值或抛出的异常封装为一个 resolved/rejected 的 Promise;Promise.try(func)不等价于Promise.resolve().then(func), 区别是传递给Promise.try(func)的回调函数 func 是同步调用的 (传递给 Promise 构造函数的executor执行器函数也是同步调用的), 传递给Promise.resolve().then(func)的回调函数 func 是异步调用的Promise.withResolvers()返回一个 Promise 对象, 一个用于解决该 Promise 对象的 resolve 函数, 一个用于拒绝该 Promise 对象的 reject 函数
const promise = Promise.try(func, arg1, arg2);const { promise, resolve, reject } = Promise.withResolvers();实现 Promise
Section titled “实现 Promise”Promise
/** * 1.1 "promise" 有 then 方法的对象或函数, 行为符合本规范 * 1.2 "thenable" 有 then 方法的对象或函数 * 1.3 "value" 合法的 JS 值 (包括 undefined、thenable 或 promise) * 1.4 "exception" 使用 throw 语句抛出的值 * 1.5 "reason" 代表 promise 被拒绝的原因 */
/* eslint-disable @typescript-eslint/no-explicit-any */
// 2.1 promise 的三个状态:pending、fulfilled 或 rejectedenum PromiseState { PENDING = "pending", FULFILLED = "fulfilled", REJECTED = "rejected",}
type Resolve<T> = (value: T | PromiseLike<T>) => void;type Reject = (reason?: any) => void;type OnFulfilled<T, TResult> = | ((value: T) => TResult | PromiseLike<TResult>) | undefined | null;type OnRejected<TResult> = | ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null;
class MyPromise<T = any> { // 2.1.1 pending 时 // 2.1.1.1 promise 可以转换为 fulfilled 或 rejected private _state: PromiseState = PromiseState.PENDING;
// 2.1.2 fulfilled 时 // 2.1.2.1 promise 不能转换为其他状态 // 2.1.2.2 必须有一个 value, 且 value 不能改变 // 不能改变即 ===, 值类型是值不可变, 引用类型是引用不可变 private _value: any = undefined;
// 2.1.3 rejected 时 // 2.1.3.1 promise 不能转换为其他状态 // 2.1.3.2 必须有一个 reason, 且 reason 不能改变 // 不能改变即 ===, 值类型是值不可变, 引用类型是引用不可变 private _reason: any = undefined;
// 存储回调函数 private _onFulfilledCallbacks: (() => void)[] = []; private _onRejectedCallbacks: (() => void)[] = [];
constructor(executor: (resolve: Resolve<T>, reject: Reject) => void) { try { executor(this._resolve.bind(this), this._reject.bind(this)); } catch (e) { this._reject(e); } }
/** * The Promise Resolution Procedure * * @param x * @returns */ private _resolve(x: any): void { // 2.1.2 fulfilled 时 // 2.1.2.1 promise 不能转换为其他状态 // 2.1.3 rejected 时 // 2.1.3.1 promise 不能转换为其他状态 if (this._state !== PromiseState.PENDING) return;
// 2.3.1 如果 promise 和 x 指向同一个对象, 则使用 TypeError 作为 reason, reject promise // 如果不 reject promise, 则会导致无限循环 if (x === this) { return this._reject(new TypeError("Chaining cycle detected for promise")); }
// 2.3.2 如果 x 是一个 promise, 则使用 x 的状态 if (x instanceof MyPromise) { // 2.3.2.1 如果 x (一个 promise) 是 pending, 则 promise 必须保持 pending, 直到 x 被 resolve 或 reject if (x._state === PromiseState.PENDING) { x.then( (v: any) => this._resolve(v), (r: any) => this._reject(r), ); // 2.3.2.2 如果 x (一个 promise) 是 fulfilled, 则 promise 也 fulfilled, value 与 x 的 value 相同 } else if (x._state === PromiseState.FULFILLED) { this._fulfill(x._value); } else { // 2.3.2.3 如果 x (一个 promise) 是 rejected, 则 promise 也 rejected, reason 与 x 的 reason 相同 this._reject(x._reason); } return; }
// 2.3.3 如果 x 是一个对象或函数 if (x !== null && (typeof x === "object" || typeof x === "function")) { let then; try { // 2.3.3.1 则令 then = x.then then = x.then; } catch (e) { // 2.3.3.2 如果获取 x.then 导致抛出异常 e // 则使用 e 作为 reason, reject promise return this._reject(e); }
if (typeof then === "function") { // 2.3.3.3 如果 then 是一个函数 // 则使用 x 作为 this 调用 then 方法 // 第 1 个参数是 resolvePromise, 第 2 个参数是 rejectPromise
// 2.3.3.3.3 如果同时调用 resolvePromise 和 rejectPromise // 或者对同一个参数进行多次调用 // 则第一次调用优先, 后续调用都会被忽略 let called = false;
// 2.3.3.3.1 如果调用 resolvePromise 并传递 v 时, 则使用 v 作为 value, resolve promise // 2.3.3.3.4.1 如果已调用 resolvePromise 或 rejectPromise, 则忽略 const resolvePromise = (v: any) => { if (called) return; called = true; this._resolve(v); };
// 2.3.3.3.2 如果调用 rejectPromise 并传递 reason 时, 则使用 r 作为 reason, reject promise // 2.3.3.3.4.1 如果已调用 resolvePromise 或 rejectPromise, 则忽略 const rejectPromise = (r: any) => { if (called) return; called = true; this._reject(r); };
try { then.call(x, resolvePromise, rejectPromise); } catch (e) { // 2.3.3.3.4 如果调用 then 方法时抛出异常 e // 2.3.3.3.4.1 如果已调用 resolvePromise 或 rejectPromise, 则忽略 if (called) return; // 2.3.3.3.4.2 否则使用 e 作为 reason, reject promise this._reject(e); } return; } }
// 2.3.3.4 如果 then 不是一个函数, 则使用 x 作为 value, resolve promise // 2.3.4 如果 x 不是一个对象或函数, 则使用 x 作为 value, resolve promise this._fulfill(x); }
private _fulfill(value: any): void { if (this._state !== PromiseState.PENDING) return; this._state = PromiseState.FULFILLED; this._value = value;
const callbacks = this._onFulfilledCallbacks; this._onFulfilledCallbacks = []; this._onRejectedCallbacks = [];
for (const callback of callbacks) { callback(); } }
private _reject(reason: any): void { if (this._state !== PromiseState.PENDING) return; this._state = PromiseState.REJECTED; this._reason = reason;
const callbacks = this._onRejectedCallbacks; this._onFulfilledCallbacks = []; this._onRejectedCallbacks = [];
for (const callback of callbacks) { callback(); } }
// 2.2 then 方法 // promise 必须提供 then 方法, 以访问其当前或最终的 value 或 reason // promise 的 then 方法接受两个参数 public then<TResult1 = T, TResult2 = never>( // 2.2.1 onFulfilled 和 onRejected 都是可选参数 onFulfilled?: OnFulfilled<T, TResult1>, onRejected?: OnRejected<TResult2>, ): MyPromise<TResult1 | TResult2> { // 2.2.7 then 方法返回一个 promise, 称为 promise2 // then 方法的调用者称为 promise1 return new MyPromise((resolve, reject) => { // 2.2.4 onFulfilled 和 onRejected 可以使用 setTimeout, setImmediate 等宏任务实现, 也可以使用 queueMicrotask, process.nextTick 等微任务实现 const handleFulfilled = () => { queueMicrotask(() => { try { // 2.2.1.1 如果 onFulfilled 不是函数, 则忽略 // 2.2.7.3 如果 onFulfilled 不是函数, 且 promise1 是 fulfilled // 则 then 方法也返回一个 fulfilled 的 promise2, value 与 promise1 的 value 相同 if (typeof onFulfilled !== "function") { resolve(this._value); } else { // 2.2.2 如果 onFulfilled 是函数 // 2.2.2.1 必须在 promise fulfilled 后调用 onFulfilled, 且使用 promise 的 value 作为第一个参数 // 2.2.2.2 在 promise fulfilled 前不能调用 onFulfilled // 2.2.2.3 onFulfilled 只能调用 1 次 // 2.2.5 onFulfilled 和 onRejected 必须作为函数调用(即 this === undefined) const x = onFulfilled.call(undefined, this._value); // 2.2.7.1 如果 onFulfilled 或 onRejected 返回值 x // 则 then 方法使用 resolve(x) 返回一个 fulfilled 的 promise2, value 为 x resolve(x); } } catch (e) { // 2.2.7.2 如果 onFulfilled 或 onRejected 抛出异常 e // 则 then 方法使用 reject(e) 返回一个 rejected 的 promise2, reason 为 e reject(e); } }); };
const handleRejected = () => { queueMicrotask(() => { try { // 2.2.1.2 如果 onRejected 不是函数, 则忽略 // 2.2.7.4 如果 onRejected 不是函数, 且 promise1 是 rejected // 则 then 方法也返回一个 rejected 的 promise2, reason 与 promise1 的 reason 相同 if (typeof onRejected !== "function") { reject(this._reason); } else { // 2.2.3 如果 onRejected 是函数 // 2.2.3.1 必须在 promise rejected 后调用 onRejected, 且使用 promise 的 reason 作为第一个参数 // 2.2.3.2 在 promise rejected 前不能调用 onRejected // 2.2.3.3 onRejected 只能调用 1 次 // 2.2.5 onFulfilled 和 onRejected 必须作为函数调用(即 this === undefined) const x = onRejected.call(undefined, this._reason); resolve(x); } } catch (e) { // 2.2.7.2 如果 onFulfilled 或 onRejected 抛出异常 e // 则 then 方法使用 reject(e) 返回一个 rejected 的 promise2, reason 为 e reject(e); } }); };
//#region if (this._state === PromiseState.FULFILLED) { // 2.2.2.1 必须在 promise fulfilled 后调用 onFulfilled, 且使用 promise 的 value 作为第一个参数 handleFulfilled(); } else if (this._state === PromiseState.REJECTED) { // 2.2.3.1 必须在 promise rejected 后调用 onRejected, 且使用 promise 的 reason 作为第一个参数 handleRejected(); } else { // 2.2.6 一个 promise 的 then 方法可以多次调用 // 2.2.6.1 当 promise fulfilled 时, 所有 onFulfilled 回调 (onFulfilledCallbacks) 按顺序执行 this._onFulfilledCallbacks.push(handleFulfilled); // 2.2.6.2 当 promise rejected 时, 所有 onRejected 回调 (onRejectedCallbacks) 按顺序执行 this._onRejectedCallbacks.push(handleRejected); } //#endregion }); }
public catch<TResult = never>( onRejected?: OnRejected<TResult>, ): MyPromise<T | TResult> { return this.then(undefined, onRejected); }
public finally(onFinally?: (() => void) | undefined | null): MyPromise<T> { return this.then( (value) => { if (typeof onFinally !== "function") return value; return MyPromise.resolve(onFinally()).then(() => value); }, (reason) => { if (typeof onFinally !== "function") throw reason; return MyPromise.resolve(onFinally()).then(() => { throw reason; }); }, ); }
static resolve(value: any): MyPromise<any> { if (value instanceof MyPromise && value.constructor === MyPromise) { return value; } return new MyPromise((resolve) => resolve(value)); }
static reject(reason: any): MyPromise<never> { return new MyPromise((_, reject) => reject(reason)); }
static all(promises: Iterable<any>): MyPromise<any[]> { return new MyPromise((resolve, reject) => { const pArray = Array.isArray(promises) ? promises : Array.from(promises); const n = pArray.length; if (n === 0) { resolve([]); return; } const results: any[] = new Array(n); let completed = 0; for (let i = 0; i < n; i++) { MyPromise.resolve(pArray[i]).then( (value: any) => { results[i] = value; completed++; if (completed === n) { resolve(results); } }, (reason: any) => { reject(reason); }, ); } }); }
static allSettled(promises: Iterable<any>): MyPromise<any[]> { return new MyPromise((resolve) => { const pArray = Array.isArray(promises) ? promises : Array.from(promises); const n = pArray.length; const results: any[] = new Array(n); if (n === 0) { resolve([]); return; }
let completed = 0; for (let i = 0; i < n; i++) { MyPromise.resolve(pArray[i]).then( (value: any) => { results[i] = { status: "fulfilled", value }; completed++; if (completed === n) { resolve(results); } }, (reason: any) => { results[i] = { status: "rejected", reason }; completed++; if (completed === n) { resolve(results); } }, ); } }); }
static any(promises: Iterable<any>): MyPromise<any> { return new MyPromise((resolve, reject) => { const pArray = Array.isArray(promises) ? promises : Array.from(promises); const n = pArray.length; const errors: any[] = new Array(n); if (n === 0) { reject(new AggregateError([], "All promises were rejected")); return; }
let rejectedCount = 0; for (let i = 0; i < n; i++) { MyPromise.resolve(pArray[i]).then( (value: any) => { resolve(value); }, (reason: any) => { errors[i] = reason; rejectedCount++; if (rejectedCount === n) { reject(new AggregateError(errors, "All promises were rejected")); } }, ); } }); }
static race(promises: Iterable<any>): MyPromise<any> { return new MyPromise((resolve, reject) => { const pArray = Array.isArray(promises) ? promises : Array.from(promises); for (const item of pArray) { MyPromise.resolve(item).then(resolve, reject); } }); }
static try<T>(fn: () => T | PromiseLike<T>): MyPromise<T> { return new MyPromise((resolve) => { resolve(fn()); }); }
static withResolvers<T>() { let resolve!: Resolve<T>; let reject!: Reject; const promise = new MyPromise<T>((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }}
export default MyPromise;require('./cjs-module.js') 时, Node.js 会将代码包裹到 IIFE 中
mkdir test && cd test && pnpm initecho 'module.exports = { name: "lark" }; console.log(arguments);' > ./cjs-module.js && node ./cjs-module.js(function (exports, require, module, __filename, __dirname) { module.exports = { name: "lark" }; console.log(arguments);})({}, require, module, __filename, __dirname);const twArg = "slate";const twArg2 = 500;// const templateStr = `text-${twArg}-${twArg2}`
// parser: 模版字符串的解析函数function parser( templateStrArr: TemplateStringsArray, ...insertedValues: unknown[]) { // templateStrArr: ['text-', '-', ''] // insertedValues: ['slate', 500] console.log(templateStrArr, insertedValues); return `color: #62748e;`;}
const parsedStr = parser`text-${twArg}-${twArg2}`;console.log(parsedStr); // color: #62748e同步任务, 异步任务
Section titled “同步任务, 异步任务”- 同步任务:
<script>整体代码- Promise 的构造函数是同步的
new Promise((resolve, reject) => {/** 同步代码 */})
- Promise 的构造函数是同步的
- 同步任务栈: 同步任务压入同步任务栈 (函数调用栈)
- 异步任务: 包括宏任务和微任务
- 宏任务
setTimeout,setInterval定时器XMLHttpRequest,fetch,postMessageI/O 操作requestAnimationFrame下一帧重绘回流前, 执行传递的回调函数setImmediatenode 环境
- 微任务
Promise[.then, .catch, .finally]async/awaitMutationObserver监听整个 DOM 树的改变process.nextTicknode 环境, 当前事件循环的所有的微任务执行前, 执行传递的回调函数
- 宏任务
- 异步任务队列
- 宏任务队列: 宏任务加入宏任务队列
- 微任务队列: 微任务加入微任务队列
同步任务即 <script> 整体代码 -> 同步任务的微任务队列 -> 宏任务 1 -> 宏任务 1 的微任务队列 -> 宏任务 2 -> 宏任务 2 的微任务队列 -> …
- 执行同步任务即
<script>整体代码, 将同步任务的所有微任务加入微任务队列 - 清空微任务队列: 按序执行所有微任务, 如果微任务执行过程中产生新的微任务, 则一并执行
- 从宏任务队列中取出并执行 1 个宏任务, 将该宏任务的所有微任务加入微任务队列
- 重复 2,3
如果将同步任务即 <script> 整体代码也视为一个宏任务, 则执行顺序简化为: 每一个事件循环, 先执行 1 个宏任务, 再执行该宏任务的所有微任务, 再进入下一个事件循环
浏览器在 1 帧中做了什么
Section titled “浏览器在 1 帧中做了什么”对于 60fps 的屏幕, 1 帧是 1000/60 = 16.7ms, 浏览器在 1 帧中:
- 处理用户事件: 例如 change, click, input 等
- 执行定时器回调函数
- 执行 requestAnimationFrame 下一帧重绘回流前, 执行传递的回调函数
- 重绘和回流: 重绘 repaint, 有关颜色等, 性能开销小; 回流 reflow, 有关宽高等, 性能开销大
- 如果有空闲时间, 则执行 requestIdleCallback (例如 idle 浏览器空闲时懒加载 JS 脚本)
hint (v8 引擎提供)
- number 数学运算触发, valueOf 优先
- string 字符串上下文触发, toString 优先, 例如 String(val);
${obj} - default 同 number
const isPrimitive = (val: unknown) => (typeof val !== "object" && typeof val !== "function") || val === null;
function toPrimitive(obj: object, hint: "number" | "string" | "default") { if ( obj[Symbol.toPrimitive] && typeof obj[Symbol.toPrimitive] === "function" ) { const res = obj[Symbol.toPrimitive].call(obj, hint); if (!isPrimitive(res)) { throw new TypeError(); } return res; }
const methods: ["toString", "valueOf"] | ["valueOf", "toString"] = hint === "string" ? ["toString", "valueOf"] : ["valueOf", "toString"];
for (const method of methods) { const fn = obj[method]; if (typeof fn === "function") { const res = fn.call(obj); if (isPrimitive(res)) { return res; } } } throw new TypeError();}例
const a = { val: 1, valueOf() { return this.val++; },};
const b = { val: 1, [Symbol.toPrimitive](hint) { console.log(hint); // default switch (hint) { case "number": case "default": { return this.val++; } case "string": default: { throw new TypeError(); } } },};
if (a == 1 && a == 2 && a == 3) { // valueOf 优先 console.log(true);}
if (b == 1 && b == 2 && b == 3) { console.log(true);}Falsy: false, undefined, null, 0, -0, NaN, ""
v8 中对象的结构
Section titled “v8 中对象的结构”v8 中对象的结构: elements 排序属性 + properties 常规属性 + 隐藏类
排序属性, 常规属性
Section titled “排序属性, 常规属性”- elements 排序属性, 数字属性称为排序属性, 根据 ECMA 规范, 可索引的属性按照索引值的大小升序排列
- properties 常规属性, 字符串属性称为常规属性
const obj = { 24: "elements-24", 1: "elements-1", 228: "elements-228", name: "properties-name", age: "properties-age", gender: "properties-gender",};
for (let key in obj) { console.log(`${key}: ${obj[key]}`);}
// obj// ├── elements 排序属性// │ ├── element0// │ ├── element1// │ ├── element2// │ └── ...// ├── properties// │ ├── property10 常规属性, 慢属性// │ ├── property11// │ ├── property12// │ └── ...// ├── property0 常规属性, 容量 10 个// ├── property1// ├── property2// ├── ...// └── property9快属性, 慢属性
Section titled “快属性, 慢属性”- v8 为了加快查找常规属性的速度, 将部分常规属性直接挂到对象自身, 称为对象内属性 (in-object properties)
- 查找快属性只需要 1 次查找
- 快属性的容量 10 个, 超过 10 个的常规属性称为慢属性
隐藏类是 v8 运行时自动生成和管理的数据结构, 用于跟踪对象的属性和方法; 隐藏类的编号 map_id 用于唯一标识该隐藏类
v8 自动生成两个隐藏类, 隐藏类 1 包含属性 name 和 age, 隐藏类 2 包含属性 name, age 和 gender, 其中属性 name 和 age 的过渡表指向隐藏类 1, 属性 gender 没有过渡表, 表示该属性是新增的
如果两个对象的属性顺序和类型都相同时, 则 v8 会自动生成一个共享的隐藏类, 可以节约内存空间
const obj1 = { name: "lark", age: 2 };const obj2 = { name: "lark", age: 2, gender: "male" };
// 隐藏类 1 包含属性 name 和 age// HiddenClass_1// ├── map_id: 1// ├── property_names: ['name', 'age']// ├── transitions: {}// └── prototype: Object.prototype
// 隐藏类 2 包含属性 name, age 和 gender// HiddenClass_2// ├── map_id: 2// ├── property_names: ['name', 'age', 'gender']// ├── transitions:// │ ├── a: HiddenClass_1// │ ├── b: HiddenClass_1// │ └── c: null// └── prototype: Object.prototypev8 垃圾回收
Section titled “v8 垃圾回收”v8 将堆内存分为新生代和老年代, 新生代中的对象存活时间较短, 老生代中的对象存活时间较长, 甚至常驻内存
代际假说认为, 大多数对象存活时间较短
新生代 gc
Section titled “新生代 gc”Scavenge 算法
- Scavenge 算法将新生代的堆内存一分为二, 每个内存空间称为 semi-space
- 两个 semi-space 中, 一个处于使用状态, 称为 from-space; 另一个处于闲置状态, 称为 to-space
- 分配对象时, 先在 from-space 中分配
- 垃圾回收时, 复制 from-space 中的存活对象到 to-space 中, 释放死亡对象占用的内存
- 复制后, 交换 from-space 和 to-space
即新生代的 gc 是将存活对象在两个 semi-space 间复制
对象晋升 (新生代 => 老年代)
Section titled “对象晋升 (新生代 => 老年代)”对象从新生代 from-space 晋升到老年代的条件: 对象至少经历过 1 次 gc, 或新生代 to-space 的内存占用率超过 25%
老年代 gc
Section titled “老年代 gc”- Mark-Sweep 标记-清除: 内存碎片化程度低时执行
- Mark-Compact 标记-紧凑: 内存碎片化程度高时执行
// 引用计数不能解决循环引用问题(() => { let a = {}; let b = {}; a.pb = b; b.pa = a;})();Mark-Sweep 标记-清除
Section titled “Mark-Sweep 标记-清除”内存碎片化程度低时执行
标记
- 构建一个根列表, 从根节点出发, 遍历所有可达对象, 标记为存活的; 不可达对象视为死亡的
- 根节点包括全局对象; 函数的参数, 局部变量; 闭包引用的对象; DOM 元素等…
清除: 清除阶段直接回收死亡对象占用的内存, 可能导致内存碎片化
Mark-Compact 标记-紧凑
Section titled “Mark-Compact 标记-紧凑”内存碎片化程度高时执行
标记
- 构建一个根列表, 从根节点出发, 遍历所有可达对象, 标记为存活的; 不可达对象视为死亡的 (垃圾)
- 根节点包括全局对象; 函数的参数, 局部变量; 闭包引用的对象; DOM 元素等…
紧凑: 紧凑阶段先将存活的对象移动到连续内存区域, 以清除内存碎片; 再回收其他内存区域
如何避免内存泄漏
Section titled “如何避免内存泄漏”- 少创建全局变量
- 手动清除定时器 (clearTimeout, clearInterval)
- 少使用闭包
- 清除 DOM 引用
- WeakMap 和 WeakSet 的键是弱引用, 不会增加引用计数
示例 1
function foo() { let a = 1; return function () { return a; };}
const bar = foo();- 通常, 函数执行结束后, 该函数的内部变量会被释放
- foo 函数返回 bar 函数, bar 函数有对 foo 函数的内部变量 a 的引用, foo 函数执行结束后, foo 函数的内部变量不会被释放
- 需要手动
bar = null, foo 函数的内部变量才会被释放
示例 2
const map = new Map([["btn", document.getElementById("btn")]]);const obj = { btn: document.getElementById("btn") };
function remove() { document.body.removeChild(document.getElementById("btn"));}- 调用 remove 函数以移除 button 元素, 但是 map, obj 中仍有对该 button 元素的引用
- 需要手动
map.delete('btn')和delete obj.btn(或obj.btn = null)
WeakMap 实验
Section titled “WeakMap 实验”// node --expose-gc # 允许手动 gcglobal.gc();// 查看当前内存占用process.memoryUsage().heapUsed; // 5e6
const wm = new WeakMap();let bigArray = new Array(1000000);wm.set(bigArray /** key */, 1 /** value */);process.memoryUsage().heapUsed; // 1e7
bigArray = null;process.memoryUsage().heapUsed; // 1e7global.gc();process.memoryUsage().heapUsed; // 5e6Web Worker
Section titled “Web Worker”Web Worker 有以下限制
- worker 线程执行的脚本与主线程执行的脚本必须同源
- worker 线程不允许操作 DOM, 不能使用 window, document, parent (parent === window) 对象, 可以使用 navigator 和 location 对象
- worker 线程不允许读取本地文件, 只允许加载网络文件
// 主线程创建一个 worker 子线程const worker = new Worker("./worker.js");
// 主线程向 worker 子线程发送消息worker.postMessage({ send: "ping" });
// 主线程监听 worker 子线程抛出的错误worker.onerror = function (e) { console.log("[main] error:", e);};// 等价于worker.addEventListener("error", function (e) { console.log("[main] error2:", e);});
// 主线程监听 worker 子线程发送的消息worker.onmessage = function (e) { console.log("[main] message:", e.data); // { echo: "pong" } // 主线程终止子线程 // worker.terminate();};// worker 子线程监听主线程发送的消息self.onmessage = function (e) { console.log("[worker] message:", e.data); // { send: 'ping' } self.postMessage({ echo: "pong" }); // worker 子线程主动关闭 // self.close();};// 等价于self.addEventListener("message", function (e) { console.log("[worker] message2:", e.data); // { send: 'ping' } self.postMessage({ echo: "pong2" }); // worker 子线程主动关闭 // self.close();});- 主线程和 worker 线程的数据通信: 使用 window.structuredClone 结构化克隆算法, 深拷贝数据
- 对于可转移对象 Transferable Objects, 可以转移所有权, 避免深拷贝
// 主线程const arr = new Uint8Array(new ArrayBuffer(8));worker.postMessage(arr); // 深拷贝worker.postMessage(arr, [arr]); // 转移所有权
// worker 线程self.onmessage = function (ev) { console.log(ev.data);};TS 装饰器
Section titled “TS 装饰器”const ClassDecoratorInst: ClassDecorator = (target) => { // target: 类, 类是构造函数的语法糖 console.log(target.name); // Sugar console.log(typeof target); // function target.prototype.name = "NewSugar";};
@ClassDecoratorInstclass Sugar {}
const sugar: any = new Sugar();console.log(sugar.name); // NewSugarconst PropDecoratorInst: PropertyDecorator = (target, propKey) => { // target: 原型对象 // propKey: 属性名 console.log(target, propKey);};
class Sugar { @PropDecoratorInst public name: string = "sugarInst";
@PropDecoratorInst add = function (a: number, b: number) { return a + b; };
@PropDecoratorInst sub = (a: number, b: number) => a - b;}
const sugar = new Sugar();console.log(sugar.name); // sugarInstconsole.log(sugar.add(1, 2)); // 3console.log(sugar.sub(1, 2)); // -1const MethodDecoratorInst: MethodDecorator = ( target, // 原型对象 propKey, // 属性名, 即方法名 propDescriptor, // 属性描述对象) => { console.log(target, propKey, propDescriptor);};
class Sugar { private _name: string = "sugarInst";
@MethodDecoratorInst foo(a: number, b: number) { return a + b; }
@MethodDecoratorInst get name() { return this._name; }
set name(newName: string) { this._name = newName; }}
const sugar = new Sugar();console.log(sugar.name); // sugarInstsugar.name = "newSugarInst";console.log(sugar.name); // newSugarInstconst ParamDecoratorInst: ParameterDecorator = ( target, // 原型对象 propKey, // 属性名, 即方法名 paramIndex, // 参数索引) => { console.log(target, propKey, paramIndex);};
class Sugar { private _name: string = "sugarInst";
add(@ParamDecoratorInst a: number, @ParamDecoratorInst b: number) { return a + b; }
get name() { return this._name; }
set name(@ParamDecoratorInst newName: string) { this._name = newName; }}
const sugar = new Sugar();console.log(sugar.name); // sugarInstsugar.name = "newSugarInst";console.log(sugar.name); // newSugarInst装饰器示例
const Get: (config: { url: string }) => MethodDecorator = ({ url }) => { return (target, propKey, propDescriptor) => { const method: any = propDescriptor.value; fetch(url) .then((res) => res.text()) .then((data) => { method({ data, code: 200, msg: "OK" }); }) .catch((err) => { method({ data: JSON.stringify(err), code: 404, msg: "Not Found" }); }); };};
class Controller { constructor() {}
@Get({ url: "https://hangtiancheng.github.io/homepage/" }) getHomepage(res: { data: string; code: number; msg: string }) { const { data, code, msg } = res; console.log(data, code, msg); }}TS 类型工具
Section titled “TS 类型工具”keyof T获取 T 的所有键, 生成一个联合类型Record<K, V>创建一个对象类型, 键为 K 类型, 值为 V 类型Partial<T>将 T 中所有属性变为可选Required<T>将 T 中所有属性变为必选Readonly<T>将 T 中所有属性变为只读Pick<T, "field" | "filed2">从 T 中选择一组属性 field, field2 构造新类型Omit<T, "field" | "filed2">从 T 中忽略一组属性 field, field2 构造新类型Exclude<T, U>从 T 中排除可以赋值给 U 的类型Extract<T, U>从 T 中提取可以赋值给 U 的类型 (类型的交集)NonNullable<T>从 T 中排除 null 和 undefinedParameters<F>获取函数类型 F 的参数类型ReturnType<F>获取函数类型 F 的返回值类型ConstructorParameters<F>获取构造函数 F 的参数类型InstanceType<C>获取类的实例类型Awaited<Y>获取 Promiseresolve(value)的值类型 (也即onfulfilled的返回值类型)Uppercase<S>,Lowercase<S>,Capitalize<S>,Uncapitalize<S>
interface User { name: string; age: number;}
type OnChangeEvents = { [K in keyof User as `on${Capitalize<K>}Change`]: (value: User[K]) => void;};
// type OnChangeEvents = {// onNameChange: (value: string) => void;// onAgeChange: (value: number) => void;// }infer 泛型推断
Section titled “infer 泛型推断”// 泛型推断interface IUser { name: string; age: number;}
type PromisifiedUser = Promise<IUser>;
type TryInferGenericsType<T> = T extends Promise<infer UnknownGenericsType> ? UnknownGenericsType // infer succeed : T; // infer failed
// type InferredGenericsType = IUsertype InferredGenericsType = TryInferGenericsType<PromisifiedUser>;const user: InferredGenericsType = { name: "lark", age: 1 };
// 递归的泛型推断type DeepPromisifiedUser = Promise<Promise<Promise<IUser>>>;type TryRecursivelyInferGenericsType<T> = T extends Promise<infer UnknownGenericsType> ? TryRecursivelyInferGenericsType<UnknownGenericsType> : T;type RecursivelyInferredGenericsType = TryRecursivelyInferGenericsType<DeepPromisifiedUser>;
// type RecursivelyInferredGenericsType = IUserconst user2: RecursivelyInferredGenericsType = { name: "lark", age: 1 };infer 协变 (类型的并集), 逆变 (类型的交集)
Section titled “infer 协变 (类型的并集), 逆变 (类型的交集)”const user = { name: "lark", age: 1 };type TryInferType<T> = T extends { name: infer UnknownNameType; age: infer UnknownAgeType;} ? [UnknownNameType, UnknownAgeType] : T;
type InferredType /** [string, number] */ = TryInferType<typeof user>;const user2: InferredType = [user.name, user.age];
// 协变返回或类型type TryInferType<T> = T extends { name: infer UnknownUnionType; age: infer UnknownUnionType;} ? UnknownUnionType : T;type InferredType /** string | number */ = TryInferType<typeof user>;const str: InferredType = "lark";const num: InferredType = 1;
// 逆变返回与类型type TryInferType<T> = T extends { fn1: (arg: infer UnknownArgType) => void; fn2: (arg: infer UnknownArgType) => void;} ? UnknownArgType : unknown;
type InferredType /** never (number & string === never) */ = TryInferType<{ fn1: (arg: number) => void; fn2: (arg: string) => void;}>;type InferredType2 /** number */ = TryInferType<{ fn1: (arg: number) => void; fn2: (arg: number) => void;}>;type Arr = ["a", "b", "c"];
type TryInferType<T extends unknown[]> = T extends [ infer UnknownFirstElemType, infer UnknownSecondElemType, infer UnknownThirdElemType,] ? { first: UnknownFirstElemType; second: UnknownSecondElemType; third: UnknownThirdElemType; } : unknown;
// { first: "a", second: "b", third: "c" }type InferredType = TryInferType<Arr>;// FirstElemTypetype TryInferType<T extends unknown[]> = T extends [ infer UnknownFirstElemType, ...unknown[],] ? UnknownFirstElemType : unknown;
type InferredType /** "a" */ = TryInferType<Arr>;
// PreRestTypetype TryInferType<T extends unknown[]> = T extends [ ...infer UnknownPreRestType, unknown,] ? UnknownPreRestType : unknown;
type InferredType /** ["a", "b"] */ = TryInferType<Arr>;
// LastElemTypetype TryInferType<T extends unknown[]> = T extends [ ...unknown[], infer UnknownLastElemType,] ? UnknownLastElemType : unknown;
type InferredType /** "c" */ = TryInferType<Arr>;
// RestTypetype TryInferType<T extends unknown[]> = T extends [ unknown, ...infer UnknownRestType,] ? UnknownRestType : unknown;
type InferredType /** ["b", "c"] */ = TryInferType<Arr>;type Arr = [1, 2, 3, 4, 5];type TryInferType<T extends unknown[]> = T extends [ infer UnknownFirstElemType, ...infer UnknownRestType,] ? [...TryInferType<UnknownRestType>, UnknownFirstElemType] // Recurse : T;
type InferredType /** [5, 4, 3, 2, 1] */ = TryInferType<Arr>;type ReversedArr = InferredType;