面试利器之深浅拷贝

220 阅读6分钟

一.直接赋值

基本数据类型赋值之后两个变量互不影响。 引用数据类型赋址之后两个变量具有相同的引用,指向同一个对象,相互之间有影响。

看代码👇

对基本类型进行赋值操作,两个变量互不影响

let a = "heihei";
let b = a;
console.log(b);
// heihei

a = "change";
console.log(a);
// change
console.log(b);
// heihei

对引用类型进行赋操作,两个变量指向同一个对象,改变变量 a 之后会影响变量 b,哪怕改变的只是对象 a 中的基本类型数据。

let a = {
    name: "heihei",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = a;
console.log(b);
// {
// 	name: "heihei",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

通常在开发中并不希望改变变量 a 之后会影响到变量 b,这时就需要用到浅拷贝和深拷贝。

二.浅拷贝

2.1 定义

浅拷贝只拷贝第一层的基本数据类型,以及第一层的引用类型地址

2.2 实现浅拷贝的方法

2.2.1 Object.assign

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

看代码👇

let a = {
    name: "heihei",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = Object.assign({}, a);
console.log(b);
// {
// 	name: "heihei",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "muyiy",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

上面代码改变对象 a 之后,对象 b 的基本属性保持不变。但是当改变对象 a 中的对象 book 时,对象 b 相应的位置也发生了变化。

2.2.2 展开语法Spread

let a = {
    name: "heihei",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = {...a};
console.log(b);
// {
// 	name: "heihei",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "muyiy",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

通过代码可以看出实际效果和 Object.assign() 是一样的。

2.2.3 Array.prototype.slice()

let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]

a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]

console.log(b);
//  ["1", [4, 3]]

可以看出,改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝。

2.3 实现一个简单的浅拷贝

function cloneShallow(obj) {
  var newObj = {};
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

测试一下

var a = {
  name: "heihei",
  book: {
    title: "You Don't Know JS",
    price: "45"
  },
  a1: undefined,
  a2: null,
  a3: 123,
  a4: [1, 2, 3, { name: "array" }],
  a5: new Date(),
  a6: new RegExp()
};
var b = cloneShallow(a);

a.name = "xiaoheihei";
a.book.price = "55";

console.log(b);
//结果
name: "heihei"
book:  {
    title: "You Don't Know JS"
    price: "55"
}

可以看到,只实现了第一层基本数据类型的赋值以及复杂数据类型的赋址。

三.深拷贝

3.1 深拷贝定义

与浅拷贝不同,深拷贝之后的两个对象之间不会有任何的关联,互不影响。 但同时相对于浅拷贝而言,速度比较慢并且开销更大。

3.2 实现深拷贝的方法

3.2.1 JSON.parse(JSON.stringify(object))

let a = {
    name: "heihei",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = JSON.parse(JSON.stringify(a));
console.log(b);
// {
// 	name: "heihei",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "heihei",
// 	book: {title: "You Don't Know JS", price: "45"}
// }

完全改变变量 a 之后对 b 没有任何影响,这就是深拷贝的魔力。

上述方法实现深拷贝所存在的缺陷

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
  5. 不能正确处理new Date()
  6. 不能处理正则
  • undefinedsymbol 和函数这三种情况,会直接忽略。
let obj = {
name: 'heihei',
a: undefined,
b: Symbol('heihei'),
c: function() {}
}
console.log(obj);
// {
// 	name: "heihei",
// 	a: undefined,
//  b: Symbol(muyiy),
//  c: ƒ ()
// }
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "heihei"}
  • 循环引用情况下,会报错
let obj = {
    a: 1,
    b: {
        c: 2,
   	d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON
  • new Date 情况下,转换结果不正确
new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)

JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""

JSON.parse(JSON.stringify(new Date()));
// "2018-12-24T02:59:41.523Z"
  • 正则情况下
let obj = {
    name: "heihei",
    a: /'123'/
}
console.log(obj);
// {name: "heihei", a: /'123'/}

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "heihei", a: {}}

3.3 实现深拷贝

3.3.1 最初版

function deepClone(obj) {
  let newObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] =
        typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
    }
  }
  return newObj;
}

这是最简单的版本

存在的问题 1.传入null时,会返回object 2.没有对传入的参数进行校验,无法正确处理Date与RegExp 3.没有兼容数组 4.存在循环引用会报错

3.3.2 升级版

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") return obj; //传入的是null或者非引用类型是直接返回,解决上面的第一个问题
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj); // 传入的是date或者正则表达式时,直接用Date和RegExp构造函数处理 解决上面第2个问题
  let newObj = obj instanceof Array ? [] : {}; // 判断是数组还是对象,解决上面第三个问题
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] =
        typeof obj[key] === "object" ? deepClone(obj[key], hash) : obj[key];
    }
  }
  return newObj;
}

3.3.3 最终版

最后一个问题,循环引用。

循环引用报错的根本原因是函数执行栈爆栈了,直接原因是对象的属性间接或直接的引用了自身。

解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== "object") return obj; //传入的是null或者非引用类型是直接返回,解决上面的第一个问题
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj); // 传入的是date或者正则表达式时,直接用Date和RegExp构造函数处理 解决上面第2个问题
  if (hash.has(obj)) return hash.get(obj);
  let newObj = obj instanceof Array ? [] : {};
  hash.set(obj, newObj); // 判断是数组还是对象,解决上面第三个问题
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] =
        typeof obj[key] === "object" ? deepClone(obj[key], hash) : obj[key];
    }
  }
  return newObj;
}

结语

这篇文章主要参考木易杨前辈的前端进阶中的一篇文章,很感谢前辈。同时也在这里给大家安利一下木易杨大佬的文章。

木易杨前端进阶