【2022前端进阶】赋值、浅拷贝和深拷贝(js / javascript)

目录

一、启言

二、那什么是浅拷贝和深拷贝?

三、数据类型、深浅拷贝的区别?

1、数据类型内存分配形式的不同

2、数据类型复制变量时的结果不同

3、深浅拷贝的深度不同

四、赋值和浅拷贝的区别?

五、浅拷贝的实现

六、深拷贝的实现

七、参考文章


一、启言

        赋值很简单,我们先了解一下什么是深浅拷贝。而在了解深浅拷贝之前,我们得再了解一下js中的数据类型和他们在内存中的分配形式。

        首先,js共有 8 种数据类型,其中大体可分为基本数据类型引用数据类型。ES6新增的Symbol 和 谷歌67版本中出现的bigInt——是指安全存储、操作大整数。

        基本数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt

        引用数据类型:Object(Object包含function函数、array数组、date日期等)


二、那什么是浅拷贝和深拷贝?

        举个栗子🌰 ,当我们想对一个引用数据类型(后面用Object代替)进行拷贝的时候,我们改变新的Object之后,原本的Object内的值也会发生改变,这说明我们拷贝的不够完全、不够深,所以叫浅拷贝。那么当我们完完全全的拷贝了这个Object时,这种方法就叫作深拷贝,

三、数据类型、深浅拷贝的区别?

        1、数据类型内存分配形式的不同

        基本数据类型是直接存储在栈(stack)中的数据

        引用数据类型是在栈中存有指针,实体数据存在堆中,通过栈中的指针找到堆中引用数据类型的实体

        2、数据类型复制变量时的结果不同

        基本数据类型在重新赋值给一个新的变量以后,2个变量是独立的,因为是把值拷贝了一份

        引用数据类型则是复制了一个指针,2个变量指向的值是该指针所指向的内容,一旦一方修改,另一方也会受到影响

        3、深浅拷贝的深度不同

        浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存

        深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

四、赋值和浅拷贝的区别?

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

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

         举一个数组(引用数据类型)简单的赋值例子:

const arr_1 = [1]
const arr_2 = arr_1
arr_1[0] = 2
console.log(arr_1, arr_2)
// [2] [2]
// arr_1内的值发生改变,arr_2也受到了影响

五、浅拷贝的实现

数组举例,前提:创建一个新的数组,而不是赋值

// 第 1 种方法,Array.from
const arr = [{ a: 1 }, { b: 2 }, { c: 3 }]
const newArr = Array.from(arr)

console.log(newArr) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
arr[0].a = 0
console.log(newArr) // [ { a: 0 }, { b: 2 }, { c: 3 } ]

举一反三,其他会返回一个 相同的 新数组 的方法也可以实现浅拷贝

// 第 2 种方法,比如slice
const arr_1 = [{ a: 1 }, { b: 2 }, { c: 3 }]
const newArr_1 = arr_1.slice()

console.log(newArr_1) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
arr_1[0].a = 0
console.log(newArr_1) // [ { a: 0 }, { b: 2 }, { c: 3 } ]
// 第 3 种方法,比如concat
const arr_2 = [{ a: 1 }, { b: 2 }, { c: 3 }]
const newArr_2 = arr_2.concat()

console.log(newArr_2) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
arr_2[0].a = 0
console.log(newArr_2) // [ { a: 0 }, { b: 2 }, { c: 3 } ]

浅拷贝对象

// 用Object.assign浅拷贝一个对象
const obj = {
  a: {
    b: 1
} };
const copy = Object.assign({}, obj);
console.log(copy) // { a: { b: 1 } }
obj.a.b = 2
console.log(copy) // { a: { b: 2 } }
// 用 ... 浅拷贝一个对象
const newObj = { ...obj }

六、深拷贝的实现

// 第 1 种方法,依赖函数库
import _ from 'lodash'
const obj_1 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
}
const new_obj_1 = _.cloneDeep(obj_1)
console.log(obj_1.b.f === new_obj_1.b.f)
// false
// 第 2 种方法,JSON.stringify() + JSON.parse()
const obj_2 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
}
const new_obj_2 = JSON.parse(JSON.stringify(obj_2))
console.log(obj_2.b.f === new_obj_2.b.f)
// false
// 第 3 种方法,手写递归
function deepClone(args) {
  const newArgs = args instanceof Array ? [] : {}
  // 对象object就深入,不是对象object就返回
  if (typeof args !== 'object') {
    return args
  } else {
    for (const i in args) {
      newArgs[i] = typeof args[i] == 'object' ? deepClone(args[i]) : args[i]
    }
    return newArgs
  }
}

const obj_3 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
}
const new_obj_3 = deepClone(obj_3)
console.log(obj_3.b.f === new_obj_3.b.f)
// false

⚠️  缺点:递归容易出现爆栈问题

⭐️  优化方案:

        1、将递归改为循环,就不会出现爆栈问题了

        2、使用hash递归

// 有可能碰到循环引用的问题 const a = {}; a.a = a; deepCopy(a) //会造成一个死循环
function isObject(obj) {
  return (
    Object.prototype.toString.call(obj) === '[object Object]' ||
    Object.prototype.toString.call(obj) === '[object Array]'
  )
}

function deepCopy_4(obj, hash = new Map()) {
  if (!isObject(obj)) return obj // 检测是否为对象或者数组
  let newObj = Array.isArray(obj) ? [] : {}

  if (hash.has(obj)) return hash.get(obj) // 解决循环引用问题
  hash.set(obj, newObj)

  console.log(obj) // <ref *1> { a: [Circular *1] }
  for (const i in obj) {
    console.log(i) // a
    console.log(obj[i]) // <ref *1> { a: [Circular *1] }
    console.log(isObject(obj[i])) // true
    if (isObject(obj[i])) {
      newObj[i] = deepCopy_4(obj[i], hash)
    } else {
      newObj[i] = obj[i]
    }
  }
  return newObj
}

        3、干净版

function isObject(obj) {
  return (
    Object.prototype.toString.call(obj) === '[object Object]' ||
    Object.prototype.toString.call(obj) === '[object Array]'
  )
}

function deepClone(obj, hash = new Map()) {
  // 判断类型
  if (!isObject(obj)) return obj
  let newObj = obj instanceof Array ? [] : {}

  // 解决循环引用问题
  if (hash.has(obj)) return hash.get(obj)
  hash.set(obj, newObj)

  // 进行拷贝
  if (obj instanceof Array) {
    newObj = obj
  } else {
    for (const key in obj) {
      newObj[key] = deepClone(obj[key], hash)
    }
  }
  return newObj
}

七、参考文章

        1、JS中8种数据类型 - 不知名前端李小白 - 博客园

        2、js深拷贝与浅拷贝一文彻底搞懂

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值