浅拷贝与深拷贝

 

浅拷贝与深拷贝

在聊浅拷贝与深拷贝之前,我们先来聊一聊JavaScript中的数据类型,以及栈和堆。 首先我们先来看个例子

// 例1
let a = 1
let b = a

b = 2
console.log(a)  // 1
console.log(b)  // 2

// 例2
let c = { name: 'simple', age: 25}
let d = c

d.name = 'Mr.simple'

console.log(c)  // { name: 'Mr.simple', age: 25}
console.log(d)  // { name: 'Mr.simple', age: 25}

例1中很符合我们所想的,修改变量b不会影响到变量a, 很符合常规逻辑,但是例2中,为什么我修改了变量d,变量c也受到影响了呢?我们慢慢来解释。

JavaScript数据类型

​ 首先JavaScript是一门动态、弱类型语言, 我们在编写代码的时候不需要指定变量的类型, JavaScript编译器会自动推断。目前,JavaScript总共有7种基本类型和一种引用类型。如下展示。

Boolean    只有true和false两个值
Null       只有一个值null
Undefined  一个没有被赋值的变量会有个默认值undefined变量提升的时的默认值也是undefined
Number     JavaScript中的数字类型基于IEEE75标准的双精度64位二进制格式的值-2^63 - 1  2^63 - 1
BigInt     JavaScript中一个新的数字类型可以用任意精度表示使用BigInt即使超出Number的安全范围限制       					  也可以安全的存储和操作
String     字符串
Symbol     符号类型是唯一的并且是不可修改的通常用来作为Object的key
Object     对象类型被看做是一组属性的集合

之所以把他们区分为两种不同的类型,是因为他们在内存中存储的位置不同。 基本类型存储在中, 引用类型存储在中。

为什么会有栈和堆之分

​ 当JavaScript执行的时候,会为每个函数(包括全局)生成一个执行上下文,大致结构如下。然后将执行上下文放入函数调用栈中,此时,会有一个栈针指向最新放入函数调用栈的执行上下文,当该函数执行完成以后,栈针向下移,此时,该执行上下文所占用的栈区就会被释放,当有新的执行上下文压入栈时,会直接覆盖,栈针上移。如此频繁的操作,当然是占用的内存越少,操作越快。

执行上下文

​ 变量环境和词法环境会存储函数中的变量,当代码执行的时候,遇到基本类型,会把基本类型放置在变量环境或词法环境中,当遇到引用类型的时候,会在堆内存中开辟一块空间,将变量放进去,并且把堆内存中的地址存储在变量环境或词法环境中。

​ 最终目的是让js执行速度加快,如此区分也有利于js的垃圾回收。

先来解释一下例题

当我们执行如上代码时, 代码存放方式如下

例题

从上图可以看出,当执b = a 时, 会在栈内存中分配一个和a一模一样的地址用来存储b, 此时a和b相互独立,当修改b,a不会跟着变化

我们看例2的内存分配图, 栈中的变量c只存储着堆中对象的引用,当执行d = c时,此时,变量d也只是复制了对象的引用,即c和d指向同一个对象,所有当修改d指向的对象时,c也会跟着变化。

浅拷贝

浅拷贝只是拷贝了对象的第一层属性, 所以可能还会与源对象有关联

浅拷贝实现

// 方法一
function shallowClone(target) {
  let obj = Array.isArray(target) ? [] : {}
  for(let key in target) {
    obj[key] = target[key]
  }
  return obj
}

// 方法二
Object.assign()

let a = {  name: 'simple',  friend: { name: 'xiaoming', age: 23 } }
const b = shallowClone(a)

示意图如下

浅拷贝

所以会导致如下情况,修改b.friend.name会间接修改a.friend.name

b.friend.name = 'xiaohong'
console.log(a.friend.name) // xiaohong

深拷贝

深拷贝是递归拷贝对象的所有属性,拷贝完成以后两个对象没有任何关系

function deepClone(target) {
  const cache = [] // cache的作用是缓存复制过的属性,防止引用自身的对象造成递归无限循环,栈溢出
  cache.push(target)
  let dest = Array.isArray(target) ? [] : {}
  for(let item in target) {
    if(typeof target[item] === 'object') {
      const index = cache.indexOf(target[item])
      if(index > -1) {
        dest[item] = cache[index]
      } else {
        dest[item] = deepClone(target[item])	
      }
    } else {
      dest[item] = target[item]
    }
  }
  return dest
}


let a = {  name: 'simple',  friend: { name: 'xiaoming', age: 23 } }
const b = deepClone(a)

示意图如下

深拷贝

所以当修改b.friend.name的时候不会影响到a.friend.name

b.friend.name = 'xiaohong'
console.log(a.friend.name) // xiaoming
console.log(b.friend.name) // xiaohong

以上,就是本人对JavaScript浅拷贝和深拷贝的理解,比较浅显 ^.^