🚀 JavaScript 进阶必知必会 — 那些容易忘掉但超实用的知识点(建议收藏)
本文整理了前端开发中最常用、最容易遗忘、但面试和实战又超高频的 10 个 JavaScript 进阶知识点。每个点都配有可直接复制运行的代码示例,适合查漏补缺。
📑 目录
- 1. 深浅拷贝:再也不怕引用类型乱改了
- 2. 数组方法:shift / unshift / splice 一锅端
- 3. reduce 高阶用法:求和只是皮毛
- 4. 一行代码搞定:求和 / 去重 / 洗牌 / …
- 5. 善用 eval:强大但危险的双刃剑
- 6. 防抖 & 节流:性能优化必备
- 7. 闭包:为什么函数能"记住"它出生的地方
- 8. Promise 链式调用:让异步不再地狱
- 9. apply / call / bind:this 到底指向谁
- 10. 解构 & 展开运算符:代码瞬间变优雅
1. 深浅拷贝:再也不怕引用类型乱改了
基本类型拷贝值,引用类型拷贝地址。浅拷贝只复制第一层,深拷贝递归到底。
① JSON 序列化法(最简单)
能处理大部分场景,但无法拷贝函数、undefined、Symbol、循环引用,Date 会变成字符串。
const original = { a: 1, b: { c: 2 }, d: [ 3, 4 ] };
const deep = JSON.parse(JSON.stringify(original));
// ✅ 简单粗暴、一行搞定
// ❌ 不能拷函数、undefined、Symbol、RegExp、Date、Map/Set、循环引用
② 原生 structuredClone(推荐,现代浏览器)
浏览器原生 API,支持 Date、RegExp、Map、Set、ArrayBuffer,甚至循环引用。
const original = { map: new Map([['a', 1]]), date: new Date() };
const deep = structuredClone(original); // 完美 ✨
③ 递归手写(理解原理)
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (cache.has(obj)) return cache.get(obj); // 解决循环引用
const clone = Array.isArray(obj) ? [] : {};
cache.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], cache);
}
}
return clone;
}
💡 生产环境优先用
structuredClone,老环境再用 JSON 方案或 Lodash 的_.cloneDeep。
2. 数组方法:shift / unshift / splice 一锅端
这些方法会直接修改原数组(mutate),面试常被问区别。
修改原数组的 7 大方法
const arr = [1, 2, 3, 4, 5];
// 增
arr.push(6); // 末尾加,返回新长度
arr.unshift(0); // 开头加,返回新长度(比 push 慢很多!)
// 删
arr.pop(); // 末尾删,返回被删的元素
arr.shift(); // 开头删,返回被删的元素(比 pop 慢很多!)
// 改
arr.splice(2, 1, 'x'); // 从索引2删1个,插入'x',返回被删的数组
arr.reverse(); // 反转
arr.sort((a, b) => a - b); // 排序(传比较函数!)
不修改原数组的"安全"方法
arr.map(); // 映射
arr.filter(); // 过滤
arr.slice(); // 切片(splice 的安全替代)
arr.concat(); // 拼接
[...arr, 6]; // ⭐ 推荐:展开运算符,纯函数风格
⚠️ shift / unshift 性能陷阱:它们会让整个数组重新索引,时间复杂度 O(n),大数组尽量避免。需要频繁头尾操作请用
push/pop+ 索引。
3. reduce 高阶用法:求和只是皮毛
reduce 是数组的"瑞士军刀",能做 map / filter / flat / groupBy 几乎所有事。
reduce 语法回顾
arr.reduce((accumulator, currentValue, index, array) => {
return accumulator;
}, initialValue); // ⚠️ 初始值忘了会跳过第一次循环!
① 数组求和 / 求积
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((a, b) => a + b, 0); // 15
const mul = nums.reduce((a, b) => a * b, 1); // 120
② 数组扁平化(替代 flat)
const nested = [1, [2, 3], [4, [5, 6]]];
// 扁平一层
const flat1 = nested.reduce((a, b) => a.concat(b), []);
// [1, 2, 3, 4, [5, 6]]
// 递归扁平化(等价于 .flat(Infinity))
const flatDeep = arr =>
arr.reduce((a, b) =>
a.concat(Array.isArray(b) ? flatDeep(b) : b), []);
// [1, 2, 3, 4, 5, 6]
③ 按属性分组(替代 groupBy)
const people = [
{ name: '小明', age: 20 },
{ name: '小红', age: 22 },
{ name: '小刚', age: 20 },
];
const byAge = people.reduce((acc, p) => {
(acc[p.age] = acc[p.age] || []).push(p);
return acc;
}, {});
// { 20: [小明, 小刚], 22: [小红] }
④ 统计词频
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const freq = words.reduce(
(acc, w) => (acc[w] = (acc[w] || 0) + 1, acc), {}
);
// { apple: 3, banana: 2, orange: 1 }
💡 ES2024 已原生支持
Object.groupBy()和arr.groupToMap(),reduce 可以作为兼容方案。
4. 一行代码搞定:求和 / 去重 / 洗牌 / …
写代码不只是功能对,还要简洁优雅。以下都是一行搞定的骚操作,建议收藏。
// 🔢 数组求和 / 求积
const sum = arr.reduce((a, b) => a + b, 0);
const product = arr.reduce((a, b) => a * b, 1);
// 🔢 求最大值 / 最小值
const max = Math.max(...arr);
const min = Math.min(...arr);
// 🔢 平均值
const avg = arr.reduce((a, b) => a + b, 0) / arr.length;
// 🎯 数组去重
const unique = [...new Set(arr)];
// 🎯 对象数组按属性去重
const dedup = [...new Map(arr.map(o => [o.id, o])).values()];
// 🎲 随机洗牌(Fisher-Yates 真正均匀版)
const fairShuffle = arr => {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
};
// 🎲 随机取数组一个元素
const randomItem = arr => arr[Math.floor(Math.random() * arr.length)];
// 📦 快速合并两个对象(后者覆盖前者)
const merged = { ...obj1, ...obj2 };
// 📦 数组转对象
const obj = Object.fromEntries(arr.map(v => [v.id, v]));
// 📦 对象转数组
const entries = Object.entries(obj); // [[key, value], ...]
// 🧹 清空数组(3 种方式)
arr.splice(0); // ✅ 快,保留引用
arr.length = 0; // ✅ 最快,保留引用
arr = []; // ❌ 创建新数组,外部引用还在
// 🔗 URL 参数转对象
const params = Object.fromEntries(new URLSearchParams(location.search));
// 🔗 对象序列化成 query string
const query = new URLSearchParams(obj).toString();
5. 善用 eval:强大但危险的双刃剑
eval 能把字符串当代码执行,但会带来安全风险和性能问题。能用其他方式就别用它。
eval 的典型使用场景
// 场景1:动态计算表达式
const expr = "(3 + 5) * 2";
const result = eval(expr); // 16
// 场景2:动态访问对象属性(❌ 不推荐,用下面的方式)
const key = 'name';
const val = obj[key]; // ✅ 这就行
const val2 = Reflect.get(obj, key); // ✅ 更规范
// 场景3:解析 JSON(❌ 绝对不要用 eval,用 JSON.parse)
const data = JSON.parse(jsonStr); // ✅ 安全
// 场景4:函数构造器(比 eval 稍微好一点)
const fn = new Function('x', 'return x * 2');
fn(5); // 10
⚠️ eval 的 3 宗罪:
- 安全:执行恶意字符串 = XSS 漏洞
- 性能:eval 内的代码无法被 JS 引擎优化
- 作用域:eval 会污染当前作用域(严格模式稍好)
原则:能用不用,别强迫自己用。
必须用 eval 时的安全做法
// ⭐ 用 Function 构造器隔离作用域
const safeEval = code => {
return new Function('"use strict"; return (' + code + ')')();
};
// 或用沙箱 iframe
// const sandbox = document.createElement('iframe');
// sandbox.src = 'about:blank';
// sandbox.contentWindow.eval(userCode);
6. 防抖 & 节流:性能优化必备
高频触发的事件(scroll、resize、input、mousemove)用防抖或节流降频。
防抖 debounce:停下来 N 毫秒后才执行
function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 使用:搜索框输入防抖
const handleSearch = debounce(keyword => {
console.log('搜索:', keyword);
}, 500);
节流 throttle:每隔 N 毫秒最多执行一次
function throttle(fn, interval = 300) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用:滚动监听节流
window.addEventListener('scroll', throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200));
💡 口诀:防抖 = “停了再执行”,节流 = “等间隔执行”。搜索框用防抖,滚动事件用节流。
7. 闭包:为什么函数能"记住"它出生的地方
闭包 = 函数 + 它出生时所在的词法环境。函数被外层变量引用时,整个作用域都不会被回收。
典型场景 1:数据私有化
function createCounter() {
let count = 0; // 外部访问不到
return {
increment() { count++; },
decrement() { count--; },
get() { return count; },
};
}
const c = createCounter();
c.increment(); c.increment();
console.log(c.get()); // 2
console.log(c.count); // undefined ← 私有!
典型场景 2:循环 + setTimeout 的经典坑
// ❌ 错误写法:打印 5 次 5
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// ✅ 闭包修复(IIFE)
for (var i = 0; i < 5; i++) {
((n) => setTimeout(() => console.log(n), 100))(i);
}
// ✅ ES6 更简单:let 块级作用域天然解决
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
⚠️ 内存泄漏风险:闭包会让作用域常驻内存,用多了可能导致内存泄漏。现代浏览器有良好的 GC,但老代码要注意:不需要时手动解除引用(置为 null)。
8. Promise 链式调用:让异步不再地狱
Promise 解决了"回调地狱"问题,配合 async/await 写出同步风格的异步代码。
Promise 的三种状态
new Promise((resolve, reject) => {
const ok = true;
ok ? resolve('成功') : reject('失败');
})
.then(res => console.log(res))
.catch(err => console.error(err))
.finally(() => console.log('结束'));
并行请求:all / race / allSettled
const p1 = fetch('/api/a').then(r => r.json());
const p2 = fetch('/api/b').then(r => r.json());
// ✅ 全部成功才继续,一个失败全崩
const [dataA, dataB] = await Promise.all([p1, p2]);
// ✅ 竞速:谁先返回用谁
const first = await Promise.race([p1, p2]);
// ✅ 全部结束,不管成功失败
const results = await Promise.allSettled([p1, p2]);
// [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]
串行执行异步函数
// 方法1:async/await + for...of(最推荐)
async function runSeries(fns) {
const results = [];
for (const fn of fns) {
results.push(await fn());
}
return results;
}
// 方法2:reduce 链式调用
const results = await fns.reduce(
(p, fn) => p.then(r => [...r, fn()]),
Promise.resolve([])
);
💡 async 函数里没 return 会自动返回
Promise.resolve(undefined),throw 则自动变成Promise.reject(error)。
9. apply / call / bind:this 到底指向谁
三个方法都是用来改变函数的 this 指向,区别在于参数形式和是否立即执行。
function sayHello(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: '小明' };
// call:立刻执行,参数用逗号分隔
sayHello.call(user, '你好', '!'); // 你好, 小明!
// apply:立刻执行,参数用数组
sayHello.apply(user, ['嗨', '?']); // 嗨, 小明?
// bind:返回新函数(this 已绑定),延迟执行
const fn = sayHello.bind(user, '哈喽');
fn('~'); // 哈喽, 小明~
常见用途
// 1. 借用数组方法给类数组对象
const nodeList = document.querySelectorAll('div');
const arr = [].slice.call(nodeList); // 转数组(老写法)
const arr2 = [...nodeList]; // ✅ 推荐
// 2. Math.max 接收多个参数,用 apply 传数组
Math.max.apply(null, [1, 5, 3]);
Math.max(...[1, 5, 3]); // ✅ 推荐
// 3. bind 做柯里化
const add = (a, b, c) => a + b + c;
const add5 = add.bind(null, 5);
add5(3, 2); // 10
手写一个 bind
Function.prototype.myBind = function (context, ...args) {
const self = this;
return function (...newArgs) {
return self.apply(context, [...args, ...newArgs]);
};
};
10. 解构 & 展开运算符:代码瞬间变优雅
ES6 提供的语法糖,让提取值、复制合并变得异常简洁。
数组解构
const [a, b, , d, e = '默认'] = [1, 2, 3, 4];
// a=1, b=2, d=4, e='默认'
// 快速交换变量(超实用!)
[a, b] = [b, a];
// 函数返回多个值
function getMinMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = getMinMax([3, 7, 2, 9]);
对象解构(重命名 + 默认值)
const user = { name: '小明', age: 20 };
// 重命名:原属性名 → 新变量名
const { name: userName, age: uAge = 18 } = user;
console.log(userName, uAge); // 小明 20
// 解构时排除某些属性
const { name, ...rest } = user;
// rest = { age: 20 }
// ⭐ 函数参数解构(最推荐!)
function createUser({ name = '匿名', age = 18, role = 'user' } = {}) {
return { name, age, role };
}
createUser({ name: '小红', age: 22 }); // 正常传参
createUser(); // 全用默认值
展开运算符的 5 种用途
// 1. 数组浅拷贝
const copy = [...arr];
// 2. 数组合并
const merged = [...arr1, ...arr2, ...arr3];
// 3. 对象合并(后者覆盖前者)
const mergedObj = { ...defaultConfig, ...userConfig };
// 4. 函数传参
const nums = [1, 2, 3];
sum(...nums);
// 5. 类数组转数组
const divs = [...document.querySelectorAll('div')];
Set / Map 也能解构吗?
const map = new Map([['a', 1], ['b', 2]]);
// Map 是可迭代对象,能解构
for (const [key, val] of map) { ... }
const [[k1, v1], [k2, v2]] = map; // 只解构前两个
const set = new Set([1, 2, 3]);
const [first, second] = set; // 1, 2
📝 小结
这 10 个知识点基本覆盖了前端面试中 JavaScript 进阶部分的高频考点,也是日常开发中非常实用的技巧:
| 分类 | 知识点 |
|---|---|
| 数据操作 | 深浅拷贝、reduce 高阶用法、一行代码系列 |
| 数组方法 | shift/unshift/splice、sort、slice vs splice |
| 函数进阶 | 闭包、apply/call/bind、防抖节流 |
| 异步编程 | Promise、async/await |
| 语法糖 | 解构赋值、展开运算符、eval |
📚 整理不易,觉得有用就点个收藏 + 关注吧! 有问题欢迎在评论区留言交流 🙌
本文系统梳理了JavaScript进阶中10个高频易忘的核心知识点:深浅拷贝、数组变异方法、reduce高阶应用、一行式技巧、eval安全使用、防抖与节流、闭包原理与陷阱、Promise链式与并发控制、this绑定三法(apply/call/bind)、解构与展开运算符。内容聚焦实战与面试,涵盖原理、陷阱、安全规范及现代API兼容方案。
&spm=1001.2101.3001.5002&articleId=126159653&d=1&t=3&u=c6839328f0ae482a9ec6a390d20146df)
2674

被折叠的 条评论
为什么被折叠?



