深拷贝与浅拷贝注意的地方

106 阅读4分钟

一、数据类型

js分基本数据类型和引用数据类型。

基本数据类型:Number 、String 、Boolean 、Symbol (详解)、undefined 、null,存储在堆当中,动态分配的内存,大小不定也不会自动释放。

引用数据类型:object,栈中存储地址,自动分配内存空间。地址(0xffff) -> 堆中的内容。

二、浅拷贝

对于 基本数据类型 来说,修改拷贝前后互不影响;
对于 引用数据类型来说,修改拷贝前后第一层互不影响,但是 修改深层次内容会有影响,因为拷贝前后的变量指向的是同一块内存空间。

1、方法:

a、Object.assign()方法:(文档)

/*
* Obect.assign(target, ...sources);
* target:目标对象——将源的属性应用到什么,修改后返回。
* sources:源对象——包含您要应用的属性的对象。
*/

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };
const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

b、展开运算符(...): ( 文档)

let object1={
  name:'张三',
  age:18,
  sex:'男'
};
let object2={ ...object1 } ;
console.log(object2);// { name:'张三', age:18, sex:'男'};
object2.sex='女';
console.log(object2); // {name: '张三', age: 18, sex: '女'}
console.log(object1); // {name: '张三', age: 18, sex: '男'}

c、自己通过js实现

// 通过Object.keys()处理
let data={
  pageNum:1,
  pageSize:10,
  name:'测试',
  content:'描述',
  age:18,
  sex:null,
  appId:0,
};
let params={};
Object.keys(data).forEach(key=>{
  if(data[key]||data[key]===0){
    params[key]=data[key]
  }
});
console.log(params);// { 
                          pageNum: 1,
                          pageSize: 10,
                          name: "测试",
                          content: "描述",
                          age: 18,
                          appId: 0,  
                        }

2、注意:

a、Object.assign()方法:

合并时有相同属性,较晚来源的属性会覆盖较早的来源;

b、展开运算符(...):

展开语法和 Object.assign() 行为一致,执行的都是浅拷贝 (只遍历一层)

c、js实现:

只遍历一层的情况下可以这样处理,多层的情况下需要用到递归

三、深拷贝

针对引用数据类型多层而言,深拷贝相当于在内存空间内重新开辟了新的空间存储值,所以修改拷贝前后的变量不会互相影响。

1、方法:

a、JSON.stringfy() & JSON.parse()

let object1={
  pageNum:1,
  pageSize:10,
  eq:{
    id:1,
  },
  like:{
    name:'张三',
  },
  orders:[],
};
let object2=JSON.parse(JSON.stringfy(object1));
console.log(object2);// { pageNum:1, pageSize:10,
                          eq:{
                              id:1
                          },
                          like:{
                              name:'张三'
                          },
                          orders:[]
                         }

b、递归遍历,一个值一个值的拷贝

/**
 * 判断变量的类型
 * @param {object} value 变量值
 */
function checkType(value) {
  return Object.prototype.toString.call(value).slice(8, -1);
}

/**
 * 深拷贝(递归)
 * @param {*} sourceValue 需要拷贝的值
 */
function deepClone(sourceValue) {
  // 如果传入的数据是简单类型(不是 {} & []),直接返回即可
  if (typeof sourceValue !== "object") {
    return sourceValue;
  }
  // 判断 传入参数的数据类型(object or array)
  let targetType = checkType(sourceValue);
  // 根据传入参数的数据类型,创建 初始存储结果的变量类型 {} or []
  let result = targetType === "Object" ? {} : [];
  // 遍历 sourceValue (for...in可以遍历数据和对象)
  // 避免数组内有自定义属性,遍历数组使用 for...of,遍历对象 for...in
  if (targetType === "Array") {
    // 传入参数是数组时,次数使用的是 for...of 遍历,当然,也可以使用 数组的其他遍历方法
    for (const [key, value] of sourceValue.entries()) {
      let itemType = checkType(item);
      // 如果 value 是 数组 或 对象,则继续遍历
      if (itemType === "Object" || itemType === "Array") {
        result[key] = deepClone(value);
      } else {
        // 如果 value 是 基本数据类型 或者 函数,直接赋值即可
        result[key] = value;
      }
    }
  } else {
    // 传入参数是对象时
    for (const key in sourceValue) {
      // 遍历数组时,key 为数组的 下标
      // 遍历对象时,key 为对象的 key
      // hasOwnProperty 只能检验对象自身的属性,不能检验继承属性,也不能检验原型链上的属性
      if (sourceValue.hasOwnProperty(key)) {
        const item = sourceValue[key];
        let itemType = checkType(item);
        // 如果 value 是 数组 或 对象,则继续遍历
        if (itemType === "Object" || itemType === "Array") {
          result[key] = deepClone(item);
        } else {
          // 如果 value 是 基本数据类型 或者 函数,直接赋值即可
          result[key] = item;
        }
      }
    }
  }
  // 返回 result 即可
  return result;
}

c、loadsh.cloneDeep(value)

var objects = [{ 'a': 1 }, { 'b': 2 }];
 
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

2、注意:

a、JSON.parse(JSON.stringfy(value)):

(1)、如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;

1.png

(2)、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象

2.png

(3)、如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;

3.png

(4)、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;

4.png

b、递归遍历:

注意判断数据的类型

四、总结

基本数据类型可以用浅拷贝,引用类型数据使用时用深拷贝。根据需求情况合理使用深拷贝的方法,个人建议使用loadsh.cloneDeep()方法