【面试必问】前端的深拷贝和浅拷贝

919 阅读5分钟

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

更多文章在我的 Github 及个人公众号【全栈道路】上,欢迎观赏【一个不知名的足球狗的前端知识点】,如有受益,不要钱,小手点个Star。

阅读本文您将收获

  • 堆栈和数据类型的概念
  • 浅拷贝实现
  • 深拷贝实现

写在前面

深拷贝和浅拷贝只针对像 Object, Array 这样的复杂类型数据的。简单来说,浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。

举个栗子

let a=[0,1,2,3,4],
b=a;
a[0]=1;
console.log(a,b); // a: [1,1,2,3,4],b: [1,1,2,3,4]
  • WTF?明明b复制了a,为啥修改数组a,数组b也跟着变了

数据在内存中的存储

  • 基本类型-存储在栈内存中

1-基本类型存储.png

  • 引用类型-地址在栈内存中,值存储在堆内存中

3-引用类型存储.png

浅拷贝两种数据类型的存储

  • 当执行 let b=a 时,基本数据类型在栈内存会新开辟一个内存,所以在修改b时,两个内存中的数据不会相互影响

2-浅拷贝.png

  • 当执行 let b=a 时,引用数据类型在栈内存也会新开辟一个内存,但是数据的引用地址指向同一个

4-浅拷贝引用类型.png

  • 浅拷贝中当修改引用类型的值时,改变的只是引用类型堆内存中的值,但是两个数据的引用地址是指向同一个堆内存中的值,就会造成两个数据都会发生改变

5-修改浅拷贝引用类型.png

深拷贝数据类型的存储

  • 深拷贝时,新的引用数据类型除了在栈内存中开辟洗的空间外,还会在堆内存中开辟一个新的空间

6-深拷贝.png

  • 深拷贝的数据,修改后相互之间不会受影响

7-修改深拷贝引用类型.png

浅拷贝实现方式

  • 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响

赋值表达式 = (特制引用类型数据)

Object.assign

  • Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
let obj = { player: {name: "Kobe", age: ∞} };
let resObj = Object.assign({}, obj);
resObj.player.name = "James";
console.log(obj.player.name); // James
  • 注: 当拷贝对象层级只有一层时,Object.assign() 是深拷贝
let player = {
   name: 'Kobe'
};
let nextPlayer = Object.assign({}, player);
nextPlayer.name = 'James';
console.log(player); // {name: "Kobe"}

展开运算符(...)

  • Object.assign() 相同

Array.prototype.concat()

  • 修改数组后原数组也会更改。
let player = [1, 2, {
   name: 'Kobe'
}];
let nextPlayer=player.concat();    
nextPlayer[2].name = 'James';
console.log(player); // [1, 2, {name: 'James'}]

Array.prototype.slice()

  • 修改数组后原数组也会更改。
let player = [1, 2, {
   name: ' Kobe'
}];
let nextPlayer = player.slice();
nextPlayer[2].name = 'James'
console.log(player); // [1, 2, {name: 'James'}]

注意: Array.prototype.concat()Array.prototype.slice()处理数组的规则

  • 两个方法不修改原数组,只会返回一个浅拷贝了原数组中的元素的一个新数组。
  • 原数组的元素会按照下述规则拷贝:
    • 如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
    • 对于字符串、数字及布尔值来说,slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
let player = [1, 2, {
   name: ' Kobe'
}];
let nextPlayer = player.slice();
nextPlayer[1] = 1;
console.log(player, nextPlayer);
// 运行后会发现player[1]没有变。

深拷贝实现方式

  • 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响

  • 性能最快:JSON.parse(JSON.stringify(obj))

    • 具有循环引用的对象时,报错
    • 当值为函数或undefined时,无法拷贝
  • 递归进行逐一赋值

deepClone(obj) {
    let result;
    if (typeof obj == 'object') {
        result = isArray(obj) ? [] : {}
        for (let i in obj) {
            result[i] = isObject(obj[i])||isArray(obj[i])?deepClone(obj[i]):obj[i]
        }
    } else {
        result = obj
    }
    return result
}
function isObject(obj) {
    return Object.prototype.toString.call(obj) == "[object Object]"
}
function isArray(obj) {
    return Object.prototype.toString.call(obj) == "[object Array]"
}

写在最后

如果你觉得这篇文章对你有益,烦请点赞以及分享给更多需要的人!

欢迎关注【全栈道路】及微信公众号【全栈道路】,获取更多好文及免费书籍!
有需要【百度】&【字节跳动】&【京东】&【猿辅导】内推的也请留言哦,你将享受VIP级极速内推服务~

往期好文

提高开发效率的 Chrome 开发者工具高端使用技巧(一)

创建个性化的 Github 个人主页

微信 JS API 支付的实现

面试官问你<img>是什么元素时你怎么回答

特殊的JS 浮点数的存储与计算

[万字长文]百度和好未来面试经含答案 | 掘金技术征文

前端实用正则表达式&小技巧,一股脑全丢给你🏆 掘金技术征文|双节特别篇

冷门的 HTML tabindex 详解

几行代码教你解决微信生成海报及二维码

Vue3.0 响应式数据原理:ES6 Proxy

[前端面试]前端缓存问题看这篇,让面试官爱上你

如何优雅地画一条细线

[三分钟小文]前端性能优化-HTML、CSS、JS部分

[三分钟小文]前端性能优化-页面加载速度优化

[三分钟小文]前端性能优化-网络传输层优化