JS中深拷贝和浅拷贝

1、栈和堆

JS引擎中对变量的存储主要有两种,栈内存堆内存

  • 栈内存:主要存储JS中的基本数据类型的变量,包括String、Number、Boolean、undefined、Null、 Symbol和对象变量的指针。栈内存中的变量一般都是已知大小或者有范围上限的,所以栈内存都是自动分配内存和自动释放内存。
  • 堆内存:主要存储JS中的引用数据类型,包括Object、Array、function,他们的地址指针是存储于栈中的。堆内存中的变量大小一般是不知道的,所以堆内存是动态分配内存,内存大小不一,也不会自动释放内存。

从栈和堆的概念我们也可以分析出const 、let 定义的变量不能二次定义了?每次使用const或者let 去初始化一个变量的时候,会首先遍历当前的栈内存,看看有没有重名的变量,有的话就返回错误。
const 定义的基本数据类型不能修改,而const定义的引用数据类型就可以修改了? 因为我们定义一个const对象的时候,我们说的常量其实是栈中的指针,就是const 对象对应的堆内存的这个指针是不变的,但是堆内存中的数据本身的大小或者属性是可变的,而对于const定义的基础变量而言,这个值就相当于const对象的指正,是不可变的。

// const 定义的对象
function fn(){
  const c={name:'9'}
  c.age='20'
  c.name="90"
  console.log("数据",c)
}
fn() // 数据 {name: "90", age: "20"}
//const 定义的基础数据类型
function fn(){
 const a = 9
 a = 10
return a 
}
fn() //VM1949:3 Uncaught TypeError: Assignment to constant variable.

2、赋值和浅拷贝的区别

  • 赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
  • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

看个例子:

// 给对象赋值
var obj1={
	name:'张三',
	age:400,
	like:{paly:'玩'}
}
var obj2 = obj1
obj2.name='李四'
obj2.like.paly="吃"
console.log(obj1,obj2)

在这里插入图片描述

// 浅拷贝
var obj1={
	name:'张三',
	age:400,
	like:{paly:'玩'}
}
function copy(obj){
 var objs={}
 for(var item in obj){
   objs[item]=obj[item]
  }
return objs
}
var obj3 = copy(obj1)
obj3.name="老张"
obj3.like.paly="满"
console.log(obj1,obj3)

在这里插入图片描述
从上述例子我们可以得出,赋值得到的新对象obj2改变原始对象obj1也会发生改变,浅拷贝出来的新对象obj3,只要第一层是基本数据类型的值,新对象改变它,原始对象不会发生改变,但是原始对象的第一层属性是个对象的话,obj3改变这个对象,原始对象的数据也会发生改变。所以得出 浅拷贝只能拷贝第一层基本数据类型的值,改变他们的值不会影响原始对象,但是原始对象中含有对象的话,改变新对象中的对象,原始对象中的对象也会发生改变

3、浅拷贝 和 深拷贝 的实现

浅拷贝和深拷贝只针对Object 和Array这样的引用数据类型的。浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共指向一块内存,新对象改变旧对象也会随着改变。但是深拷贝会另外创建一个一模一样的对象,新对象跟旧对象不共享一块内存,所以修改新对象,旧对象也不会发生改变

(1)浅拷贝的实现方式
  • Object.assign()
    Object.assign()可以把任意多个的源对象自身的可枚举的属性拷贝拷贝给目标对象,然后返回目标对象,但是Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。但是如果对象只有一层的时候,是深拷贝
var obj3={
	name:'张三',
	age:400
}
var objAssign = Object.assign(obj3)
objAssign.name='李四'
objAssign.age = 900
console.log(obj3) // {name:'张三',age:400}
console.log(objAssign) // {name:'李四',age:900}
  • Array.prototype.concat()
    这个方方法是将对个数组组合成一个新数组,返回一个新数组,跟Object.assign()类似,只拷贝引用不拷贝对象本身。
let arr = [1, 3, {
    username: '科比'
    }];
let arr2=arr.concat();    
arr2[2].username = '我的';
console.log(arr);

在这里插入图片描述

var arr1=[1,2,34,4]
var arr2 = [99999]
var arr3 = arr1.concat(arr2)
arr3[2] = 88888

在这里插入图片描述

  • Array.prototype.slice()
    slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
let arr = [1, 3, {
    username: ' 科比'
    }];
let arr3 = arr.slice();
arr3[2].username = '我的'
console.log(arr);

在这里插入图片描述

(2)深拷贝的实现方式
  • JSON.parse(JSON.stringify())
    用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
	let arr = [666, 888, {
	    username: ' 999'
	}];
	let arr2 = JSON.parse(JSON.stringify(arr));
	arr2[2].username = '100'; 
	console.log(arr, arr2)

在这里插入图片描述
缺点: 不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不在是正则(变为空对象),得到的函数不在是函数(变为null)

let arr = [1,2,{name:'嘿嘿'},function(){}]
let arr3 = JSON.parse(JSON.stringify(arr))
arr3[2].name = '哈哈哈'
console.log(arr, arr3)

在这里插入图片描述
这是因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。

  • 手写递归的方法
    递归实现深度克隆的原理:遍历对象、数组知道里边都是基本数据类型,然后再去复制,就是深度拷贝。
//定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
//实现深度克隆---对象/数组
function clone(target) {
  //判断拷贝的数据类型
  //初始化变量result 成为最终克隆的数据
  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') {
      //对象/数组里嵌套了对象/数组
      //继续遍历获取到value值
      result[i] = clone(value)
    } else {
      //获取到value值是基本的数据类型或者是函数。
      result[i] = value
    }
  }
  return result
}

  • 函数库lodash
    该函数库也有提供_.cloneDeep用来做 Deep Copy
var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

本文参考文章:
浅拷贝与深拷贝
JS中的栈内存和堆内存
觉得他们写的很好,很容易懂,记录下来以防自己忘记。

猜你喜欢

转载自blog.csdn.net/diwang_718/article/details/106567508