谈谈JS中的浅拷贝和深拷贝

深拷贝和浅拷贝的起源

  • Js变量包含两种不同数据类型的值:基本类型和引用类型
  1. 基本类型指的是简单的数据段,包括ES6中新增的一共是6种:number、string、boolean、null、undefined、symbol
  2. 引用类型值指的是那些可能由多个值构成的对象,只有一种:object

  在将一个值赋值给变量时,解析器必须确定这个值是基本类型值还是引用类型值。基本类型是按值访问的,因为可以操作保存在变量中的实际的值。

  引用类型的值是保存在内存中的对象。与其他语言不同,JS不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象

  • Js变量的存储方式----栈(stack)和堆(heap)
  1. 栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
  2. 堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
  • Js值传递与引用传递

  基本类型与引用类型最大的区别就是传值与传地址的区别

  1. 值传递:基本类型采用的是值传递
  2. 地址传递:引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量

问题引出

通过上面的描述,我们了解到对象类型在赋值的过程中其实是复制了地址,从而会导致在改变了一方其他也会被改变的情况。通常在开发过程中我们不希望出现这样的问题

let a = {
  age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
View Code

浅拷贝

浅拷贝解决思路就是先设置一个新的对象obj2,通过遍历的方式将obj1对象的值一一赋值给新对象

let obj1 = {
  us: 'jett',
  address: {
    city: 'hz'
  },
  age: 22
}

let obj2 = {}
for(let key in obj1) {
  obj2[key] = obj1[key]
}

console.log(obj2) // {us: "jett", address: {city: "hz"}, age: 22}

obj2.address.city = 'jx'
console.log(obj1) // {us: "jett", address: {city: "jx"}, age: 22}
// 此时obj1的address内的city也变了
View Code

通过上面的代码可以看出,浅拷贝只能实现一层的改变,如果obj1中的属性为一个对象,此时拷贝的是地址,所以并不是深拷贝。

还有一种我们可以通过Object.assign来实现浅拷贝,Object.assign只会拷贝所有的属性值到新的对象,如果属性值是对象的话,拷贝的是地址。

let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
View Code

另外还可以通过扩展运算符...来实现浅拷贝

通常浅拷贝可以解决大部分的问题,但是当我们想拷贝属性为对象的值时,就要使用深拷贝了。

深拷贝

深拷贝,就是能够实现真正意义上的数组和对象的拷贝,我们可以用过递归调用浅拷贝的方式来实现

递归实现:(很多边缘情况,以及特殊的值暂未考虑)

let obj1 = {
  us: 'jett',
  address: {
    city: 'hz'
  },
  age: 22
}

let obj2 = {}
function deepCopy(obj1, obj2) {
  for(let key in obj1) {
    if(typeof obj1[key] === 'object'){
      console.log('-----')
      obj2[key] = {}
      deepCopy(obj1[key], obj2[key])
    }else {
      obj2[key] = obj1[key]
    }
  }
}
deepCopy(obj1, obj2)
console.log(obj2) // {us: "jett", address: {city: "hz"}, age: 22}

obj2.address.city = 'jx'
console.log('obj1', obj1) // {us: "jett", address: {city: "hz"}, age: 22}
// 此时obj1的address内的city没变,实现了深拷贝
View Code

也可以通过JSON.parse(JSON.stringify(object))来解决

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
View Code

但是该方法有局限性

  • 会忽略undefined
  • 会忽略symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}
View Code

但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决大部分问题。

自己实现一个深拷贝是很困难的,需要我们考虑好多边界情况,比如原型链如何处理、DOM如何处理等,这里实现的深拷贝只是一个简易版

function deepClone(obj) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非对象')
  }

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj }
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })

  return newObj
}
View Code

对于JS的浅拷贝和深拷贝方法还有很多,这里只是介绍了常见的几种。

原文地址:https://www.cnblogs.com/jett-woo/p/12517218.html