JS高级 之 深拷贝 && 浅拷贝

目录

一、浅拷贝的实现

1. Object.assgin

2. 展开运算符

3. Array.prototype.concat

4. Array.prototype.slice

二、深拷贝的实现

0. 判断一个值是否是对象类型

1. JSON实现

2. 手写深拷贝函数

01 - 基本手写

02 - 优化 => 数组

03 - 优化 => 函数、Set、Map、Symbol

04 - 优化 => 循环引用 

04 - 最终方案 


一、浅拷贝的实现

1. Object.assgin

const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder'
  }
};

// 浅拷贝
const info = Object.assign({}, obj);
console.log(info); // { name: 'star', age: 18, friend: { name: 'coder' } }

// 修改引用类型数据,同时会更改
obj.friend.name = '123';
console.log(info); // { name: 'star', age: 18, friend: { name: '123' } }


2. 展开运算符

const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder'
  }
};

// 浅拷贝
const info = { ...obj };
console.log(info); // { name: 'star', age: 18, friend: { name: 'coder' } }

// 修改引用类型数据,同时会更改
obj.friend.name = '123';
console.log(info); // { name: 'star', age: 18, friend: { name: '123' } }

3. Array.prototype.concat

const arr = [1, { name: 'coder' }, '4'];

// 浅拷贝
const info = [].concat.call(arr, [{ age: 18 }]);
// const info = Array.prototype.concat.call(arr, [{ age: 18 }]);

console.log(info); //[ 1, { name: 'coder' }, '4', { age: 18 } ]

// 修改引用类型数据,同时会更改
arr[1].name = '123';
console.log(info); // [ 1, { name: '123' }, '4', { age: 18 } ]

4. Array.prototype.slice

const arr = [1, 2, { name: 'coder' }, '4'];

// 浅拷贝
const info = [].slice.call(arr);
// const info = [].slice.call(arr, 0);
// const info = [].slice.call(arr, 0, 4);
// const info = Array.prototype.slice.call(arr);

console.log(info); // [ 1, 2, { name: 'coder' }, '4' ]

// 修改引用类型数据,同时会更改
arr[2].name = '123';
console.log(info); // [ 1, 2, { name: '123' }, '4' ]

二、深拷贝的实现

0. 判断一个值是否是对象类型

// 判断一个标识符是否是对象类型
function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === 'object' || valueType === 'function');
}

1. JSON实现

const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder',
    arr: [1, 2, 3, 4]
  }
};

// 深拷贝
const info = JSON.parse(JSON.stringify(obj));
console.log(info);

// 修改原来属性,不会影响新创建的对象
obj.friend.name = '123';

console.log(info);

缺点 : 

        1. function 不会被转化,直接忽略掉了

        2. symbal 不会被转化,直接忽略掉了

        3. 存在undefined、NaN 之类情况会转化错误

        4. 存在循环引用的情况直接报错

2. 手写深拷贝函数

01 - 基本手写

function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === 'object' || valueType === 'function');
}

function deepCopy(originValue) {
  // 1. 如果不是引用类型,直接返回
  if (!isObject(originValue)) {
    return originValue;
  }
  // 2. 创建新对象
  const newObj = {};
  for (const key in originValue) {
    // 3. 赋值的时候,进行递归
    newObj[key] = deepCopy(originValue[key]);
  }
  // 4. 返回新对象
  return newObj;
}

const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder'
  }
};

// 深拷贝
const info = deepCopy(obj);
console.log(info);

// 修改原来属性,不会影响新创建的对象
obj.friend.name = '123';

console.log(info);

02 - 优化 => 数组

function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === 'object' || valueType === 'function');
}

function deepCopy(originValue) {
  // 1. 如果不是引用类型,直接返回
  if (!isObject(originValue)) {
    return originValue;
  }
  // 2. 创建新对象 | 数组
  const newObj = Array.isArray(originValue) ? [] : {};
  for (const key in originValue) {
    // 3. 赋值的时候,进行递归
    newObj[key] = deepCopy(originValue[key]);
  }
  // 4. 返回新对象
  return newObj;
}

const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder',
    info: [1, 2, 3, 4, 5]
  }
};

// 深拷贝
const info = deepCopy(obj);
console.log(info);
obj.age = 20;
console.log(info);

03 - 优化 => 函数、Set、Map、Symbol

function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === 'object' || valueType === 'function');
}

// 深拷贝函数
function deepCopy(originValue) {
  /**
   * 值是 symbol数据类型
   * 需写在基本数据类型之前,否则会被返回错误
   */
  if (typeof originValue === 'symbol') {
    return Symbol(originValue.description);
  }
  /**
   * 基本数据类型,直接返回
   */
  if (!isObject(originValue)) {
    return originValue;
  }
  /**
   * 函数类型
   * 不需要拷贝,直接返回
   */
  if (typeof originValue === 'function') {
    return originValue;
  }
  /**
   * set类型
   * 因为set类型不能被for...in,所以单独处理
   */
  if (originValue instanceof Set) {
    const newSet = new Set();
    for (const item of originValue) {
      newSet.add(deepCopy(item));
    }
    return newSet;
  }
  /**
   * map类型
   * 因为set类型不能被for...in,所以单独处理
   */
  if (originValue instanceof Map) {
    const newMap = new Map();
    for (const [key, value] of originValue) {
      newMap.set(deepCopy(key), deepCopy(value));
    }
    return newMap;
  }
  /**
   * 对象,数组类型
   */
  const newObj = Array.isArray(originValue) ? [] : {};
  // one - 遍历普通的key
  for (const key in originValue) {
    newObj[key] = deepCopy(originValue[key]);
  }
  // two - 遍历Symbol的key
  const symbolKeys = Object.getOwnPropertySymbols(originValue);
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey]);
  }
  // 4. 返回新对象 | 数组
  return newObj;
}

const symbolKey = Symbol('key是Symbol');
const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder',
    info: [1, 2, 3, 4, 5]
  },
  play() {
    console.log('playing!');
  },
  set: new Set([1, 22, 3]),
  map: new Map([
    [{ name: 5 }, 5],
    [6, { name: 6 }]
  ]),
  [symbolKey]: 'key是Symbol',
  symbolValue: Symbol('value是Symbol')
};

// 深拷贝
const info = deepCopy(obj);
console.log(info);

04 - 优化 => 循环引用 

function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === 'object' || valueType === 'function');
}

/**
 * 循环引用解决方案 :
 * 拷贝过的对象,不需要再次拷贝,直接返回即可
 *    1. 使用 WeakMap 进行弱引用,用来判断是否拷贝过
 *    2. 使用同一个 WeakMap
 */
function deepCopy(originValue, map = new WeakMap()) {
  if (typeof originValue === 'symbol') {
    return Symbol(originValue.description);
  }

  if (!isObject(originValue)) {
    return originValue;
  }

  if (typeof originValue === 'function') {
    return originValue;
  }

  if (originValue instanceof Set) {
    const newSet = new Set();
    for (const item of originValue) {
      // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
      newSet.add(deepCopy(item, map));
    }
    return newSet;
  }

  if (originValue instanceof Map) {
    const newMap = new Map();
    for (const [key, value] of originValue) {
      // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
      newMap.set(deepCopy(key, map), deepCopy(value, map));
    }
    return newMap;
  }

  /**
   * 每次创建对象前,判断map中是否已经拥有了 => 即是否已经拷贝过了
   * 如果有,直接返回以前创建的对象
   */
  if (map.get(originValue)) {
    return map.get(originValue);
  }
  const newObj = Array.isArray(originValue) ? [] : {};
  /**
   * 1. 每次创建新对象后,推入 WeakMap 中,用来保存
   * 2. 当前的对象的地址作为 key ,值为新创建出来的对象
   */
  map.set(originValue, newObj);

  for (const key in originValue) {
    // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
    newObj[key] = deepCopy(originValue[key], map);
  }
  const symbolKeys = Object.getOwnPropertySymbols(originValue);
  for (const symbolKey of symbolKeys) {
    // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
    newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map);
  }
  return newObj;
}

const symbolKey = Symbol('key是Symbol');
const obj = {
  name: 'star',
  age: 18,
  friend: {
    name: 'coder',
    info: [1, 2, 3, 4, 5]
  }
};
// 自己引用自己
obj.obj = obj;

// 深拷贝
const info = deepCopy(obj);
console.log(info);

04 - 最终方案 

// 判断一个值是否是对象类型
function isObject(value) {
  const valueType = typeof value;
  return value !== null && (valueType === 'object' || valueType === 'function');
}
// 深拷贝函数
function deepCopy(originValue, map = new WeakMap()) {
  if (typeof originValue === 'symbol') {
    return Symbol(originValue.description);
  }
  if (!isObject(originValue)) {
    return originValue;
  }
  if (typeof originValue === 'function') {
    return originValue;
  }
  if (originValue instanceof Set) {
    const newSet = new Set();
    for (const item of originValue) {
      newSet.add(deepCopy(item, map));
    }
    return newSet;
  }
  if (originValue instanceof Map) {
    const newMap = new Map();
    for (const [key, value] of originValue) {
      newMap.set(deepCopy(key, map), deepCopy(value, map));
    }
    return newMap;
  }

  if (map.get(originValue)) {
    return map.get(originValue);
  }
  const newObj = Array.isArray(originValue) ? [] : {};
  map.set(originValue, newObj);
  for (const key in originValue) {
    newObj[key] = deepCopy(originValue[key], map);
  }
  const symbolKeys = Object.getOwnPropertySymbols(originValue);
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map);
  }
  return newObj;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值