image.png

image.png

js 的基本数据类型的赋值,就是值传递。引用类型对象的赋值是将对象地址的引用赋值。这时候修改对象中的属性或者值,会导致所有引用这个对象的值改变。如果想要真的复制一个新的对象,而不是复制对象的引用,就要用到对象的深拷贝。

数据类型(基本数据类型和引用数据类型)

基本数据类型(栈内存,引用值,深拷贝)

var a = 3;
let b = a;
b = 4;
console.log(a, b); //a=3,b=4

引用数据类型(堆内存,引用址,指针指向该地址。浅拷贝)

console.log("引用数据类型");
let arr1 = [1, 2];
let arr2 = arr1;
arr2.push(3);
console.log(arr1, arr2); //[1,2,3] [1,2,3]

浅拷贝

1.‘=’赋值。只是将对象的引用赋值

const a = { name: "xiaoMing", age: 20 };
const b = a;
console.log(b); //{name:'xiaoMing',age:20};
a.name = "xiaohong";
console.log(b); //{name:'xiaohong',age:20}

2.Object.assign()

Object.assign 是 ES6 的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。Object.assgin 只能深拷贝第一层, 深层的还是浅拷贝, 记住这个就行了。

Object.assign(target, ...sources);

参数:
target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。

let target = {};
let source = { a: "koala", b: { name: "程序员成长指北" } };
Object.assign(target, source);
console.log(target); // { a: 'koala', b: { name: '程序员成长指北' } }

source.a = "smallKoala";
source.b.name = "程序员成长指北哦";
console.log(source); // { a: 'smallKoala', b: { name: '程序员成长指北哦' } }
console.log(target); // { a: 'koala', b: { name: '程序员成长指北哦' } }

//Object.assign 是浅拷贝,拷贝的是对象的引用值,如果为引用类型对象时,一级属性为深拷贝,对象中有二级属性的话,则二级属性以后都是浅拷贝。

3.扩展运算符(...)

let obj = { a: 1, b: { c: 1 } };
let obj2 = { ...obj };
obj.a = 2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

//扩展运算符是浅拷贝,拷贝的是对象的引用值,如果为引用类型对象时,一级属性为深拷贝,如果对象中有二级属性的话,则二级属性以后都是浅拷贝。

如果对象或者数组中包含子数组和子对象,那子数组或者对象为浅拷贝

原因是...遍历时那部分为对象/数组类型指向原来的地址

对象

var obj = { a: 1, b: 2, c: { a: 3 }, d: [4, 5] };
var obj1 = obj;
var obj2 = JSON.parse(JSON.stringify(obj)); //深拷贝常用方法
var obj3 = { ...obj };
var obj4 = Object.assign({}, obj);
obj.a = 999;
obj.c.a = -999;
obj.d[0] = 123;
console.log(obj1); //{a: 999, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj2); //{a: 1, b: 2, c: { a: 3 },d: [4, 5]}
console.log(obj3); //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj4); //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}

数组

var arr = [1, 2, 3, [4, 5], { a: 6, b: 7 }];
var arr1 = JSON.parse(JSON.stringify(arr)); //深拷贝常用方法
var arr2 = arr;
var arr3 = [...arr];
var arr4 = Object.assign([], arr);
console.log(arr === arr1); //false
console.log(arr === arr2); //true
console.log(arr === arr3); //false
console.log(arr === arr4); //false
arr[0] = 999;
arr[3][0] = -999;
arr[4].a = 123;

console.log(arr1); //[1, 2, 3, [4, 5], {a: 6, b: 7}]
console.log(arr2); //[999, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr3); //[1, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr4); //[1, 2, 3, [-999, 5], {a: 123, b: 7}]

深拷贝

1.手动复制

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;

console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到

console.log(obj2);
// { a: 10, b: 100, c: 30 }

2.JSON 做字符串转换

用 JSON.stringify 把对象转成字符串,再用 JSON.parse 把字符串转成新的对象。

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;

console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则。只能序列化对象的可枚举的自有属性。

弊端

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

eg:
var test = {
    name: 'a',
    date: [new Date(1536627600000), new Date(1540047600000)],
  };
  let b;
  b = JSON.parse(JSON.stringify(test));
console.log(b);

解决方法,将 new Date()变为字符串,new Date().toString()

2.如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的结果将只得到空对象;

const test = {
  name: "a",
  date: new RegExp("\\w+"),
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = "test";
//console.error('ddd', test, copyed);
console.log(copyed);

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

const test = {
  name: "a",
  date: function hehe() {
    console.log("fff");
  },
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = "test";
console.error("ddd", test, copyed);

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

5.JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果 obj 中的对象是有构造函数生成的, 则使用 JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的 constructor;

function Person(name) {
  this.name = name;
  console.log(name);
}
const liai = new Person("liai");
const test = {
  name: "a",
  date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = "test";
console.error("ddd", test, copyed);

6.如果对象中存在循环引用的情况也无法正确实现深拷贝;
总结:
用法简单,然而使用这种方法会有一些隐藏的坑:因为在序列化 JavaScript 对象时,所有函数和原型成员会被有意忽略。
通俗点说,JSON.parse(JSON.stringfy(X)),其中 X 只能是 Number, String, Boolean, Array, 扁平对象,即那些能够被 JSON 直接表示的数据结构。

3.递归拷贝

function deepClone(initalObj, finalObj) {
  var obj = finalObj || {};
  for (var i in initalObj) {
    var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if (prop === obj) {
      continue;
    }
    if (typeof prop === "object") {
      obj[i] = prop.constructor === Array ? [] : {};
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }
  return obj;
}
var str = {};
var obj = { a: { a: "hello", b: 21 } };
deepClone(obj, str);
console.log(str.a);

4.使用 Object.create()方法

直接使用 var newObj = Object.create(oldObj),可以达到深拷贝的效果。

function deepClone(initalObj, finalObj) {
  var obj = finalObj || {};
  for (var i in initalObj) {
    var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if (prop === obj) {
      continue;
    }
    if (typeof prop === "object") {
      obj[i] = prop.constructor === Array ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }
  return obj;
}

5.jquery

jquery 有提供一个$.extend 可以用来做 Deep Copy。

var $ = require("jquery");
var obj1 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);
// false

6.第三方函数

还有一些其它的第三方函数库有深拷贝 function,如 lodash。

文章引用:

https://blog.csdn.net/ljw1412/article/details/79651725
https://www.jianshu.com/p/52db1d0c1780
https://segmentfault.com/a/1190000016440069

Q.E.D.