深入 JS 之深浅拷贝

发布于 2022-10-03 22:25:23 字数 3890 浏览 113 评论 0

细说赋值与浅拷贝的区别

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

我们先来看两个例子,对比赋值与浅拷贝会对原对象带来哪些改变?

// 对象赋值
var obj1 = {
  name: "zhangsan",
  age: "18",
  language: [1, [2, 3], [4, 5]]
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二", "三"];
console.log("obj1", obj1);
console.log("obj2", obj2);
// 浅拷贝
var obj1 = {
  name: "zhangsan",
  age: "18",
  language: [1, [2, 3], [4, 5]]
};
var obj3 = shallowCopy(obj1);
obj3.name = "lisi";
obj3.language[1] = ["二", "三"];
function shallowCopy(src) {
  var dst = {};
  for (var prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}
console.log("obj1", obj1);
console.log("obj3", obj3);

上面例子中,obj1 是原始数据,obj2 是赋值操作得到,而 obj3 浅拷贝得到。我们可以很清晰看到对原始数据的影响。

浅拷贝的实现方式

1. Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: { a: "kobe", b: 39 } };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade

注意:当 object 只有一层的时候,是深拷贝。

let obj = {
  username: "kobe"
};
let obj2 = Object.assign({}, obj);
obj2.username = "wade";
console.log(obj); //{username: "kobe"}

2. Array.prototype.slice()

let arr = [
  1,
  3,
  {
    username: " kobe"
  }
];
let arr3 = arr.slice();
arr3[2].username = "wade";
console.log(arr);

3. Array.prototype.concat()

let arr = [
  1,
  3,
  {
    username: "kobe"
  }
];
let arr2 = arr.concat();
arr2[2].username = "wade";
console.log(arr);

关于 Array 的 slice 和 concat 方法的补充说明:Array 的 slice 和 concat 方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

4. 扩展运算符

let a = {
    name: "sadhu",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = {...a};
console.log(b);
// {
// 	name: "sadhu",
// 	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: "sadhu",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

手写一个简易浅拷贝

遍历对象,然后把属性和属性值都放在一个新的对象就好了。

var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

关于思维导图中深拷贝的一点补充

就补充一个关于 JSON.parse(JSON.stringify()) 例子:

let obj = {
  reg: /^asd\$/,
  fun: function() {},
  und: undefined,
  syb: Symbol("foo"),
  asd: "asd"
};
let cp = JSON.parse(JSON.stringify(obj));
console.log(cp);

可以看到,函数、正则、Symbol、undefined 都没有被正确的复制。

手写一个简易深拷贝

我们在浅拷贝的基础上判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数:

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

这只是简易,针对普通应用场景。有问题比如 typeof 判断返回的是 function,这种函数的复制是一个很难解决的问题呀,即使是 jQuery 的 extend 也没有去处理函数。关于null和循环引用也没有考虑,需要用到时可以去看看我后面贴出的参考文章里的代码。

性能问题

尽管使用深拷贝会完全的克隆一个新对象,不会产生副作用,但是深拷贝因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

清眉祭

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文