JavaScript浅拷贝和深拷贝

JavaScript浅拷贝与深拷贝

一、数据类型

数据分为基本数据类型(string, number, boolean, null, undefined,symbol等)和对象数据类型。

1、基本数据类型的特点:直接存储在栈中的数据

2、引用数据类型的特点:存储的是该对象在栈中引用,真实数据存放在堆内存里

在这里插入图片描述

二、浅拷贝与深拷贝

深拷贝和浅拷贝是只针对对象和数组这样的引用数据类型的

以下是深拷贝和浅拷贝的区别:

在这里插入图片描述

区别:浅拷贝只复制指向某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

三、赋值和浅拷贝的区别

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

浅拷贝会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

先来看两个例子.

  • 对象赋值
// 对象赋值
let obj1 = {
  name: 'zhangsan',
  age: '18',
  language': [1, [2, 3], [4, 5]],
};
let obj2 = obj1;
obj2.name = "lisi";//修改obj2,obj1也随之被修改
obj2.language[1] = ["二", "三"];

console.log('obj1', obj1);
console.log('obj2', obj2);

运行结果:
在这里插入图片描述

  • 浅拷贝
// 浅拷贝
function shallowCopy(src) {
  let dst = {};
  for (let prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}

运行结果:
在这里插入图片描述

浅拷贝只是拷贝了子对象的引用,所以执行obj2.a.a =“wade”;时,两个子对象的属性都发生了改变,想要实现子对象的拷贝需要使用深拷贝(后面会讲)

  • 结论:
    上面例子中,我们可以很清晰看到赋值和浅拷贝对原始数据不同的影响,具体请看下表:
    在这里插入图片描述

四、浅拷贝的实现方式

1. Object.assign()

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

let obj1 ={ a:{a:"kobe", b:39},b:"kobe"};
let obj2 = Object.assign({}, obj1);
obj2.a.a ="wade";
obj2.b="wade";
console.log(obj1,obj2);

在这里插入图片描述
注意:当object只有一层的时候,浅拷贝就等同于深拷贝

2. 展开运算符(推荐)

展开运算符是ES6里面的写法,写起来非常简洁

let obj1 = {
  username: 'kobe'
};
let obj2 = {...obj1};
obj2.username = 'wade';

console.log(obj1,obj2); 

运行结果:
在这里插入图片描述

五、深拷贝的实现方式(重点)

1. JSON.parse(JSON.stringify())

1.1概念与基本用法

先介绍两个概念:

  • 序列化:对象=>JSON字符串,通过JSON.stringify()实现
  • 反序列化:JSON字符串=>对象,通过JSON.parse()实现

利用 JSON.stringify 将js对象序列化(对象=>JSON字符串),再使用JSON.parse来反序列化(JSON字符串=>对象)js对象

基本用法:

let arr1 =[1,3,{username:' kobe'}];
let arr2 =JSON.parse(JSON.stringify(arr1));
arr1[2].username ='duncan';

console.log(arr1, arr2);

运行结果:
在这里插入图片描述
可以看到,对子对象的修改并没有改变原来的对象,这就是深拷贝

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象, 新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

1.2局限性

不过这种方式只适用于一般数据的拷贝(对象、数组),存在局限性,以下场景会出问题

  • 如果数组(对象)里面有时间对象,经过JSON.parse(JSON.stringify())后,时间将只是字符串的形式,而不是时间对象;
let arr1 =[1,3,{username:' kobe',birth:new Date()}];
let arr2 =JSON.parse(JSON.stringify(arr1));

console.log(arr1, arr2);

在这里插入图片描述

  • 如果数组(对象)里有RegExp、Error对象,则经过序列化反序列化的结果将只得到空对象;
let arr1 =[ new RegExp('\\w+'), new Error('error message')];
let arr2 =JSON.parse(JSON.stringify(arr1));

console.log(arr1, arr2);

运行结果:
在这里插入图片描述

  • 如果数组(对象)里有function或者undefined,则经过序列化反序列化的结果将中没有function和undefined(即null)
let arr1 =[1,3,{username:' kobe'},function(){},undefined];
let arr2 =JSON.parse(JSON.stringify(arr1));

console.log(arr1, arr2);

运行结果:
在这里插入图片描述

  • 如果数组(对象)里有对象是由构造函数生成的,则经过序列化反序列化之后会没有对象的 constructor;
function Person(name) {
   this.name = name;
}

let arr1 =[new Person('kobe') ];
let arr2 =JSON.parse(JSON.stringify(arr1));

console.log(arr1, arr2);

运行结果:
在这里插入图片描述
下面两种情况,实际项目不怎么会碰到,了解一下即可

  • 如果数组(对象)里有NaN、Infinity和-Infinity,则经过序列化反序列化的结果为null
       let arr1 =[ NaN,Infinity,-Infinity];
       let arr2 =JSON.parse(JSON.stringify(arr1));
       console.log(arr1, arr2);

运行结果:
在这里插入图片描述

  • 如果对象中存在循环引用的情况,则序列化反序列化直接报错(无法深拷贝)
      let obj1 ={name:'kobe'};
      obj1.obj1 = obj1;
      let obj2 =JSON.parse(JSON.stringify(obj1));
      console.log(obj1, obj2);

运行结果:
在这里插入图片描述

1.3总结:
  • 如果拷贝的对象不涉及上面的情况,可以使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝

2. 手写递归方法

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

// 定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}//这个函数下文会有解释

function depCopy(target) {// 实现深拷贝
  let result,
  targetType = checkedType(target) ;// 判断拷贝的值
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else { // 如果不是对象或数组就直接返回
    return target
  }
  // 遍历目标数据
  for (let i in target) {
    let value = target[i]
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') { 
      result[i] = depCopy(value)
    } else {
      // 获取到value值是基本的数据类型或者是函数。
      result[i] = value;
    }
  }
  return result
}

解释一下这个类型判断函数

Object.prototype.toString()该方法返回描述某个对象数据类型的字符串如:

var toString = Object.prototype.toString;
toString.call([1,2,3]);//[object Array]
toString.call({name:kobe});//[object Object]

Object.prototype.toString.call(obj).slice(8,-1)返回的就是这个obj对象的类型

slice(startIndex,endIndex),从0开始索引,其中8代表从第8位(包含)开始截取,-1代表截取到倒数第一位(不含),如[object Object],正好会截取到[object Object]中的Object。

3. lodash工具库(推荐)

在实际项目开发中,推荐使用lodash工具库,节省时间,当然也可以用jquery,underscore等其它的.

导入

1.官网下载:https://www.lodashjs.com

2.NPM

npm i -g npm
npm i -S lodash

使用_.cloneDeep函数 用来做深拷贝。

import  _  from 'lodash'

let  _ = require('lodash');

let obj1 = {
a: 1,
b: {
     f: {
         g: 1
     }
},
c: [1, 2, 3]
};
let obj2 = _.cloneDeep(obj1); //直接使用就行
console.log(obj1.b.f === obj2.b.f); // false,深拷贝成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值