JavaScript 数据操作与优化面试题
1. 请解释 JavaScript 中的深拷贝和浅拷贝
Details
在 JavaScript 中,拷贝是指创建一个新对象,使其包含与原对象相同的值。根据拷贝的深度,可分为浅拷贝和深拷贝。
浅拷贝
浅拷贝是指创建一个新对象,新对象的属性值与原对象相同,但对于引用类型的属性,新对象和原对象共享同一个引用。
浅拷贝的方法
Object.assign():
javascriptconst obj = { a: 1, b: { c: 2 } }; const copy = Object.assign({}, obj); copy.b.c = 3; console.log(obj.b.c); // 输出: 3(原对象也被修改)展开运算符 (...):
javascriptconst obj = { a: 1, b: { c: 2 } }; const copy = { ...obj }; copy.b.c = 3; console.log(obj.b.c); // 输出: 3(原对象也被修改)Array.prototype.slice()(用于数组):
javascriptconst arr = [1, { a: 2 }]; const copy = arr.slice(); copy[1].a = 3; console.log(arr[1].a); // 输出: 3(原数组也被修改)Array.prototype.concat()(用于数组):
javascriptconst arr = [1, { a: 2 }]; const copy = [].concat(arr); copy[1].a = 3; console.log(arr[1].a); // 输出: 3(原数组也被修改)
深拷贝
深拷贝是指创建一个新对象,新对象的属性值与原对象相同,对于引用类型的属性,新对象会创建一个新的引用,而不是共享原对象的引用。
深拷贝的方法
JSON.parse(JSON.stringify()):
javascriptconst obj = { a: 1, b: { c: 2 } }; const copy = JSON.parse(JSON.stringify(obj)); copy.b.c = 3; console.log(obj.b.c); // 输出: 2(原对象未被修改)注意:这种方法有一些限制,比如不能处理函数、undefined、Symbol 等类型。
递归实现深拷贝:
javascriptfunction deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof Array) { return obj.map(item => deepClone(item)); } if (typeof obj === 'object') { const clonedObj = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key]); } } return clonedObj; } }使用第三方库:如 Lodash 的
_.cloneDeep()方法。
浅拷贝 vs 深拷贝
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 拷贝层级 | 只拷贝一层 | 拷贝所有层级 |
| 引用类型 | 共享引用 | 创建新引用 |
| 性能 | 更快 | 更慢 |
| 内存占用 | 更少 | 更多 |
应用场景
- 浅拷贝:适用于只包含基本数据类型的对象,或者不需要修改引用类型属性的情况。
- 深拷贝:适用于包含多层嵌套引用类型的对象,或者需要修改拷贝对象而不影响原对象的情况。
2. 请解释 JavaScript 中的防抖(Debounce)和节流(Throttle)
Details
防抖和节流是 JavaScript 中用于控制函数执行频率的两种技术,它们可以优化性能,特别是在处理频繁触发的事件时。
防抖(Debounce)
防抖是指在一段时间内,如果事件被多次触发,只执行最后一次触发的事件。
防抖的实现
javascript
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
}
}防抖的应用场景
- 搜索框输入:用户输入时,不需要每次按键都发送请求,而是等待用户停止输入一段时间后再发送。
- 窗口 resize:窗口大小改变时,不需要每次改变都执行 resize 事件处理函数,而是等待调整完成后再执行。
- 表单提交:防止用户重复点击提交按钮。
防抖的示例
javascript
const debouncedSearch = debounce((query) => {
console.log('Searching for:', query);
// 发送搜索请求
}, 300);
// 监听输入事件
document.getElementById('search').addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});节流(Throttle)
节流是指在一段时间内,无论事件被触发多少次,只执行一次事件。
节流的实现
javascript
function throttle(func, delay) {
let lastExecTime = 0;
return function(...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime >= delay) {
lastExecTime = currentTime;
func.apply(this, args);
}
}
}节流的应用场景
- 滚动事件:滚动时,不需要每次滚动都执行事件处理函数,而是每隔一段时间执行一次。
- 游戏中的动画:游戏中的动画更新不需要每一帧都执行,而是每隔一段时间执行一次。
- 鼠标移动事件:鼠标移动时,不需要每次移动都执行事件处理函数,而是每隔一段时间执行一次。
节流的示例
javascript
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
// 处理滚动事件
}, 100);
// 监听滚动事件
window.addEventListener('scroll', throttledScroll);防抖 vs 节流
| 特性 | 防抖 | 节流 |
|---|---|---|
| 执行时机 | 事件停止触发后延迟执行 | 每隔一段时间执行一次 |
| 执行次数 | 最后一次触发时执行一次 | 多次触发时每隔一段时间执行一次 |
| 应用场景 | 搜索输入、窗口 resize | 滚动事件、游戏动画 |
总结
防抖和节流都是用于优化性能的技术,它们可以减少函数的执行次数,提高应用的响应速度。选择使用哪种技术取决于具体的应用场景。