「JavaScript深入」深拷贝与浅拷贝,如何手写实现?

  • 浅拷贝:浅拷贝得到的对象会受原对象的影响(同样可以影响原对象)
  • 深拷贝:深拷贝得到的对象不会受到原对象影响

浅拷贝

实现

  • Object.assign
  • Array.prototype.slice()Array.prototype.concat()
  • 使用拓展运算符实现的赋值

手写浅拷贝

javascript 复制代码
// 实现一个浅拷贝
function shallowClone(obj) {
  const newObj = {};
  for(let key in obj) {
    if(obj.hasOwnProperty(key)){
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

深拷贝

JSON.parse(JSON.stringify(obj))

  • JSON.stringify() 将对象序列化成 json 对象(转换成JSON字符串)
  • JSON.parse()json 对象反序列化成 js 对象(解析成对象)
javascript 复制代码
// 使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝
const obj = { name: '高富帅', age: 20 }
const newObj = JSON.parse(JSON.stringify(obj))
const arr = [1, '1', null, true, undefined]
const newArr = JSON.parse(JSON.stringify(arr))
arr[1] = 'a'
obj.name = 'song'
console.log(newObj); // { name: '高富帅', age: 20 }
console.log(newArr); // [1, '1', null, true, undefined]

这个方法其实已经能适用于大部分的应用场景,毕竟很少有情况需要去拷贝一个函数什么的

🤔 但这里面的确有一些问题,是 JSON.stringify() 这个方法本身的实现逻辑带来的,该方法有下面特点:

  • 布尔值、数值、字符串对应的包装对象,在序列化过程会自动转换成其原始值。
  • undefined任意函数Symbol 值,在序列化过程有两种不同的情况。若出现在非数组对象的属性值中,会被忽略;若出现在数组中,会转换成 null
  • 任意函数undefined 被单独转换时,会返回 undefined
  • 所有以 Symbol 为属性键的属性都会被完全忽略,即便在该方法第二个参数 replacer 中指定了该属性。
  • Date 日期调用了其内置的 toJSON() 方法转换成字符串,因此会被当成字符串处理。
  • NaNInfinity 的数值及 null 都会当做 null
  • 这些对象 MapSetWeakMapWeakSet 仅会序列化可枚举的属性。
  • 被转换值如果含有 toJSON() 方法,该方法定义什么值将被序列化。
  • 对包含 循环引用 的对象进行序列化,会抛出错误。

🔗 实现深拷贝总结------ JSON.parse(JSON.stringify(obj))

边界

上面已经说明了 JSON.stringify() 还有很多情况不能处理,虽然我们不能一下记住这些情况,但至少说明了如果要自己实现一个深拷贝的方法,还有很多边界问题要处理

  • 常见的边界Case

    主要有循环引用,包装对象、函数、原型链、不可枚举属性、Map/WeakMapSet/WeakSetRegExpSymbolDateArrayBuffer、原生 DOM/BOM 对象等

  • 第三方库 目前而言最完善的深拷贝方法是 Lodash 库的 _.cloneDeep(value) 方法

    🔗 Lodash中文文档 ------ _.cloneDeep(value)

手写深拷贝

手写一个简易的深拷贝:

javascript 复制代码
const deepCopy = (source) => {
  //判断是否为arr
  const isArray = (arr) =>
    Object.prototype.toString.call(arr) === "[object Array]";
  //判断是否为object和function
  const isObject = (obj) =>
    obj !== null && (typeof obj === "object" || typeof obj === "function");
  //递归拷贝
  const copy = (input) => {
    //如果是函数或者不是对象,直接返回(函数和非对象不需要深拷贝)
    if (typeof input === "function" || !isObject(input)) return input;
    const output = isArray(input) ? [] : {};
    for (let key in input) {
      //确保只复制对象自身的属性,而不是原型链上的属性
      if (input.hasOwnProperty(key)) {
        output[key] = copy(input[key]);
      }
    }
    return output;
  };
  return copy(source);
};

验证深拷贝结果:

javascript 复制代码
const school2 = deepCopy(school);
school2.name = "buaa2";
console.log(school.name); // buaa
console.log(school2.name); // buaa2
const arr1 = [1, 2];
const arr2 = deepCopy(arr1);
arr2[0] = 3;
console.log(arr1[0]); // 1
console.log(arr2[0]); // 3

对于很多边界情况,以上简易版本还有许多地方需要处理,需要具体情况具体分析,但主要思想就是通过这样递归的方法,实现引用类型的深拷贝

相关推荐
Moment5 分钟前
前端白屏检测SDK:从方案设计到原理实现的全方位讲解 ☺️☺️☺️
前端·javascript·面试
阿波次嘚9 分钟前
关于在electron(Nodejs)中使用 Napi 的简单记录
前端·javascript·electron
接着奏乐接着舞。11 分钟前
Electron + Vue 项目如何实现软件在线更新
javascript·vue.js·electron
Ting丶丶14 分钟前
Electron入门笔记
javascript·笔记·electron
咖啡虫16 分钟前
解决 React 中的 Hydration Failed 错误
前端·javascript·react.js
贩卖纯净水.16 分钟前
《React 属性与状态江湖:从验证到表单受控的实战探险》
开发语言·前端·javascript·react.js
阿丽塔~17 分钟前
面试题之react useMemo和uesCallback
前端·react.js·前端框架
束尘18 分钟前
React面试(二)
javascript·react.js·面试
束尘18 分钟前
React通过命令式的弹窗控制,实现组件封装
javascript·react.js·ecmascript
刺客-Andy21 分钟前
React 之 Redux 第二十九节 Redux各项组成详解
前端·react.js·前端框架