zoukankan      html  css  js  c++  java
  • 谈谈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的浅拷贝和深拷贝方法还有很多,这里只是介绍了常见的几种。

  • 相关阅读:
    cestos7安装zookeeper
    Cestos7安装Elasticsearch5.4.3
    debian使用nginx创建静态文件存储
    tomcat优化
    tomcat停止和启动脚本
    linux中shell变量$#,$@,$0,$1,$2的含义解释
    redis设置bind
    Jenkins send build artifacts over ssh配置
    nginx 负载均衡5种配置方式
    Jenkins tomcat打包启动脚本,待完善
  • 原文地址:https://www.cnblogs.com/jett-woo/p/12517218.html
Copyright © 2011-2022 走看看