站长资讯网
最全最丰富的资讯网站

一文带你详细了解JavaScript中的深拷贝

一文带你详细了解JavaScript中的深拷贝

前端(vue)入门到精通课程:进入学习
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API调试工具:点击使用

网上有很多关于深拷贝的文章,但是质量良莠不齐,有很多都考虑得不周到,写的方法比较简陋,难以令人满意。本文旨在完成一个完美的深拷贝,大家看了如果有问题,欢迎一起补充完善。

评价一个深拷贝是否完善,请检查以下问题是否都实现了:

  • 基本类型数据是否能拷贝?

  • 键和值都是基本类型的普通对象是否能拷贝?

  • Symbol作为对象的key是否能拷贝?

  • DateRegExp对象类型是否能拷贝?

  • MapSet对象类型是否能拷贝?

  • Function对象类型是否能拷贝?(函数我们一般不用深拷贝)

  • 对象的原型是否能拷贝?

  • 不可枚举属性是否能拷贝?

  • 循环引用是否能拷贝?

怎样?你写的深拷贝够完善吗?

深拷贝的最终实现

这里先直接给出最终的代码版本,方便想快速了解的人查看,当然,你想一步步了解可以继续查看文章余下的内容:

function deepClone(target) {     const map = new WeakMap()          function isObject(target) {         return (typeof target === 'object' && target ) || typeof target === 'function'     }      function clone(data) {         if (!isObject(data)) {             return data         }         if ([Date, RegExp].includes(data.constructor)) {             return new data.constructor(data)         }         if (typeof data === 'function') {             return new Function('return ' + data.toString())()         }         const exist = map.get(data)         if (exist) {             return exist         }         if (data instanceof Map) {             const result = new Map()             map.set(data, result)             data.forEach((val, key) => {                 if (isObject(val)) {                     result.set(key, clone(val))                 } else {                     result.set(key, val)                 }             })             return result         }         if (data instanceof Set) {             const result = new Set()             map.set(data, result)             data.forEach(val => {                 if (isObject(val)) {                     result.add(clone(val))                 } else {                     result.add(val)                 }             })             return result         }         const keys = Reflect.ownKeys(data)         const allDesc = Object.getOwnPropertyDescriptors(data)         const result = Object.create(Object.getPrototypeOf(data), allDesc)         map.set(data, result)         keys.forEach(key => {             const val = data[key]             if (isObject(val)) {                 result[key] = clone(val)             } else {                 result[key] = val             }         })         return result     }      return clone(target) }
登录后复制

1. JavaScript数据类型的拷贝原理

先看看JS数据类型图(除了Object,其他都是基础类型):
一文带你详细了解JavaScript中的深拷贝
在JavaScript中,基础类型值的复制是直接拷贝一份新的一模一样的数据,这两份数据相互独立,互不影响。而引用类型值(Object类型)的复制是传递对象的引用(也就是对象所在的内存地址,即指向对象的指针),相当于多个变量指向同一个对象,那么只要其中的一个变量对这个对象进行修改,其他的变量所指向的对象也会跟着修改(因为它们指向的是同一个对象)。如下图:
一文带你详细了解JavaScript中的深拷贝

2. 深浅拷贝

深浅拷贝主要针对的是Object类型,基础类型的值本身即是复制一模一样的一份,不区分深浅拷贝。这里我们先给出测试的拷贝对象,大家可以拿这个obj对象来测试一下自己写的深拷贝函数是否完善:

// 测试的obj对象 const obj = {     // =========== 1.基础数据类型 ===========     num: 0, // number     str: '', // string     bool: true, // boolean     unf: undefined, // undefined     nul: null, // null     sym: Symbol('sym'), // symbol     bign: BigInt(1n), // bigint      // =========== 2.Object类型 ===========     // 普通对象     obj: {         name: '我是一个对象',         id: 1     },     // 数组     arr: [0, 1, 2],     // 函数     func: function () {         console.log('我是一个函数')     },     // 日期     date: new Date(0),     // 正则     reg: new RegExp('/我是一个正则/ig'),     // Map     map: new Map().set('mapKey', 1),     // Set     set: new Set().add('set'),     // =========== 3.其他 ===========     [Symbol('1')]: 1  // Symbol作为key };  // 4.添加不可枚举属性 Object.defineProperty(obj, 'innumerable', {     enumerable: false,     value: '不可枚举属性' });  // 5.设置原型对象 Object.setPrototypeOf(obj, {     proto: 'proto' })  // 6.设置loop成循环引用的属性 obj.loop = obj
登录后复制

obj对象在Chrome浏览器中的结果:

一文带你详细了解JavaScript中的深拷贝

2.1 浅拷贝

浅拷贝: 创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址所指向的对象,肯定会影响到另一个对象。

首先我们看看一些浅拷贝的方法(详细了解可点击对应方法的超链接):

方法 使用方式 注意事项
Object.assign() Object.assign(target, ...sources)
说明:用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
1.不会拷贝对象的继承属性;
2.不会拷贝对象的不可枚举的属性;
3.可以拷贝 Symbol 类型的属性。
展开语法 let objClone = { ...obj }; 缺陷和Object.assign()差不多,但是如果属性都是基本类型的值,使用扩展运算符进行浅拷贝会更加方便。
Array.prototype.concat()拷贝数组 const new_array = old_array.concat(value1[, value2[, ...[, valueN]]]) 浅拷贝,适用于基本类型值的数组
Array.prototype.slice()拷贝数组 arr.slice([begin[, end]]) 浅拷贝,适用于基本类型值的数组

这里只列举了常用的几种方式,除此之外当然还有其他

赞(0)
分享到: 更多 (0)