进阶知识点补充

这一篇是收尾的进阶补充,把之前零散或没深入的知识点集中讲透:原型与原型链、深浅拷贝、防抖/节流、Symbol、迭代器与生成器,以及严格模式、柯里化、类型判断、逻辑赋值运算符、Proxy 等。

一、原型与原型链(深入)

1. 核心关系(基础回顾)

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
}
Person.prototype.say = function () {
console.log(this.name);
};

const p = new Person("Tom");
p.say(); // "Tom"

三条关键等式(原型链的本质):

1
2
3
p.__proto__ === Person.prototype;                  // true(实例 → 原型对象)
Person.prototype.__proto__ === Object.prototype; // true(原型对象 → Object 原型)
Object.prototype.__proto__ === null; // true(原型链顶端)

2. 现代 API:getPrototypeOf / setPrototypeOf

1
Object.getPrototypeOf(p) === Person.prototype;   // true(推荐,替代 __proto__)

3. instanceof 的原理

instanceof 就是沿对象的原型链向上查找,看是否出现过某个构造函数的 prototype:

1
2
p instanceof Person;   // true(链上有 Person.prototype)
p instanceof Object; // true(链上有 Object.prototype)

4. new 操作符内部做了什么

1
2
3
4
5
6
7
8
function myNew(constructor, ...args) {
// 1. 创建一个新对象,其原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 执行构造函数,this 指向新对象
const result = constructor.apply(obj, args);
// 3. 构造函数返回对象就用返回值,否则返回新对象
return result instanceof Object ? result : obj;
}

记忆点:new = 建对象 + 绑原型 + 执行构造 + 返回对象。理解它,就理解了原型和 class 的关系。

二、深浅拷贝

1. 赋值 vs 浅拷贝 vs 深拷贝

1
2
3
4
5
6
7
8
9
10
11
const obj = { a: { b: 1 } };

const ref = obj; // 赋值:共享同一个引用
ref.a.b = 2; // obj.a.b 也变 2

const shallow = { ...obj }; // 浅拷贝:只拷贝第一层
shallow.a.b = 3; // obj.a.b 也变 3(内层仍共享)
shallow.a = {}; // obj.a 不变(第一层已独立)

const deep = structuredClone(obj); // 深拷贝:完全独立
deep.a.b = 4; // obj.a.b 不变

2. 浅拷贝的方法

1
2
3
4
Object.assign({}, obj);   // 对象浅拷贝
{ ...obj }; // 展开运算符
arr.slice(); // 数组浅拷贝
[...arr];

3. 深拷贝的方法

① JSON 方式(有局限):

1
const copy = JSON.parse(JSON.stringify(obj));

⚠️ 局限:会丢失函数、undefined、Symbol,无法处理循环引用、Date 转字符串。只适合纯数据对象。

② 手写递归深拷贝:

1
2
3
4
5
6
7
8
9
10
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj; // 原始值直接返回
const copy = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]); // 递归
}
}
return copy;
}

③ 原生 structuredClone(现代推荐):

1
const copy = structuredClone(obj);   // 支持循环引用、Date 等

三、防抖 debounce

概念:触发后 n 秒内没有再次触发,才执行(只执行最后一次)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer); // 每次触发都清掉上次定时器
timer = setTimeout(() => {
fn.apply(this, args); // 延迟 delay 毫秒后执行
}, delay);
};
}

// 用法:输入框搜索
input.addEventListener("input", debounce(function () {
console.log("发起搜索");
}, 500));

场景:搜索框输入、窗口 resize、按钮防连点。

四、节流 throttle

概念:n 秒内最多执行一次(固定频率)。

1
2
3
4
5
6
7
8
9
10
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) { // 距上次执行超过 delay 才执行
fn.apply(this, args);
lastTime = now;
}
};
}

场景:滚动监听、鼠标移动(高频触发但无需每次都响应)。

防抖 vs 节流

防抖 debounce 节流 throttle
触发时机 停止触发 n 秒后才执行 每 n 秒固定执行一次
比喻 电梯等人(等人齐了才关门) 红绿灯(固定间隔放行)
场景 搜索框、resize 滚动、mousemove

记忆:防抖是”最后一下”,节流是”固定频率”。两者都用闭包保存定时器/时间戳。

五、Symbol

1. 概念:独一无二的值

1
2
3
const s1 = Symbol("desc");
const s2 = Symbol("desc");
s1 === s2; // false(描述相同也不相等)

2. 用途:对象唯一属性名

1
2
3
4
5
6
const key = Symbol("id");
const obj = {
[key]: 123,
name: "Tom"
};
obj[key]; // 123

⚠️ Symbol 属性不会被 Object.keys / for...in 遍历到,适合做”隐藏”属性、避免命名冲突。

3. 全局注册表:Symbol.for / keyFor

1
2
3
4
const s1 = Symbol.for("foo");
const s2 = Symbol.for("foo");
s1 === s2; // true(全局注册表里是同一个)
Symbol.keyFor(s1); // "foo"

4. 内置 Symbol

  • Symbol.iterator:定义对象的迭代行为(见下一节)。
  • Symbol.toPrimitive:对象转原始值时调用。

六、迭代器与生成器

1. 迭代器协议

有 next() 方法、返回 { value, done } 的对象就是迭代器:

1
2
3
4
5
6
7
const arr = [1, 2, 3];
const it = arr[Symbol.iterator](); // 拿到迭代器

it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }

for...of 的底层原理就是:调用 Symbol.iterator 拿迭代器,循环 next() 直到 done 为 true。这也是为什么普通对象不能用 for...of(没有 Symbol.iterator)。

2. 生成器 function* / yield

生成器是能暂停和恢复的函数,每次 yield 暂停,下次 next() 继续:

1
2
3
4
5
6
7
8
9
10
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: false }
g.next(); // { value: undefined, done: true }

3. 应用:惰性求值 / 无限序列

1
2
3
4
5
6
7
8
9
function* idMaker() {
let id = 1;
while (true) {
yield id++;
}
}
const ids = idMaker();
ids.next().value; // 1
ids.next().value; // 2(用到才生成,不占内存)

七、其他重要知识点

1. 严格模式 “use strict”

1
"use strict";   // 写在文件或函数开头

主要影响:

  • 未声明就赋值的变量会报错(非严格会静默创建全局变量)。
  • 函数独立调用时 this 为 undefined(非严格是 window)。
  • 禁止重复参数名、with 等。

2. 柯里化与函数组合

柯里化:把多参函数变成”一次只收一个参数”的链式函数:

1
2
3
4
5
6
function add(a) {
return function (b) {
return a + b;
};
}
add(1)(2); // 3

函数组合:把多个函数拼成一个(从右到左执行):

1
2
3
4
5
const compose = (f, g) => x => f(g(x));
const double = x => x * 2;
const plusOne = x => x + 1;
const fn = compose(double, plusOne);
fn(3); // (3+1)*2 = 8

3. 类型判断全解

方式 适用 示例
typeof 基本类型(null 除外) typeof "a" → "string"
instanceof 判断原型链 [] instanceof Array → true
Object.prototype.toString.call 最准确 call([]) → "[object Array]"
1
2
3
Object.prototype.toString.call([]);      // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call({}); // "[object Object]"

4. 逻辑赋值运算符

1
2
3
a ||= b;   // 等价 a || (a = b),a 为 falsy 时才赋值
a &&= b; // 等价 a && (a = b),a 为 truthy 时才赋值
a ??= b; // 等价 a ?? (a = b),a 为 null/undefined 时才赋值

5. Proxy / Reflect(简要)

Proxy 拦截对象操作,是 Vue3 响应式的底层:

1
2
3
4
5
6
7
8
9
10
11
12
13
const target = { name: "Tom" };
const obj = new Proxy(target, {
get(target, key) {
console.log("读取", key);
return target[key];
},
set(target, key, value) {
console.log("设置", key, value);
target[key] = value;
}
});
obj.name; // 打印"读取 name"
obj.name = "Jerry"; // 打印"设置 name Jerry"

八、速查表

需求 写法
取对象原型 Object.getPrototypeOf(obj)
判断原型链 obj instanceof Constructor
浅拷贝 { ...obj } / Object.assign({}, obj)
深拷贝 structuredClone(obj)
防抖 clearTimeout + setTimeout
节流 时间戳比较 Date.now()
创建 Symbol Symbol("desc") / Symbol.for("key")
拿迭代器 arr[Symbol.iterator]()
生成器 function* g() { yield 1; }
严格模式 "use strict"
柯里化 a => b => a + b
最准确类型判断 Object.prototype.toString.call(x)
逻辑赋值 a ??= b
拦截对象操作 new Proxy(target, handler)

九、复习要点

  1. 原型链:实例.__proto__ → 构造函数.prototype → Object.prototype → null;instanceof 就是沿链查找;new 做了四件事。
  2. 浅拷贝只复制第一层(.../Object.assign),内层仍共享;深拷贝用 structuredClone(或递归/JSON,注意 JSON 丢函数)。
  3. 防抖停止 n 秒后才执行(搜索框),节流每 n 秒执行一次(滚动),都用闭包实现。
  4. Symbol 是唯一值,做对象唯一属性名,且不会被 Object.keys 遍历。
  5. 迭代器有 next() 返回 { value, done };for...of 靠 Symbol.iterator;生成器 function* 能暂停恢复。
  6. 严格模式让 this 更安全、未声明变量报错。
  7. 柯里化是”一次一个参数”,组合是”从右到左拼函数”。
  8. 类型判断:基本类型用 typeof、原型链用 instanceof、最准用 toString.call。