JS 深浅拷贝

JS 深浅拷贝

  • [1. 概述](#1. 概述)
  • [2. 数据类型](#2. 数据类型)
  • [3. 深浅拷贝](#3. 深浅拷贝)
    • [3.1 拷贝对象为基本数据类型](#3.1 拷贝对象为基本数据类型)
    • [3.2 拷贝对象中有引用数据类型](#3.2 拷贝对象中有引用数据类型)
  • 4、js中的深浅拷贝
    • [4.1 浅拷贝](#4.1 浅拷贝)
      • [4.1.1 slice()](#4.1.1 slice())
      • [4.1.2 concat()](#4.1.2 concat())
    • [4.2 深拷贝](#4.2 深拷贝)
      • [4.2.1 ES6的展开语法](#4.2.1 ES6的展开语法)
      • [4.2.2 JSON.parse(JSON.stringify(待拷贝对象))](#4.2.2 JSON.parse(JSON.stringify(待拷贝对象)))
      • [4.2.3 jQuery 中的 .extend (添加true就是深拷贝,不添加就是浅拷贝)](#4.2.3 jQuery 中的 .extend (添加true就是深拷贝,不添加就是浅拷贝))
      • [4.2.4 手写递归的方式来实现深拷贝](#4.2.4 手写递归的方式来实现深拷贝)

1. 概述

深拷贝与浅拷贝在其它语言中也经常被提及到,在实际项目开发过程中也常常需要区分当前使用的到底是深拷贝还是浅拷贝,有时候在该使用深拷贝的地方,我们使用了浅拷贝,会导致深藏不露的bug。

2. 数据类型

在探讨深浅拷贝之前,我们先梳理一下js中的数据类型,js的数据类型分为两类:基本数据类型和引用数据类型,前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中。

如下图所示,基本类型如number、string、boolean、Null和undefined等存储在栈内存中,而引用数据类型如Array、Object和函数等则是分别存储数据1的地址、数据2的地址和数据3的地址。

3. 深浅拷贝

3.1 拷贝对象为基本数据类型

js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是深拷贝。

javascript 复制代码
let a = 10;
let b = a;

a = 100;
console.log(a)  // 100
console.log(b)  // 10

修改其中一个变量的值,不会影响到另一个变量的值。

3.2 拷贝对象中有引用数据类型

浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了"指向堆内存的地址"。

深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。

深浅拷贝的示意图如下图:

总结

浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址

深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

4、js中的深浅拷贝

4.1 浅拷贝

4.1.1 slice()

javascript 复制代码
let arr1 = [1, 42, [3, 4]]
let arr1Copy = arr1.slice()
arr1Copy[0] = 10      // 修改基本数据类型开辟新的内存地址
arr1Copy[2][0] = 100  // 修改引用数据类型指向同一块内存地址
console.log(arr1)     // [1, 42, [100, 4]]
console.log(arr1Copy) // [10, 42, [100, 4]]

arr1中的元素1是基本数据类型,所以arr1Copy能够改变其值,而不影响arr1的值。

而[3, 4]是引用数据类型,arr1和arr1Copy指向同一块堆内存地址,所以这两个变量中3都变成了100。

4.1.2 concat()

javascript 复制代码
 let arr2 = ['cat', 'dog', 'pig', {'name': 'xia', 'age': 18}]
 let arr2Copy = [].concat(arr2)
 arr2Copy[2] = 'big pig'
 arr2Copy[3]['name'] = 'aa'
 console.log(arr2)
 console.log(arr2Copy)

类似的还有...扩展运算符、Array.from、Object.assign()方法。

4.2 深拷贝

4.2.1 ES6的展开语法

javascript 复制代码
let a = {
  name : '张三',
   age : '18'
}
let b = { ...a };
b.name = '李四';
console.log('a:',a);   // {name: '张三', age: '18'}
console.log('b:',b);   // {name: '李四', age: '18'}

4.2.2 JSON.parse(JSON.stringify(待拷贝对象))

万能转换器 JSON.parse(JSON.stringify(obj))深拷贝已有对象,它可以深拷贝多层级的,不用担心嵌套问题。

  • JSON.stringfy() 将对象序列化成json对象
  • JSON.parse() 反序列化------将json对象反序列化成js对象
javascript 复制代码
let obj = {
  name: 'wyc'
}
// 将对象序列化成json对象
let str = JSON.stringify(obj)
// 将json对象反序列化成js对象
let obj2 = JSON.parse(str)
// 修改拷贝的数据
obj2.name = 'wyc_ok'

console.log(obj);   // {name: 'wyc'}
console.log(obj2);  // {name: 'wyc_ok'}

但此拷贝的缺点,即没法拷贝内部函数

javascript 复制代码
let a = {
    name : '张三',
    age : '18',
    like(){
        console.log('喜欢唱歌、滑冰');
    }
}
let b =JSON.parse( JSON.stringify(a) );
b.name = '李四';
console.log('a:',a);
console.log('b:',b);

4.2.3 jQuery 中的 $.extend (添加true就是深拷贝,不添加就是浅拷贝)

$.extend( 是否开启深拷贝, 拷贝后的值, 要拷贝的对象 )

javascript 复制代码
let origin = [[1,1], 2, 3, 4];
let new_data = []

$.extend(true,new_data,origin)

new_data[0].push(5)

console.log('origin:',origin);
console.log('new_data:',new_data);

4.2.4 手写递归的方式来实现深拷贝

javascript 复制代码
const origin = {
  name : '张三',
   age : '18',
   like(){
       console.log('喜欢唱歌、滑冰');
   },
   a : [[1,1], 2, 3, 4]
}


function extend(origin, deep){
   // deep true  启动深拷贝
   // false  浅拷贝
   let obj = {}
   // 数组对象
   if(origin instanceof Array){
       // true 数组  obj 就得是数组
       obj = []
   }
   for(let key in origin){
       let value = origin[key]
       // 确定value是不是引用型,前提是deep 是true
       obj[key] = (!!deep && typeof value === "object" && value !== null) ? extend(value, deep) : value
   }
   return obj
}


const new_data = extend(origin, true)

new_data.a[0].push(6666)
console.log(origin)
console.log(new_data)
相关推荐
木西3 分钟前
Nest.js实战:构建聊天室的群聊与私聊模块
前端·后端·nestjs
数字人直播5 分钟前
跨境电商如何选择高转化率的AI数字人直播平台?
前端·后端
天生我材必有用_吴用6 分钟前
深入理解JavaScript设计模式之代理模式
前端
华洛26 分钟前
《从0到1打造企业级AI售前机器人——实战指南五:处理用户意图的细节实现!》
javascript·vue.js·node.js
掘金安东尼1 小时前
7个【宝藏工具】从智能画图到 SSL 自动化,一应俱全
前端·面试·github
zhangxingchao1 小时前
Flutter屏幕适配指南
前端
石小石Orz1 小时前
被对象嘲讽,写个网页护眼宝你都不会么
前端
江城开朗的豌豆1 小时前
🔥 Vue组件传值:小杨教你玩转父子组件通信
前端·javascript·面试
江城开朗的豌豆1 小时前
Vue组件花式聊天指南:6种传值妙招,总有一款适合你
前端·javascript·vue.js
黑土豆1 小时前
在Vue3项目中实现PDF文件解析与预览的完整实践
前端·javascript·vue.js