欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数组的深拷贝与浅拷贝

程序员文章站 2024-03-21 16:58:52
...

JS数据类型

基本类型

Number、Boolean、String、undefined、Null。

变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

引用类型

 Function,Array,Object------技术对象系列,typeof()这个三种类型得到的都是object

存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

基本类型都放在栈(图左)(stack)里
引用类型都放在堆(图右)(heap)里

数组的深拷贝与浅拷贝

 

浅拷贝与深拷贝

通常我们拷贝一个变量的值给另一个变量时,会直接使用"="赋值。

但是,当我们拷贝一个引用类型时,经常会出现如下情况:

let arr1 = [1,2,3];
let arr2 = arr1;
console.log(arr1);  // [1,2,3]
console.log(arr2);  // [1,2,3]

arr1[2] = 4;
console.log(arr1);  // [1,2,4]
console.log(arr2);  // [1,2,4]

arr2[2] = 5;
console.log(arr1);  // [1,2,5]
console.log(arr2);  // [1,2,5]

我们发现,只要改变一个变量的值,另一个变量的值也会改变。这是因为两个变量的值指向同一个堆内存的数据,使用‘=’赋值之只是将一个变量对象的引用(指针)赋值给另一个变量。

基本类型变量的值都存储在栈(stack)中,所以浅拷贝与深拷贝只针对引用类型有所区分。

 

浅拷贝

浅拷贝:只是复制了对象的引用地址,没有两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。

也就是拷贝对象里面的数据,但是没有完全将子对象的数据全部拷贝。

浅拷贝的实现

1.引用复制(遍历拷贝-拷贝一层)

//对象遍历拷贝
function clone(copyObj) {
  let obj = {};
  for ( let i in copyObj) {
    obj[i] = copyObj[i];      //使用‘=’直接赋值,没有深度拷贝,子对象可能是object
  }
  return obj;
}
let x = {
  a: 1,
  b: { d: 1 },
  c: [ 1, 2, 3 ]
};
let y = clone(x);
x.a = 2;
x.b.d = 2;
x.c[0] = 2;
console.log(x.a);     // 2
console.log(y.a);     // 1
console.log(x.b);     // {d: 2}
console.log(y.b);     // {d: 2}
console.log(x.c);     // [ 2, 2, 3 ]
console.log(y.c);     // [ 2, 2, 3 ]


//数组遍历拷贝

function copy (array) {
   let newArray = []
   for(let item of array) {
      newArray.push(item);
   }
   return  newArray;
}
let arr1 = [1,{a:1},[ 1, 2, 3 ]];
let arr2 = copy(arr1);
arr1[0]= 2;
arr1[1].a= 2;
arr1[2][1]= 2;
console.log(arr1[0]);    // 2
console.log(arr2[0]);    // 1
console.log(arr1[1]);    // {a: 2}
console.log(arr2[1]);    // {a: 2}
console.log(arr1[2]);    // [ 2, 2, 3 ]
console.log(arr2[2]);    // [ 2, 2, 3 ]

2.Object.assign() (对象浅拷贝-拷贝一层)   

Object.assign():用于对象的合并,将源对象的所有可枚举属性,复制到目标对象,并返回合并后的target

用法: Object.assign(target, source1, source2);  

let x = {
  a: 1,
  b: {d: 1},
  c: [ 1, 2, 3 ]
};
let y = Object.assign({}, x);
console.log(x.a);   // 1
console.log(y.a);   // 1
console.log(x.b);   // {d: 1}
console.log(y.b);   // {d: 1}
console.log(x.c);   // [ 1, 2, 3 ]
console.log(y.c);   // [ 1, 2, 3 ]

x.a = 2;
x.b.d = 2;
x.c[0] = 2;
console.log(x.a);   // 2
console.log(y.a);   // 1
console.log(x.b);   // {d: 2}
console.log(y.b);   // {d: 2}
console.log(x.c);   // [ 2, 2, 3 ]
console.log(y.c);   // [ 2, 2, 3 ]

3.slice() 和 concat() (数组浅拷贝-拷贝一层)

(1) slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)

用法:array.slice(start,end) start表示是起始元素的下标,end表示的是终止元素的下标

当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组

let arr1 = [1,{a:'a'},[ 1, 2, 3 ]];
let arr2 = arr1.slice();
console.log(x.a);   // 1
console.log(y.a);   // 1
console.log(x.b);   // {d: 1}
console.log(y.b);   // {d: 1}
console.log(x.c);   // [ 1, 2, 3 ]
console.log(y.c);   // [ 1, 2, 3 ]

x.a = 2;
x.b.d = 2;
x.c[0] = 2;
console.log(x.a);   // 2
console.log(y.a);   // 1
console.log(x.b);   // {d: 2}
console.log(y.b);   // {d: 2}
console.log(x.c);   // [ 2, 2, 3 ]
console.log(y.c);   // [ 2, 2, 3 ]

(2)concat() 方法用于连接两个或多个数组。( 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。)

用法:array.concat(array1,array2,......,arrayN)

当concat()不带任何参数的时候,默认参数为空数组

let arr1 = [1,{a:'a'},[ 1, 2, 3 ]];
let arr2 = arr1.concat();
console.log(x.a);   // 1
console.log(y.a);   // 1
console.log(x.b);   // {d: 1}
console.log(y.b);   // {d: 1}
console.log(x.c);   // [ 1, 2, 3 ]
console.log(y.c);   // [ 1, 2, 3 ]

x.a = 2;
x.b.d = 2;
x.c[0] = 2;
console.log(x.a);   // 2
console.log(y.a);   // 1
console.log(x.b);   // {d: 2}
console.log(y.b);   // {d: 2}
console.log(x.c);   // [ 2, 2, 3 ]
console.log(y.c);   // [ 2, 2, 3 ]

4.扩展运算符(...)(扩展运算符浅拷贝-拷贝一层)

// 对象使用(...)扩展运算符
let x = {
  a: 1,
  b: {d: 1},
  c: [ 1, 2, 3 ]
};
let y = {...x};
console.log(x.a);   // 1
console.log(y.a);   // 1
console.log(x.b);   // {d: 1}
console.log(y.b);   // {d: 1}
console.log(x.c);   // [ 1, 2, 3 ]
console.log(y.c);   // [ 1, 2, 3 ]

x.a = 2;
x.b.d = 2;
x.c[0] = 2;
console.log(x.a);   // 2
console.log(y.a);   // 1
console.log(x.b);   // {d: 2}
console.log(y.b);   // {d: 2}
console.log(x.c);   // [ 2, 2, 3 ]
console.log(y.c);   // [ 2, 2, 3 ]


// 数组使用(...)扩展运算符
let arr1 = [1,{a:'a'},[ 1, 2, 3 ]];
let arr2 = [...arr1];
console.log(x.a);   // 1
console.log(y.a);   // 1
console.log(x.b);   // {d: 1}
console.log(y.b);   // {d: 1}
console.log(x.c);   // [ 1, 2, 3 ]
console.log(y.c);   // [ 1, 2, 3 ]

x.a = 2;
x.b.d = 2;
x.c[0] = 2;
console.log(x.a);   // 2
console.log(y.a);   // 1
console.log(x.b);   // {d: 2}
console.log(y.b);   // {d: 2}
console.log(x.c);   // [ 2, 2, 3 ]
console.log(y.c);   // [ 2, 2, 3 ]

深拷贝

深拷贝:在堆内存中开辟了一块内存地址用于存放复制的对象,并且将原对象的所有层级的数据都拷贝给另一个对象,形成两个独立的对象。也就是2个不同的内存地址,所以修改其中任意的值,另一个值都不会变化。

1.JSON对象的parse和stringify

JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象。

JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+)

通过JSON.stringify实现深拷贝有几点要注意

  1. 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
  2. 无法拷贝不可枚举的属性,无法拷贝对象的原型链
  3. 拷贝Date引用类型会变成字符串
  4. 拷贝RegExp引用类型会变成空对象
  5. 对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
  6. 无法拷贝对象的循环应用(即obj[key] = obj)
function Obj() {
    this.func = function () {
        alert(1) 
    };
    this.obj = {a:1};
    this.arr = [1,2,3];
    this.und = undefined;
    this.reg = /123/;
    this.date = new Date(0);
    this.NaN = NaN
    this.infinity = Infinity
    this.sym = Symbol(1)
}
var obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{
    enumerable:false,
    value:'innumerable'
})
console.log('obj1',obj1);
var str = JSON.stringify(obj1);
var obj2 = JSON.parse(str);
console.log('obj2',obj2);

拷贝信息如下:

数组的深拷贝与浅拷贝