JS深浅拷贝

JS 深浅拷贝

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

1. 概述

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

2. 数据类型

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

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

3. 深浅拷贝

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

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

let a = 10;
let b = a;

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

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

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

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

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

深浅拷贝的示意图如下图:
JS深浅拷贝_第2张图片

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

4、js中的深浅拷贝

4.1 浅拷贝

4.1.1 slice()

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()

 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)

JS深浅拷贝_第3张图片

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

4.2 深拷贝

4.2.1 ES6的展开语法

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对象
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'}

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

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);

JS深浅拷贝_第4张图片

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

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

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);

JS深浅拷贝_第5张图片

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


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)

JS深浅拷贝_第6张图片

你可能感兴趣的:(javascript,前端,开发语言)