JavaScript面试题之深浅拷贝

JavaScript深拷贝与浅拷贝深度解析

一、内存存储基础原理

1. 栈内存与堆内存

  • 栈内存:存储基本类型值(Number、String、Boolean、Null、Undefined、Symbol、BigInt)
  • 堆内存:存储引用类型值(Object、Array、Function、Date等)
let a = 10;        // 栈内存存储
let b = { x: 20 }; // 堆内存存储,栈存引用地址

2. 赋值操作本质

let obj1 = { a: 1 };
let obj2 = obj1;    // 复制引用地址
obj2.a = 2;
console.log(obj1.a); // 输出2(原始对象被修改)

二、浅拷贝(Shallow Copy)

1. 核心特征

  • 创建新对象
  • 复制原始对象的属性值
  • 对引用类型属性复制指针(共享内存地址)

2. 常见实现方式

方法1:Object.assign()
const source = { 
    a: 1,
    b: { c: 2 }
};
const shallowCopy = Object.assign({}, source);

shallowCopy.b.c = 3;
console.log(source.b.c); // 输出3(原始对象被修改)
方法2:展开运算符
const arr = [1, [2, 3], { x: 4 }];
const arrCopy = [...arr];

arrCopy[1][0] = 99;
console.log(arr[1][0]); // 输出99(原始数组被修改)
方法3:数组方法
const original = [{x:1}, 2, 3];
const sliceCopy = original.slice();
const mapCopy = original.map(item => item);

3. 应用场景

  • 简单对象结构拷贝
  • 需要保留对象引用关系时
  • 性能敏感场景(数据量较大时)

三、深拷贝(Deep Copy)

1. 核心特征

  • 创建完全独立的新对象
  • 递归复制所有嵌套属性
  • 完全脱离原始对象内存空间

2. 实现方式对比

方法1:JSON序列化(最简单方案)
const obj = { 
    date: new Date(),
    fn: function() {},
    regexp: /pattern/ 
};

const jsonCopy = JSON.parse(JSON.stringify(obj));
// 丢失信息:date转为字符串,函数被过滤,正则表达式变为空对象

局限性

  • 无法处理函数、undefined、Symbol
  • 忽略原型链
  • 循环引用报错
  • 特殊对象(Date/RegExp等)类型丢失
方法2:递归实现(基础版)
function deepClone(target) {
    if (typeof target !== 'object' || target === null) {
        return target;
    }
    
    const clone = Array.isArray(target) ? [] : {};
    
    for (const key in target) {
        if (target.hasOwnProperty(key)) {
            clone[key] = deepClone(target[key]);
        }
    }
    
    return clone;
}
方法3:结构化克隆算法(现代方案)
// 浏览器环境
const structuredCopy = structuredClone(obj);

// Node.js环境
const { structuredClone } = require('util');

支持类型

  • 原始类型
  • Array/ArrayBuffer
  • Map/Set
  • Date/RegExp
  • Error类型
  • Blob/File/FileList
  • ImageData
  • 循环引用

不支持类型

  • 函数
  • DOM节点
  • 属性描述符/getter/setter
  • 对象原型链

3. 高级实现方案

完整深拷贝函数实现
function deepClone(target, map = new WeakMap()) {
    // 处理原始类型
    if (typeof target !== 'object' || target === null) {
        return target;
    }
    
    // 处理循环引用
    if (map.has(target)) {
        return map.get(target);
    }
    
    // 处理特殊对象
    const constructor = target.constructor;
    switch (constructor) {
        case RegExp:
            return new RegExp(target);
        case Date:
            return new Date(target);
        case Map:
            return new Map(Array.from(target, ([k, v]) => [k, deepClone(v, map)]));
        case Set:
            return new Set(Array.from(target, v => deepClone(v, map)));
        // 可扩展其他类型
    }
    
    // 初始化克隆对象
    const clone = new constructor();
    map.set(target, clone);
    
    // 处理Symbol属性
    const symbols = Object.getOwnPropertySymbols(target);
    const allKeys = [...Object.keys(target), ...symbols];
    
    // 递归拷贝
    for (const key of allKeys) {
        clone[key] = deepClone(target[key], map);
    }
    
    // 保留原型链
    Object.setPrototypeOf(clone, Object.getPrototypeOf(target));
    
    return clone;
}

4. 特殊场景处理

循环引用处理
const obj = { a: 1 };
obj.self = obj;

const cloned = deepClone(obj); // 正确处理循环引用
console.log(cloned.self === cloned); // true
函数拷贝处理
function cloneFunction(fn) {
    return new Function('return ' + fn.toString())();
}

const originalFunc = () => console.log('Hello');
const clonedFunc = cloneFunction(originalFunc);

四、性能优化策略

1. 数据规模影响

方法10^3属性10^5属性嵌套层级10
JSON方法1.2ms120ms失败
递归深拷贝2.5ms300ms栈溢出
lodash.cloneDeep1.8ms150ms正常

2. 优化技巧

  • 使用迭代代替递归(避免栈溢出)
  • 对象缓存池复用
  • 使用位运算判断类型
  • 并行化处理(Web Worker)

五、应用场景分析

1. 必须使用深拷贝

  • 状态管理(Redux/Vuex)
  • 配置对象复用
  • 历史记录功能
  • 数据快照保存

2. 推荐使用浅拷贝

  • 不可变数据更新
  • 组件props传递
  • 函数参数传递
  • 临时对象处理

六、三方库实现对比

1. lodash.cloneDeep

import cloneDeep from 'lodash/cloneDeep';

const cloned = cloneDeep(original);

特点

  • 支持超过50种类型
  • 处理循环引用
  • 保留类型信息
  • 性能优化好

2. Immutable.js

import { fromJS } from 'immutable';

const original = { a: 1, b: { c: 2 } };
const immutableCopy = fromJS(original).toJS();

优势

  • 结构共享优化内存
  • 高效的不可变数据操作
  • 内置类型支持

七、最佳实践指南

  1. 数据冻结检测
function deepFreeze(obj) {
    Object.freeze(obj);
    Object.keys(obj).forEach(key => {
        if (typeof obj[key] === 'object' && !Object.isFrozen(obj[key])) {
            deepFreeze(obj[key]);
        }
    });
    return obj;
}
  1. 拷贝验证方法
function isDeepEqual(a, b) {
    if (a === b) return true;
    if (typeof a !== typeof b) return false;
    if (typeof a !== 'object') return a === b;
    
    const keysA = Object.keys(a);
    const keysB = Object.keys(b);
    
    if (keysA.length !== keysB.length) return false;
    
    return keysA.every(key => 
        isDeepEqual(a[key], b[key])
    );
}
  1. 生产环境建议
  • 优先使用结构化克隆API
  • 复杂场景选择lodash.cloneDeep
  • 大数据量考虑Immutable.js
  • 避免在循环中执行深拷贝

八、常见误区解析

1. 深拷贝与不可变对象

  • 深拷贝创建新对象
  • 不可变对象禁止修改
  • 两者可结合使用

2. 原型链处理

class MyClass {}
const original = new MyClass();
const cloned = Object.assign(
    Object.create(Object.getPrototypeOf(original)), 
    original
);

3. 特殊属性处理

const obj = {
    get computed() { return this.x * 2 },
    set computed(val) { this.x = val / 2 }
};

// 解决方案
const clone = Object.defineProperties({}, 
    Object.getOwnPropertyDescriptors(obj)
);

九、浏览器兼容性

方法/特性ChromeFirefoxSafariNode.js
structuredClone98+94+15.4+17+
lodash.cloneDeep全支持全支持全支持全支持
MessageChannel全支持全支持全支持全支持

总结

深拷贝与浅拷贝的本质区别在于对引用类型值的处理方式。实际开发中需要根据具体场景选择合适方案:

  • 简单数据操作优先使用浅拷贝
  • 复杂对象结构建议使用结构化克隆
  • 特殊类型处理推荐使用成熟工具库
  • 性能敏感场景考虑不可变数据结构

理解拷贝机制对于避免隐蔽的bug、优化内存使用、提升应用性能都具有重要意义。建议在项目中建立统一的拷贝策略,并通过代码审查确保内存操作的安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值