「你必须知道的JavaScript」深浅拷贝

190 阅读3分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

深浅拷贝

前阵子做一个图表可视化的项目,对每个类型的图表都配置了默认配置项(对象),一开始没考虑深拷贝问题,结果出篓子了...修改后面的图表,前面的被改了...那么今天就整理下深浅拷贝相关的内容。

浅拷贝

拷贝基本类型,对于对象类型,拷贝的是地址

  • Object.assign
  • ...展开运算符

深拷贝

解决对象引用问题

(一)JSON.parse(JSON.srtingify(obj))

  • 局限性
    • 会忽略undefined
    • 会忽略symbol
    • 不能序列化函数
    • 不能解决循环引用的对象
循环引用问题
let obj = {
	a: 1,
	b: {
		c: 2,
		d: 3, 
	}
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj)) 

console.log(newObj)
// Uncaught TypeError: Converting circular structure to JSON
序列化问题

在遇到函数、undefined 或者 symbol的时候,该对象也不能正常的序列化

JSON语法

  • 支持数字、字符串、布尔值、null四种,不支持undefined
  • NaN、Infinity和-Infinity序列化的结果是null
  • 不支持函数
  • 除了RegExp、Error对象,JSON语法支持其他所有对象
  • 日期对象序列化的结果是字符串,并不会将其还原为日期对象
  • 只能序列化对象的可枚举的自有属性
let a = {
	age: undefined,
	sex: Symbol('male'), 
	jobs: function() {}, name: 'merlin'
}
let b = JSON.parse(JSON.stringify(a))

console.log(b) // {name: "merlin"}

(二)MessageChannel

使用场景:拷⻉的对象含有内置类型并且不包含函数,可以处理 undefined 和循环引用对象

function structuralClone(obj) {
	return new Promise(resolve => {
		const { port1, port2 } = new MessageChannel();
		port2.onmessage = ev => resolve(ev.data);
		port1.postMessage(obj)
	}) 
}
var obj = {
	a: 1,
	b: { 
		c: 2
	}
}
obj.b.d = obj.b

// 注意该方法是异步的
const test = async () => {
	const clone = await structuralClone(obj)
	console.log(clone)
}
test()

(三)手写深拷贝函数

简单实现版
/**
 * @description: 判断一个值是否是基本类型及函数
 * @param {string} value 任何值
 * @return {*}
 */

function isPrimitive(value: any) {
  return /Number|Boolean|String|Null|Undefined|Symbol|Function/.test(
    Object.prototype.toString.call(value)
  );
}

/**
 * @description: 深拷贝简易版,包含基础类型,函数,对象,数组
 * @param {any} source 原数据
 * @return {*}
 */
function deepCopy(source: any) {
  // 基本类型及函数
  if (isPrimitive(source)) {
    return source;
  }
  // 判断是数组还是对象
  let result = Array.isArray(source) ? [] : {};
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      if (typeof source[key] === 'object') {
        result[key] = deepCopy(source[key]);   //递归复制
      } else {
        result[key] = source[key];
      }
    }
  }
  return result;
}
较详细版
  • 处理复杂对象,如 DateRegexp
  • 处理循环引用
/**
 * @description: 判断一个值是否是基本类型及函数
 * @param {string} value 任何值
 * @return {*}
 */

function isPrimitive(value: any) {
  return /Number|Boolean|String|Null|Undefined|Symbol|Function/.test(
    Object.prototype.toString.call(value)
  );
}

/**
 * @description: 深拷贝函数,包括基础类型,函数,Map,Set,Date,Regex,引用类型等。
 * @param {any} source 原数据
 * @param {WeakMap} memory 记录临时值
 * @return {*}
 */
function deepClone(source:any, memory: WeakMap<object, any> = new WeakMap()) {
  let result = null;
  // 原始数据类型及函数
  if (isPrimitive(source)) {
    result = source;
  }
  // 数组
  else if (Array.isArray(source)) {
    result = source.map((value) => deepClone(value, memory));
  }
  // 内置对象Date、Regex
  else if (Object.prototype.toString.call(source) === "[object Date]") {
    result = new Date(source);
  } else if (Object.prototype.toString.call(source) === "[object Regex]") {
    result = new RegExp(source);
  }
  // 内置对象Set、Map
  else if (Object.prototype.toString.call(source) === "[object Set]") {
    result = new Set();
    for (const value of source) {
      result.add(deepClone(value, memory));
    }
  } else if (Object.prototype.toString.call(source) === "[object Map]") {
    result = new Map();
    for (const [key, value] of source.entries()) {
      result.set(key, deepClone(value, memory));
    }
  }
  // 引用类型
  else {
    if (memory.has(source)) {
      result = memory.get(source);
    } else {
      result = Object.create(null);
      memory.set(source, result);
		// Reflect.ownKeys()返回所有属性
		// Object.keys()只能返回可枚举属性
      Reflect.ownKeys(source).forEach((key) => {
        const value = source[key];
        result[key] = deepClone(value, memory);
      });
    }
  }
  return result;
}

参考链接