前端核心知识点梳理与面试题详解
1. Promise
核心知识点
- Promise 是异步编程的解决方案,用于处理异步操作
- 三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
- 状态一旦改变就不会再变,从 pending 到 fulfilled 或 rejected
- 常用方法:then()、catch()、finally()、all()、race()、allSettled() 等
面试题:实现 Promise.all
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
// 判断传入的是否是可迭代对象
if (!Array.isArray(promises)) {
return reject(new TypeError('The input must be an array'));
}
const results = [];
let completedCount = 0;
const total = promises.length;
if (total === 0) {
return resolve(results);
}
promises.forEach((promise, index) => {
// 确保每个元素都是 Promise 对象
Promise.resolve(promise).then(
(value) => {
results[index] = value;
completedCount++;
// 所有 Promise 都成功时才 resolve
if (completedCount === total) {
resolve(results);
}
},
(reason) => {
// 有一个 Promise 失败就立即 reject
reject(reason);
}
);
});
});
};
面试题:实现 Promise 串行执行
// 实现一个函数,让多个Promise按顺序执行
function promiseSerial(tasks) {
// 初始返回一个已 resolved 的 Promise
return tasks.reduce((prev, current) => {
return prev.then((results) => {
// 等待当前任务执行完成
return current().then((result) => {
// 将结果添加到数组中
return [...results, result];
});
});
}, Promise.resolve([]));
}
// 使用示例
const createTask = (time, value) => {
return () => new Promise(resolve => {
setTimeout(() => {
console.log(value);
resolve(value);
}, time);
});
};
const tasks = [
createTask(1000, '任务1'),
createTask(500, '任务2'),
createTask(800, '任务3')
];
promiseSerial(tasks).then(results => {
console.log('所有任务完成:', results);
});
2. 原型链
核心知识点
- 每个对象都有
__proto__
属性,指向其构造函数的 prototype - 构造函数有 prototype 属性,是其实例的原型
- 原型链是由
__proto__
连接而成的链式结构 - 当访问对象的属性或方法时,会先在自身查找,找不到则沿原型链向上查找
Object.prototype
是原型链的终点,其__proto__
为 null
面试题:原型链相关输出题
function Foo() {
getName = function() { console.log(1); };
return this;
}
Foo.getName = function() { console.log(2); };
Foo.prototype.getName = function() { console.log(3); };
var getName = function() { console.log(4); };
function getName() { console.log(5); }
// 以下输出结果是什么?
Foo.getName(); // 2 - 访问Foo的静态方法
getName(); // 4 - 函数声明提升后被变量声明覆盖
Foo().getName(); // 1 - Foo()执行后修改了全局getName
getName(); // 1 - 已经被Foo()修改
new Foo.getName(); // 2 - 优先级问题,相当于new (Foo.getName)()
new Foo().getName(); // 3 - 实例化后访问原型上的方法
new new Foo().getName();// 3 - 先实例化Foo,再调用其原型上的getName并实例化
面试题:实现 instanceof
function myInstanceof(left, right) {
// 基本类型直接返回false
if (typeof left !== 'object' || left === null) return false;
// 获取对象的原型
let proto = Object.getPrototypeOf(left);
// 遍历原型链
while (true) {
// 找到尽头仍未匹配,返回false
if (proto === null) return false;
// 找到匹配的原型,返回true
if (proto === right.prototype) return true;
// 继续向上查找
proto = Object.getPrototypeOf(proto);
}
}
// 测试
function Person() {}
const p = new Person();
console.log(myInstanceof(p, Person)); // true
console.log(myInstanceof(p, Object)); // true
console.log(myInstanceof(p, Array)); // false
console.log(myInstanceof([], Array)); // true
3. 生成器(Generator)
核心知识点
- 生成器函数使用
function*
声明,内部使用yield
关键字 - 调用生成器函数返回迭代器对象,而非直接执行函数体
- 通过迭代器的
next()
方法控制函数执行,每次遇到yield
暂停 next()
方法返回包含value
和done
属性的对象- 可用于实现异步操作的同步化表达、自定义迭代器等
面试题:使用 Generator 实现异步操作
// 模拟异步操作
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`数据: ${url}`);
}, 1000);
});
}
// 使用Generator处理异步
function* getData() {
console.log('开始请求数据1');
const data1 = yield fetchData('url1');
console.log('获取到数据1:', data1);
console.log('开始请求数据2');
const data2 = yield fetchData('url2');
console.log('获取到数据2:', data2);
return '所有数据获取完成';
}
// 执行生成器
function runGenerator(gen) {
const iterator = gen();
function handleResult(result) {
if (result.done) {
console.log('最终结果:', result.value);
return;
}
// 处理Promise
result.value.then(data => {
handleResult(iterator.next(data));
});
}
handleResult(iterator.next());
}
// 运行
runGenerator(getData);
4. 闭包
核心知识点
- 闭包是函数及其捆绑的周边环境状态的引用的组合
- 形成条件:函数嵌套、内部函数引用外部函数的变量、内部函数被外部引用
- 作用:实现私有变量、模块化、柯里化、保存状态等
- 注意:不当使用可能导致内存泄漏
面试题:实现防抖函数
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoked = false;
// 返回闭包函数
return function(...args) {
const context = this;
// 如果存在定时器,清除它
if (timer) {
clearTimeout(timer);
}
// 立即执行的情况
if (immediate && !isInvoked) {
fn.apply(context, args);
isInvoked = true;
} else {
// 延迟执行
timer = setTimeout(() => {
fn.apply(context, args);
isInvoked = false;
timer = null;
}, delay);
}
};
}
// 使用示例
const handleSearch = (keyword) => {
console.log('搜索:', keyword);
};
// 防抖处理,延迟500ms执行,不立即执行
const debouncedSearch = debounce(handleSearch, 500);
// 模拟频繁调用
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc'); // 只有最后一次会在500ms后执行
面试题:实现节流函数
function throttle(fn, interval) {
let lastTime = 0;
let timer = null;
return function(...args) {
const context = this;
const now = Date.now();
const remainingTime = interval - (now - lastTime);
// 如果时间间隔已到,直接执行
if (remainingTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(context, args);
lastTime = now;
} else if (!timer) {
// 否则设置定时器,确保最后一次一定会执行
timer = setTimeout(() => {
fn.apply(context, args);
lastTime = Date.now();
timer = null;
}, remainingTime);
}
};
}
// 使用示例
const handleScroll = () => {
console.log('滚动事件触发');
};
// 节流处理,每100ms最多执行一次
const throttledScroll = throttle(handleScroll, 100);
// 监听滚动事件
window.addEventListener('scroll', throttledScroll);
5. 异步与事件循环
核心知识点
- JavaScript 是单线程语言,通过事件循环实现异步
- 事件循环:调用栈 -> 微任务队列 -> 宏任务队列 -> UI渲染
- 微任务优先级高于宏任务,常见微任务:Promise.then/catch/finally、process.nextTick、MutationObserver
- 常见宏任务:setTimeout、setInterval、DOM事件、I/O操作、setImmediate
面试题:事件循环输出题
console.log('1');
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4');
});
}, 0);
new Promise(function(resolve) {
console.log('5');
resolve();
}).then(function() {
console.log('6');
});
setTimeout(function() {
console.log('7');
new Promise(function(resolve) {
console.log('8');
resolve();
}).then(function() {
console.log('9');
});
}, 0);
console.log('10');
// 输出顺序:1 5 10 6 2 3 4 7 8 9
6. Map
核心知识点
- Map 是 ES6 新增的键值对集合
- 与对象相比,Map 的键可以是任意类型,而对象的键只能是字符串或 Symbol
- 常用方法:set()、get()、has()、delete()、clear()、size 属性
- 可以通过 for…of 直接迭代,迭代顺序是插入顺序
- 适合用于频繁添加/删除键值对的场景
面试题:实现 Map 的基本功能
class MyMap {
constructor() {
// 存储键值对的数组
this.items = [];
}
// 向Map中添加元素
set(key, value) {
// 检查键是否已存在
const index = this.items.findIndex(item => this.isEqual(item.key, key));
if (index !== -1) {
// 键存在则更新值
this.items[index].value = value;
} else {
// 键不存在则添加新键值对
this.items.push({ key, value });
}
return this;
}
// 获取指定键的值
get(key) {
const item = this.items.find(item => this.isEqual(item.key, key));
return item ? item.value : undefined;
}
// 检查是否包含指定键
has(key) {
return this.items.some(item => this.isEqual(item.key, key));
}
// 删除指定键
delete(key) {
const index = this.items.findIndex(item => this.isEqual(item.key, key));
if (index !== -1) {
this.items.splice(index, 1);
return true;
}
return false;
}
// 清空Map
clear() {
this.items = [];
}
// 获取Map的大小
get size() {
return this.items.length;
}
// 判断两个键是否相等
isEqual(a, b) {
// 处理NaN的情况,NaN !== NaN但在Map中视为相等
if (a !== a && b !== b) return true;
// 处理+0和-0的情况,在Map中视为相等
if (a === 0 && b === 0) return 1 / a === 1 / b;
return a === b;
}
// 迭代器,用于for...of循环
*[Symbol.iterator]() {
for (const item of this.items) {
yield [item.key, item.value];
}
}
}
// 使用示例
const map = new MyMap();
map.set('name', '张三');
map.set(1, '数字1');
map.set(NaN, 'NaN值');
console.log(map.get('name')); // 张三
console.log(map.size); // 3
console.log(map.has(NaN)); // true
for (const [key, value] of map) {
console.log(key, value);
}
7. 数组
核心知识点
- 数组是有序的元素集合,具有动态长度
- 常用方法:push()、pop()、shift()、unshift()、slice()、splice() 等
- 高阶函数:map()、filter()、reduce()、forEach()、find()、some()、every() 等
- 数组去重、扁平化、排序是常见操作
面试题:数组扁平化
// 方法1:使用递归
function flatten(arr, depth = Infinity) {
if (depth <= 0) return arr.slice();
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
// 递归扁平化,并减少深度
return acc.concat(flatten(item, depth - 1));
} else {
return acc.concat(item);
}
}, []);
}
// 方法2:使用ES6的flat方法
// const flattened = arr.flat(depth);
// 测试
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArray)); // [1, 2, 3, 4, 5]
console.log(flatten(nestedArray, 1)); // [1, 2, [3, [4]], 5]
面试题:数组去重
// 方法1:使用Set
function unique1(arr) {
return [...new Set(arr)];
}
// 方法2:使用filter和indexOf
function unique2(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
}
// 方法3:使用对象键值对
function unique3(arr) {
const obj = {};
return arr.filter(item => {
// 处理不同类型但值相同的情况,如1和'1'
const key = typeof item + item;
if (!obj[key]) {
obj[key] = true;
return true;
}
return false;
});
}
// 方法4:使用Map
function unique4(arr) {
const map = new Map();
return arr.filter(item => {
if (!map.has(item)) {
map.set(item, true);
return true;
}
return false;
});
}
// 测试
const testArray = [1, 2, 2, '3', '3', true, true, false, false, null, null, undefined, undefined, NaN, NaN];
console.log(unique1(testArray)); // Set方法能正确去重NaN
console.log(unique2(testArray)); // indexOf无法识别NaN,会保留重复的NaN
console.log(unique3(testArray)); // 能区分不同类型的值
console.log(unique4(testArray)); // Map方法也能正确去重NaN
以上梳理了前端核心知识点及常见面试题,涵盖了Promise、原型链、生成器、闭包、异步、事件循环、Map和数组等内容。这些知识点不仅是面试高频考点,也是日常开发中经常用到的核心概念,掌握这些内容对于前端工程师至关重要。
前端对象与字符串知识点梳理及面试题详解
一、对象(Object)
核心知识点
- 对象是键值对的集合,键可以是字符串或Symbol,值可以是任意类型
- 对象的创建方式:对象字面量
{}
、new Object()
、构造函数、Object.create()
等 - 对象属性的访问方式:点语法(
obj.key
)和方括号语法(obj['key']
) - 可枚举属性与不可枚举属性:可枚举属性会被
for...in
遍历到 - 对象的特性:
writable
(可写)、enumerable
(可枚举)、configurable
(可配置) - 常见方法:
Object.keys()
、Object.values()
、Object.entries()
、Object.assign()
等
面试题:实现对象的深拷贝
function deepClone(obj, hash = new WeakMap()) {
// 处理null和基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理日期
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 区分数组和对象
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// 遍历属性(包括Symbol键)
Reflect.ownKeys(obj).forEach(key => {
cloneObj[key] = deepClone(obj[key], hash);
});
return cloneObj;
}
// 测试
const obj = {
a: 1,
b: { c: 2 },
d: [3, 4],
e: new Date(),
f: /test/,
[Symbol('g')]: 5
};
obj.self = obj; // 循环引用
const cloned = deepClone(obj);
console.log(cloned);
面试题:实现对象的扁平化解构
function flattenObject(obj, prefix = '', result = {}) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
// 递归处理嵌套对象
flattenObject(value, newKey, result);
} else {
result[newKey] = value;
}
}
}
return result;
}
// 测试
const nestedObj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: 4
}
},
g: 5
};
console.log(flattenObject(nestedObj));
// 输出: { a: 1, 'b.c': 2, 'b.d.e': 3, 'b.d.f': 4, g: 5 }
面试题:实现对象的属性拦截(类似Vue2的响应式原理)
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return;
}
// 遍历对象属性
Object.keys(obj).forEach(key => {
let value = obj[key];
// 递归处理嵌套对象
observe(value);
// 重定义属性
Object.defineProperty(obj, key, {
get() {
console.log(`获取属性${key}的值: ${value}`);
return value;
},
set(newValue) {
console.log(`设置属性${key}的值: ${newValue}`);
// 处理新值为对象的情况
observe(newValue);
value = newValue;
}
});
});
}
// 测试
const data = {
name: '张三',
age: 20,
address: {
city: '北京'
}
};
observe(data);
data.name; // 触发get
data.age = 21; // 触发set
data.address.city = '上海'; // 触发get和set
二、字符串(String)
核心知识点
- 字符串是字符的有序序列,在JavaScript中是不可变的
- 常见创建方式:字符串字面量(
''
或""
)、模板字符串(` `
)、new String()
- 常用属性:
length
(长度) - 常用方法:
- 查找:
indexOf()
、lastIndexOf()
、includes()
、startsWith()
、endsWith()
- 截取:
slice()
、substring()
、substr()
- 转换:
toUpperCase()
、toLowerCase()
、trim()
- 其他:
split()
、replace()
、charAt()
、concat()
- 查找:
- 模板字符串支持多行文本和变量插值(
${}
)
面试题:实现字符串反转
// 方法1:使用数组方法
function reverseString1(str) {
return str.split('').reverse().join('');
}
// 方法2:使用for循环
function reverseString2(str) {
let result = '';
for (let i = str.length - 1; i >= 0; i--) {
result += str[i];
}
return result;
}
// 方法3:使用递归
function reverseString3(str) {
if (str === '') {
return '';
} else {
return reverseString3(str.substr(1)) + str.charAt(0);
}
}
// 测试
console.log(reverseString1('hello')); // 'olleh'
console.log(reverseString2('world')); // 'dlrow'
console.log(reverseString3('test')); // 'tset'
面试题:实现字符串中的单词反转(不改变单词顺序)
function reverseWords(str) {
// 分割单词(处理多个空格的情况)
const words = str.split(/\s+/);
// 反转每个单词
const reversedWords = words.map(word => {
return word.split('').reverse().join('');
});
// 拼接回字符串
return reversedWords.join(' ');
}
// 测试
console.log(reverseWords('Hello World')); // 'olleH dlroW'
console.log(reverseWords('I love JavaScript')); // 'I evol tpircSavaJ'
console.log(reverseWords(' Hello there ')); // ' olleH ereht '
面试题:实现千位分隔符格式化数字
function formatNumber(num) {
// 处理非数字情况
if (typeof num !== 'number' || isNaN(num)) {
return '0';
}
// 转换为字符串并分割整数和小数部分
const parts = num.toString().split('.');
let integerPart = parts[0];
const decimalPart = parts[1] || '';
// 处理负数
let sign = '';
if (integerPart[0] === '-') {
sign = '-';
integerPart = integerPart.slice(1);
}
// 反转字符串便于处理
let reversed = integerPart.split('').reverse().join('');
let formatted = '';
// 每三位添加一个逗号
for (let i = 0; i < reversed.length; i++) {
if (i !== 0 && i % 3 === 0) {
formatted += ',';
}
formatted += reversed[i];
}
// 反转回来并拼接符号和小数部分
formatted = sign + formatted.split('').reverse().join('');
return decimalPart ? `${formatted}.${decimalPart}` : formatted;
}
// 测试
console.log(formatNumber(1234567)); // '1,234,567'
console.log(formatNumber(1234567.89)); // '1,234,567.89'
console.log(formatNumber(-123456)); // '-123,456'
console.log(formatNumber(0)); // '0'
面试题:实现字符串匹配算法(KMP算法)
// 构建部分匹配表
function buildLPS(pattern) {
const lps = new Array(pattern.length).fill(0);
let len = 0; // 最长前缀后缀的长度
for (let i = 1; i < pattern.length; ) {
if (pattern[i] === pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len !== 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
// KMP匹配算法
function kmpSearch(text, pattern) {
const n = text.length;
const m = pattern.length;
if (m === 0) return 0; // 空模式匹配开头
const lps = buildLPS(pattern);
let i = 0; // text的索引
let j = 0; // pattern的索引
while (i < n) {
if (pattern[j] === text[i]) {
i++;
j++;
if (j === m) {
return i - j; // 找到匹配,返回起始索引
}
} else {
if (j !== 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return -1; // 未找到匹配
}
// 测试
console.log(kmpSearch('ABC ABCDAB ABCDABCDABDE', 'ABCDABD')); // 15
console.log(kmpSearch('hello world', 'world')); // 6
console.log(kmpSearch('test string', 'xyz')); // -1
对象和字符串是JavaScript中最基础也是最常用的数据类型,掌握它们的特性和操作方法对于前端开发至关重要。上述知识点和面试题覆盖了常见的考点和实际开发需求,深入理解这些内容有助于提升代码质量和解决问题的能力。
以下是对前端核心知识点(Promise、原型链、闭包、异步、事件循环、Map、数组)的系统梳理及高频手写题目详解,结合面试考点和实现细节,助你高效备战。
一、核心知识梳理与面试详解
⚡ 1. Promise与异步编程
- 核心机制:
- 三种状态:
Pending
(等待)、Fulfilled
(成功)、Rejected
(失败),状态不可逆。 - 链式调用:
.then()
处理成功状态,.catch()
捕获失败,.finally()
无论成败均执行。
- 三种状态:
- 手写
Promise.all
:
得分点:function myPromiseAll(promises) { return new Promise((resolve, reject) => { // 校验输入是否为可迭代对象 if (typeof promises?.[Symbol.iterator] !== 'function') { reject(new TypeError('Argument is not iterable')); return; } const promiseArray = Array.from(promises); const results = new Array(promiseArray.length); let completedCount = 0; // 空数组直接返回 if (promiseArray.length === 0) resolve(results); promiseArray.forEach((p, i) => { Promise.resolve(p) // 包装非Promise值 .then(res => { results[i] = res; // 按索引存储结果 if (++completedCount === promiseArray.length) resolve(results); }) .catch(err => reject(err)); // 任一失败立即终止 }); }); }
- 校验可迭代对象、处理空数组、非Promise包装。
- 结果顺序与输入一致(避免
push
导致错乱)。
🔗 2. 原型链与继承
- 核心概念:
- 原型(Prototype):构造函数(如
Array
)的prototype
属性,存放共享方法(如Array.prototype.map
)。 - 原型链:对象通过
__proto__
向上查找属性,终点为Object.prototype.__proto__ === null
。
- 原型(Prototype):构造函数(如
- 继承实现:
面试重点:// 寄生组合继承(最优解) function Parent(name) { this.name = name; } Parent.prototype.say = function() { console.log(this.name); }; function Child(name, age) { Parent.call(this, name); // 继承实例属性 this.age = age; } Child.prototype = Object.create(Parent.prototype); // 继承方法 Child.prototype.constructor = Child; // 修复构造函数指向
- 避免组合继承的两次调用父类构造函数问题。
- ES6的
class
本质是语法糖(如super()
调用父类构造)。
🔒 3. 闭包与生成器
- 闭包(Closure):
- 原理:函数嵌套,内层函数访问外层作用域的变量(即使外层已销毁)。
- 应用:
优势:状态隔离(多个生成器互不干扰)。// 自增ID生成器 function createIdGenerator(init = 0) { let id = init; return () => ++id; // 闭包保存id状态 } const gen = createIdGenerator(); gen(); // 1, gen(); // 2
- 生成器(Generator):
- 特性:
function*
定义,yield
暂停执行,next()
恢复执行。 - 异步应用:配合
co
库实现类似async/await
的异步控制(已逐渐被替代)。
- 特性:
🔁 4. 事件循环(Event Loop)
- 执行顺序:
- 同步代码 → 微任务(
Promise.then
、MutationObserver
) → 宏任务(setTimeout
、DOM事件
)。
- 同步代码 → 微任务(
- 经典面试题:
原理:微任务优先级高于宏任务,同步代码执行完毕后清空微任务队列。console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4'); // 输出:1 → 4 → 3 → 2
🗺️ 5. Map与数组高频操作
Map vs. Object:
- Map优势:键可为任意类型(对象、函数)、有序迭代、性能更优(频繁增删场景)。
- API:
set()
,get()
,has()
,delete()
。
数组重点方法:
方法 用途 是否改变原数组 map()
映射新数组 ❌ filter()
过滤满足条件的元素 ❌ reduce()
累计计算(如求和、扁平化) ❌ splice()
删除/插入元素 ✅ slice()
截取子数组 ❌ 手写数组扁平化:
function flatten(arr) { return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []); } flatten([1, [2, [3]]]); // [1, 2, 3]
二、高频手撕题目详解
1. 防抖(Debounce)与节流(Throttle)
- 防抖:连续触发时只执行最后一次:
function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } // 场景:搜索框输入联想(停止输入300ms后请求)
- 节流:固定间隔执行一次:
function throttle(fn, interval) { let lastTime = 0; return (...args) => { const now = Date.now(); if (now - lastTime >= interval) { fn.apply(this, args); lastTime = now; } }; } // 场景:滚动事件(每200ms计算位置)
2. 手写深拷贝
function deepClone(obj, map = new Map()) {
if (typeof obj !== 'object' || obj === null) return obj;
if (map.has(obj)) return map.get(obj); // 解决循环引用
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
边界处理:循环引用(Map
记录已拷贝对象)、函数和正则等特殊类型。
三、面试策略与复习建议
- 手写题要点:
- 先口述思路(如防抖的“清除定时器+延迟执行”),再写代码。
- 处理边界:空输入、非预期类型(如
Promise.all
校验可迭代对象)。
- 框架结合:
- React:闭包在
useCallback
中的应用(依赖项变化才更新函数)。 - Vue:响应式原理依赖
Object.defineProperty
的getter/setter
(类闭包状态保存)。
- React:闭包在
- 性能优化:
- 减少闭包滥用(避免内存泄漏),如事件监听后及时移除。
- 数组操作优先
map
/filter
(返回新数组,避免forEach
副作用)。
本文完整题目及扩展解析参考:https://blog.csdn.net/weixin_48377020/article/details/132241816 | https://blog.csdn.net/T_iAn_T/article/details/145597660。
在 JavaScript 中,对象(引用类型)和字符串(基本类型)的拷贝机制存在本质差异,这直接影响开发中的数据处理逻辑。以下是关键知识点梳理和对比:
📦 一、对象的拷贝
1. 浅拷贝(Shallow Copy)
- 特点:仅复制对象的第一层属性。嵌套对象仍共享内存地址。
- 实现方式:
Object.assign({}, obj)
- 展开运算符
{ ...obj }
- 数组方法如
slice()
、concat()
- 示例:
const obj = { a: 1, b: { c: 2 } }; const shallowCopy = { ...obj }; shallowCopy.b.c = 3; // 修改嵌套属性 console.log(obj.b.c); // 3(原对象被影响)
2. 深拷贝(Deep Copy)
- 特点:递归复制所有层级,新旧对象完全独立。
- 实现方式:
JSON.parse(JSON.stringify(obj))
- ✅ 简单快捷
- ❌ 无法处理函数、
undefined
、Symbol
、Date
(转为字符串)、循环引用
- 递归实现:
function deepClone(obj, hash = new WeakMap()) { if (typeof obj !== 'object' || obj === null) return obj; if (hash.has(obj)) return hash.get(obj); // 解决循环引用 const clone = Array.isArray(obj) ? [] : {}; hash.set(obj, clone); for (const key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key], hash); } } return clone; }
- 特殊类型处理:
Date
→new Date(obj.getTime())
RegExp
→new RegExp(obj)
Map
/Set
→ 递归复制元素
- 现代 API:
structuredClone()
- ✅ 支持循环引用、
Date
、Map
、Set
等 - ❌ 不支持函数、DOM 节点
- ✅ 支持循环引用、
- 第三方库:
_.cloneDeep(obj)
(Lodash)
🔤 二、字符串的拷贝
- 字符串是基本类型(Primitive Type),赋值时直接复制值,而非引用地址。
- 示例:
const str1 = "hello"; const str2 = str1; // 复制值 str2 = "world"; // 修改不影响 str1 console.log(str1); // "hello"
- 特点:
- 无需深拷贝/浅拷贝概念,每次赋值均创建独立副本。
- 操作(如拼接、切片)均返回新字符串,原字符串不变。
⚠️ 三、关键差异总结
特性 | 对象(引用类型) | 字符串(基本类型) |
---|---|---|
拷贝机制 | 赋值传递引用地址 | 赋值直接复制值 |
修改影响 | 浅拷贝时嵌套属性互相影响 | 修改后原数据不变 |
深拷贝需求 | 必需(需递归处理嵌套引用) | 无需 |
内存占用 | 浅拷贝节省内存,深拷贝开销大 | 每次修改均创建新副本 |
💡 四、实际应用建议
- 对象拷贝场景:
- 简单数据且无特殊类型 →
JSON.parse(JSON.stringify())
- 复杂对象(含循环引用、
Map
等)→structuredClone()
或 Lodash 的_.cloneDeep()
- 简单数据且无特殊类型 →
- 字符串操作:
- 直接赋值或使用
slice()
、substring()
等返回新字符串的方法。
- 直接赋值或使用
- 性能优化:
- 避免对大对象频繁深拷贝,改用 不可变数据(Immutable.js) 或 按需拷贝。
完整实现代码及边界案例可参考:https://blog.csdn.net/qq_53353440/article/details/148548048、https://developer.mozilla.org/en-US/docs/Web/API/structuredClone。