Front-end-learning-to-organize-notes icon indicating copy to clipboard operation
Front-end-learning-to-organize-notes copied to clipboard

深拷贝/浅拷贝问题

Open Chocolate1999 opened this issue 4 years ago • 3 comments

Chocolate1999 avatar Jan 21 '21 09:01 Chocolate1999

浅拷贝

Object.assign()

语法:Object.assign(target, ...sources) ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标 target ,剩下的参数是拷贝的源对象 sources(可以是多个)。

详细介绍,可以阅读文档《MDN Object.assign》。

let obj = {
  name: 'Chocolate',
  score: {
    web: 99,
    math: 100
  }
}
let newObj = Object.assign({},obj);
newObj.score.web = 100;
console.log(obj.name); // 'Chocolate'
console.log(newObj.name); // 'Chocolate'
console.log(obj.score.web); // 100
console.log(newObj.score.web); // 100

浅拷贝只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。

Object.assign() 使用注意:

  • 只拷贝源对象的自身属性(不拷贝继承属性);
  • 不会拷贝对象不可枚举的属性;
  • 属性名为 Symbol 值的属性,可以被 Object.assign 拷贝;
  • undefinednull 无法转成对象,它们不能作为 Object.assign 参数,但是可以作为源对象。
Object.assign(undefined); // 报错
Object.assign(null);      // 报错

Object.assign({}, undefined); // {}
Object.assign({}, null);      // {}

let user = {name: "Chocolate"};
Object.assign(user, undefined) === user; // true
Object.assign(user, null)      === user; // true

Array.prototype.slice()

语法:arr.slice([begin[, end]])slice() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括 end )。原始数组不会被改变。

详细介绍,可以阅读文档《MDN Array slice》。

// 示例 数组深拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = Array.prototype.slice.call(user);
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"  ⚠️ 差异!
console.log(user[0]);         // "leo"        ⚠️ 差异!
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

Array.prototype.concat()

语法:var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

详细介绍,可以阅读文档《MDN Array concat》。

let user  = [{name: "leo"},   {age: 18}];
let user1 = [{age: 20},{addr: "fujian"}];
let user2 = user.concat(user1);
user1[0]["age"] = 25;
console.log(user);  // [{"name":"leo"},{"age":18}]
console.log(user1); // [{"age":25},{"addr":"fujian"}]
console.log(user2); // [{"name":"leo"},{"age":18},{"age":25},{"addr":"fujian"}]

Array.prototype.concat 也是一个浅拷贝,只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。

拓展运算符(...)

语法:var cloneObj = { ...obj };

扩展运算符也是浅拷贝,对于值是对象的属性无法完全拷贝成2个不同对象,但是如果属性都是基本类型的值的话,使用扩展运算符也是优势方便的地方。

let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = {...user};
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90

手写浅拷贝

实现原理:新的对象复制已有对象中非对象属性的值和对象属性的「引用」,也就是说对象属性并不复制到内存。

function cloneShallow(source) {
    let target = {};
    for (let key in source) {
        if (source.hasOwnProperty(key)) {
            target[key] = source[key];
        }
    }
    return target;
}

「for in」

for...in语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。对于每个不同的属性,语句都会被执行。

「hasOwnProperty」

该函数返回值为布尔值,所有继承了 Object 的对象都会继承到 hasOwnProperty 方法,和 in 运算符不同,该函数会忽略掉那些从原型链上继承到的属性和自身属性。语法:obj.hasOwnProperty(prop)

其中 prop 是要检测的属性「字符串名称」或者Symbol。

本文参考:

【JS】676- 1.1w字 | 初中级前端 JavaScript 自测清单 - 2

Chocolate1999 avatar Jan 22 '21 11:01 Chocolate1999

深拷贝

复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象「与原来的对象完全隔离」,互不影响,对一个对象的修改并不会影响另一个对象。

JSON.parse(JSON.stringify())

其原理是把一个对象序列化成为一个 JSON 字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用 JSON.parse() 反序列化将 JSON 字符串变成一个新的对象。

let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = JSON.parse(JSON.stringify(user));
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90 ⚠️ 差异!
console.log(user.skill.CSS);// 80 ⚠️ 差异!

JSON.stringify() 使用注意:

  • 拷贝的对象的值中如果有函数, undefinedsymbol 则经过 JSON.stringify() 序列化后的JSON字符串中这个键值对会消失;
  • 无法拷贝不可枚举的属性,无法拷贝对象的原型链;
  • 拷贝 Date 引用类型会变成字符串;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有 NaNInfinity-Infinity ,则序列化的结果会变成 null
  • 无法拷贝对象的循环应用(即 obj[key] = obj )。

ES5深拷贝函数封装

var obj = {
  name: '一百个Chocolate',
  age: 18,
  info: {
    hobby: ['game', 'music', {
      a: 1
    }],
    career: {
      teacher: 0,
      engineer: 1
    }
  }
}

function deepClone(origin, target) {
  var tar = target || {};
  var toStr = Object.prototype.toString;
  var arrType = '[object Array]';
  for (let k in origin) {
    if (origin.hasOwnProperty(k)) {
      if (typeof origin[k] === 'object' && origin[k] !== null) {
        tar[k] = toStr.call(origin[k]) === arrType ? [] : {};
        deepClone(origin[k], tar[k]);
      } else {
        tar[k] = origin[k];
      }
    }
  }
  return tar;
}

const newObj = deepClone(obj, {});
newObj.info.hobby[2].a = 123;
console.log(obj,newObj);

ES6 深拷贝

var obj = {
  name: '一百个Chocolate',
  age: 18,
  info: {
    hobby: ['game', 'music', {
      a: 1
    }],
    career: {
      teacher: 0,
      engineer: 1
    }
  }
}

function deepClone(origin){
  if(origin == undefined || typeof origin !== 'object'){
    return origin;
  }

  if(origin instanceof Date){
    return new Date(origin);
  }
  if(origin instanceof RegExp){
    return new RegExp(origin);
  }
  const target = new origin.constructor();
  for(let k in origin){
    if(origin.hasOwnProperty(k)){
      target[k] = deepClone(origin[k]);
    }
  }
  return target;
}

const newObj = deepClone(obj);
newObj.info.hobby[2].a = 123;
console.log(obj,newObj);

ES6 深拷贝 WeakMap解决死循环问题

var obj = {
  name: '一百个Chocolate',
  age: 18,
  info: {
    hobby: ['game', 'music', {
      a: 1
    }],
    career: {
      teacher: 0,
      engineer: 1
    }
  }
}

function deepClone(origin, hashMap = new WeakMap()) {
  if (origin == undefined || typeof origin !== 'object') {
    return origin;
  }

  if (origin instanceof Date) {
    return new Date(origin);
  }
  if (origin instanceof RegExp) {
    return new RegExp(origin);
  }
  const hashKey = hashMap.get(origin);
  if (hashKey) {
    return hashKey;
  }

  const target = new origin.constructor();
  hashMap.set(origin, target);
  for (let k in origin) {
    if (origin.hasOwnProperty(k)) {
      target[k] = deepClone(origin[k], hashMap);
    }
  }
  return target;
}

const newObj = deepClone(obj);
newObj.info.hobby[2].a = 123;
console.log(obj, newObj);

本文参考:

【JS】676- 1.1w字 | 初中级前端 JavaScript 自测清单 - 2

【全网首发:已完结】ES5-ES6『对象深拷贝』【JavaScript基础】

Chocolate1999 avatar Jan 22 '21 12:01 Chocolate1999

image

Chocolate1999 avatar Jan 23 '21 02:01 Chocolate1999