JS/TS
错误处理方式
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 命令
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
| 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
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) => {/** 同步代码 */})
- 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 帧中做了什么
对于 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 中对象的结构
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.prototypev8 垃圾回收
v8 将堆内存分为新生代和老年代, 新生代中的对象存活时间较短, 老生代中的对象存活时间较长, 甚至常驻内存
代际假说认为, 大多数对象存活时间较短
新生代 gc
Scavenge 算法
- Scavenge 算法将新生代的堆内存一分为二, 每个内存空间称为 semi-space
- 两个 semi-space 中, 一个处于使用状态, 称为 from-space; 另一个处于闲置状态, 称为 to-space
- 分配对象时, 先在 from-space 中分配
- 垃圾回收时, 复制 from-space 中的存活对象到 to-space 中, 释放死亡对象占用的内存
- 复制后, 交换 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 元素等…
紧凑: 紧凑阶段先将存活的对象移动到连续内存区域, 以清除内存碎片; 再回收其他内存区域
如何避免内存泄漏
- 少创建全局变量
- 手动清除定时器 (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 实验
// 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; // 5e6Web Worker
概述
Web Worker 有以下限制
- worker 线程执行的脚本与主线程执行的脚本必须同源
- worker 线程不允许操作 DOM, 不能使用 window, document, parent (parent === window) 对象, 可以使用 navigator 和 location 对象
- 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 和 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 泛型推断
// 泛型推断
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>;