「技术分享」你真的懂前端数据拷贝吗?

阅读原文 :【技术分享】你真的懂前端数据拷贝吗?

点击关注“八戒技术团队”,阅读更多技术干货

在我们的日常开发中,常常会涉及到数据的拷贝。在拷贝数据时,要保证新数据的独立性,不会互相影响,这涉及到数据的深浅拷贝。那如何正确的拷贝一个对象呢?

浅拷贝与深拷贝

浅拷贝是创建一个新对象,新旧对象共享同一个内存。如果拷贝是原始数据类型,那拷贝就是值。如果是引用类型,那拷贝的是内存地址,如果原数据进行了改变,新数据也会进行改变。

深拷贝是将数据完整的从内存中拷贝出来,在内存中开辟出新的存储区域。新旧数据的变化不会互相影响。深拷贝就是创建了一个新的数据,其值和原数据一致。

拷贝例子:

「技术分享」你真的懂前端数据拷贝吗?

浅拷贝时,我们可以看见,原数据的ID值改变,新数据的ID没有变化。但原数据的引用类型变化,新数据的引用类型也变化了。

「技术分享」你真的懂前端数据拷贝吗?

深拷贝时,我们可以看见,原数据的ID值改变,新数据的ID没有变化。原数据的引用类型变化,新数据的引用类型也没有。新数据是独立的。


浅拷贝方式

1.运算符...

「技术分享」你真的懂前端数据拷贝吗?

2. Object.assign()

「技术分享」你真的懂前端数据拷贝吗?

3. 数组concat()

「技术分享」你真的懂前端数据拷贝吗?

4. 数组slice()

「技术分享」你真的懂前端数据拷贝吗?


深拷贝方式

JSON.parse(JSON.stringify())

「技术分享」你真的懂前端数据拷贝吗?

使用JSON.stringify()转换成JSON字符串,再使用JSON.parse()解析成新的对象,生成新的存储空间,从而实现深拷贝。

注意:

  • 如果有Date,得到拷贝值是字符串
  • 如果有RegExp,Error,Set,Map得到空对象
  • 如果有function,undefined,Sybmol会丢弃
  • 如果有NaN、Infinity和 -Infinity,得到null
  • 如果有BinInt,无法序列化,报错
  • 如果有对象中有构造函数生成的,会丢弃constructor
  • 如果有循环引用,无法实现正确的拷贝,报错
function Person (name) {
     this.name = name;
 }


let obj = { 
    id: 1,
    childObj: {
        id: 11
    }
    func () {},
    und: undefined,
    date: new Date(),
    err: new Error(),
    reg: /0-9/,
    nan: NaN,
    num: Infinity,
num2: -Infinity,
    name: new Person('张三'),
sy: Symbol('sy'),
sets: new Set([ 1,2,3,4,5 ]),
maps: new Map()


}
  // 循环引用报错
  let obj1 = { id: 13 }
  // obj.obj1 = obj1;
  // obj1.obj = obj;
  let newObj = JSON.parse(JSON.stringify(obj));
  console.log(obj)
  // {
  //     id: 1, 
  //     childObj: { id: 11 }, 
  //     func: ƒ func(),
  //     und: undefined,
  //     date: Sun Mar 13 2022 12:07:22 GMT+0800 (中国标准时间) {},
  //     err: Error at,
  //     reg: /0-9/,
  //     nan: NaN,
  //     num: Infinity,
  //     num2: -Infinity,
  //     name: new Person('张三'),
  //     sy: Symbol(sy),
//     sets: Set(5) {1, 2, 3, 4, 5},
//     maps: Map(0) {size: 0}


  // }
  console.log(newObj)         
  // { 
  //      id: 1, 
  //      childObj: { id: 11 },
  //      date: '2022-03-13T03:51:40.492Z',
  //      err: {}, 
  //      id: 1, 
  //      nan: null,
  //      num: null,
//      num2: null
//      name: {name: '张三'}
//      sy: {}, 
//      sets: {}
//   }

递归循环

采用递归遍历每一项,针对特殊的数据类型,进行单独处理,使用object.prototype.toString获取数据类型。

使用map,避免循环引用,无法正确拷。

// 判断数据类型
const dataType = (target) => {      
    let type = Object.prototype.toString.call(target).slice(8, -1);
    let typeObj = {
        'Object': true,
        'Array': true,
        'Function': true,
        'RegExp': true,
        'Date': true,
        'Error': true,
        'Arguments': true,
        'Set': true,
        'Map': true,
        'Promise': true,
        'Symbol': true,
        'BigInt': true,
        'Math': true,
        'Null': true,
        'Undefined': true,
        'String': true,
        'Number': true,
        'Boolean': true
    };
    let val = {
        type
    };
    val['is' + type] = typeObj[type];
    if(type === 'String') {
        try {
            let obj = JSON.parse(target);
            val['isJSON'] = obj && typeof obj == 'object' ? true: false;
        }catch(e) {
            val['isJSON'] = false;
        }
    }
    return val;
}
const deepCopy = (data) => {
    const mapObj = new Map(); // 处理循环引用
    const copy= (data) => {
        // 基本数据类型直接返回
        let types = [ 'String', 'Number', 'BigInt', 'Boolean', 'Undefined', 'Null' ];
        if(types.includes(dataType(data).type)) return data;
        // 函数
        if(dataType(data).isFunction) {
            return   new Function('return ' + data.toString()).call(this)
        }
        // symbol
        if(dataType(data).isSymbol) {
            return Object(Symbol.prototype.valueOf.call(data));
        }
        // 时间
        if(dataType(data).isDate) {
            return new Date(data.valueOf())
        }
        // 正则
        if(dataType(data).isRegExp) {
            const reFlags = /\w*$/;
            const result = new data.constructor(data.source, reFlags.exec(data));
            result.lastIndex = data.lastIndex;
            return result;
        }
        // 引用数据类型特殊处理
        const newObj = new data.constructor();
        // 判断处理循环引用
        if (mapObj.has(data)) {
            return mapObj.get(data);
        }
        // 不存在则第一次设置
        mapObj.set(data, newObj);
        // Map
        if(dataType(data).isMap) {
            data.forEach((v,k) => {
                newObj.set(k, copy(v))
            })
            return newObj;
        }
        // Set
        if(dataType(data).isSet) {
            for(let val of data.values()) {
                newObj.add(copy(val))
            }
            return newObj;
        }
        // 对象 或 数组
        if(dataType(data).isObject || dataType(data).isArray ){
            for(let key in data) {
                newObj[key] = copy(data[key])
            }
            return newObj;
        }
        // 其它类型直接返回
        return data;
    }
    return copy(data);
}

使用:

let obj1 = { id: 13 }
// 构造函数
function Person (name) {
this.name = name;
}
let obj = { 
    id: 1,
    childObj: {
        id: 11
    },
    func () {},
    und: undefined,
    date: new Date(),
    err: new Error(),
    reg: /0-9/,
    nan: NaN,
    num: Infinity,
num2: -Infinity,
    name: new Person('张三'),
sy: Symbol('sy'),
bn: BigInt(123456897)
}
 obj.obj1 = obj1;
 obj1.obj = obj;
let newObj = deepCopy (obj);
 console.log(newObj)    
 // {
 //   bn: 123456897n
 //   id: 1, 
 //   childObj: { id: 11 }, 
 //   func: ƒ func(),
 //   und: undefined,
 //   date: Sun Mar 13 2022 12:07:22 GMT+0800 (中国标准时间) {},
 //   err: Error at,
 //   reg: /0-9/,
 //   nan: NaN,
 //   num: Infinity,
 //   num2: -Infinity,
 //   name: Person {name: '张三'},
 //   sy: Symbol {Symbol(sy), description: 'sy'},
 //   obj1:{id: 13, obj: {…}},
 //}

以上就是我对深浅拷贝的理解,以及如何进行深拷贝,进行的封装处理。目前深拷贝,没有对大型数据拷贝做处理,因为数据一旦过大,那么递归处理的时间就会增加。那么这一块还可以继续研究。希望这篇文章能在你的工作为你提供帮助。


希望以上内容能对有需要的人有所帮助

欢迎大家留言写下自己希望了解的技术方向

欢迎大家一起探讨交流

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章