Skip to Content

JS/TS

错误处理方式

try/catchwindow.onerrorwindow.addEventListener('error')window.addEventListener('unhandledrejection')
同步错误YYY
异步回调错误YY
未处理的 Promise rejectionY
async/await 异步错误YY (未 try/catch 的 async/await 异步错误 )
资源加载错误Y (事件捕获阶段)
语法错误YY
  • 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 命令

npm config list npm get registry npm config set registry https://registry.npmmirror.com npm config get script-shell npm config set script-shell "C:/Program Files/Git/bin/bash.exe"

Promise

CNvadjn
解决 (兑现)resolveresolved (fulfilled)result, value
拒绝rejectrejectedreason, error
静态方法resolvedrejected
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

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 或 rejected enum 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 init echo '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

事件循环

同步任务, 异步任务

  • 同步任务: <script> 整体代码
    • Promise 的构造函数是同步的 new Promise((resolve, reject) => {/** 同步代码 */})
  • 同步任务栈: 同步任务压入同步任务栈 (函数调用栈)
  • 异步任务: 包括宏任务和微任务
    • 宏任务
      • setTimeout, setInterval 定时器
      • XMLHttpRequest, fetch, postMessage I/O 操作
      • requestAnimationFrame 下一帧重绘回流前, 执行传递的回调函数
      • setImmediate node 环境
    • 微任务
      • Promise[.then, .catch, .finally]
      • async/await
      • MutationObserver 监听整个 DOM 树的改变
      • process.nextTick node 环境, 当前事件循环的所有的微任务执行前, 执行传递的回调函数
  • 异步任务队列
    • 宏任务队列: 宏任务加入宏任务队列
    • 微任务队列: 微任务加入微任务队列

执行顺序

同步任务即 <script> 整体代码 -> 同步任务的微任务队列 -> 宏任务 1 -> 宏任务 1 的微任务队列 -> 宏任务 2 -> 宏任务 2 的微任务队列 -> …

  1. 执行同步任务即 <script> 整体代码, 将同步任务的所有微任务加入微任务队列
  2. 清空微任务队列: 按序执行所有微任务, 如果微任务执行过程中产生新的微任务, 则一并执行
  3. 从宏任务队列中取出并执行 1 个宏任务, 将该宏任务的所有微任务加入微任务队列
  4. 重复 2,3

如果将同步任务即 <script> 整体代码也视为一个宏任务, 则执行顺序简化为: 每一个事件循环, 先执行 1 个宏任务, 再执行该宏任务的所有微任务, 再进入下一个事件循环

浏览器在 1 帧中做了什么

对于 60fps 的屏幕, 1 帧是 1000/60 = 16.7ms, 浏览器在 1 帧中:

  1. 处理用户事件: 例如 change, click, input 等
  2. 执行定时器回调函数
  3. 执行 requestAnimationFrame 下一帧重绘回流前, 执行传递的回调函数
  4. 重绘和回流: 重绘 repaint, 有关颜色等, 性能开销小; 回流 reflow, 有关宽高等, 性能开销大
  5. 如果有空闲时间, 则执行 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 中对象的结构

v8 中对象的结构: elements 排序属性 + properties 常规属性 + 隐藏类

排序属性, 常规属性

  • 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

快属性, 慢属性

  • 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.prototype

v8 垃圾回收

v8 将堆内存分为新生代和老年代, 新生代中的对象存活时间较短, 老生代中的对象存活时间较长, 甚至常驻内存

代际假说认为, 大多数对象存活时间较短

新生代 gc

Scavenge 算法

  1. Scavenge 算法将新生代的堆内存一分为二, 每个内存空间称为 semi-space
  2. 两个 semi-space 中, 一个处于使用状态, 称为 from-space; 另一个处于闲置状态, 称为 to-space
  3. 分配对象时, 先在 from-space 中分配
  4. 垃圾回收时, 复制 from-space 中的存活对象到 to-space 中, 释放死亡对象占用的内存
  5. 复制后, 交换 from-space 和 to-space

即新生代的 gc 是将存活对象在两个 semi-space 间复制

对象晋升 (新生代 => 老年代)

对象从新生代 from-space 晋升到老年代的条件: 对象至少经历过 1 次 gc, 或新生代 to-space 的内存占用率超过 25%

老年代 gc

  • Mark-Sweep 标记-清除: 内存碎片化程度低时执行
  • Mark-Compact 标记-紧凑: 内存碎片化程度高时执行
// 引用计数不能解决循环引用问题 (() => { let a = {}; let b = {}; a.pb = b; b.pa = a; })();

Mark-Sweep 标记-清除

内存碎片化程度低时执行

标记

  • 构建一个根列表, 从根节点出发, 遍历所有可达对象, 标记为存活的; 不可达对象视为死亡的
  • 根节点包括全局对象; 函数的参数, 局部变量; 闭包引用的对象; DOM 元素等…

清除: 清除阶段直接回收死亡对象占用的内存, 可能导致内存碎片化

Mark-Compact 标记-紧凑

内存碎片化程度高时执行

标记

  • 构建一个根列表, 从根节点出发, 遍历所有可达对象, 标记为存活的; 不可达对象视为死亡的 (垃圾)
  • 根节点包括全局对象; 函数的参数, 局部变量; 闭包引用的对象; DOM 元素等…

紧凑: 紧凑阶段先将存活的对象移动到连续内存区域, 以清除内存碎片; 再回收其他内存区域

如何避免内存泄漏

  1. 少创建全局变量
  2. 手动清除定时器 (clearTimeout, clearInterval)
  3. 少使用闭包
  4. 清除 DOM 引用
  5. 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 实验

// node --expose-gc # 允许手动 gc global.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; // 1e7 global.gc(); process.memoryUsage().heapUsed; // 5e6

Web Worker

概述

Web Worker 有以下限制

  1. worker 线程执行的脚本与主线程执行的脚本必须同源
  2. worker 线程不允许操作 DOM, 不能使用 window, document, parent (parent === window) 对象, 可以使用 navigator 和 location 对象
  3. worker 线程不允许读取本地文件, 只允许加载网络文件

基本使用

main 主线程
// 主线程创建一个 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 线程的数据通信: 使用 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 装饰器

类装饰器
const ClassDecoratorInst: ClassDecorator = (target) => { // target: 类, 类是构造函数的语法糖 console.log(target.name); // Sugar console.log(typeof target); // function target.prototype.name = "NewSugar"; }; @ClassDecoratorInst class Sugar {} const sugar: any = new Sugar(); console.log(sugar.name); // NewSugar

装饰器示例

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://161043261.github.io/homepage/" }) getHomepage(res: { data: string; code: number; msg: string }) { const { data, code, msg } = res; console.log(data, code, msg); } }

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 和 undefined
  • Parameters<F> 获取函数类型 F 的参数类型
  • ReturnType<F> 获取函数类型 F 的返回值类型
  • ConstructorParameters<F> 获取构造函数 F 的参数类型
  • InstanceType<C> 获取类的实例类型
  • Awaited<Y> 获取 Promise resolve(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 泛型推断

// 泛型推断 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 = IUser type 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 = IUser const user2: RecursivelyInferredGenericsType = { name: "lark", age: 1 };

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; }>;

Demo

Demo 1
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>;
Last updated on