Javascript/ES6+/Typescript重点内容篇——手撕(待总结)
前端核心知识点梳理与面试题详解
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 都成功时才 resolveif (completedCount === total) {resolve(results);}},(reason) => {// 有一个 Promise 失败就立即 rejectreject(reason);});});});
};
面试题:实现 Promise 串行执行
// 实现一个函数,让多个Promise按顺序执行
function promiseSerial(tasks) {// 初始返回一个已 resolved 的 Promisereturn 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) {// 基本类型直接返回falseif (typeof left !== 'object' || left === null) return false;// 获取对象的原型let proto = Object.getPrototypeOf(left);// 遍历原型链while (true) {// 找到尽头仍未匹配,返回falseif (proto === null) return false;// 找到匹配的原型,返回trueif (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;}// 处理Promiseresult.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;}// 清空Mapclear() {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)); // truefor (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。