基于最日常的代码教你秒懂深浅拷贝(附:深拷贝手写实现)

2,107 阅读4分钟
1.前言:深浅拷贝“远”吗?

说起JavaScript的深浅拷贝,很多前端童靴,特别是新童靴的感觉就是这个知识点很重要,面试必问,但真正理解其切有点难度;手写也只是一味死记硬背,并不知其到底为何如此实现。本着知其然知其所以然的学习态度,咱们就从最日常的代码中来讲解它。其实,在我们日常开发中,‘使用’或者说是‘临近’深浅拷贝,出现频率还是很大的,只是你并未发现而已。其中,最常见的就是Object.assign()JSON.parse(JSON.stringify(xxx))。是不是很意外?别慌,到底怎么回事,咱们继续往下看。

2.JavaScript数据类型:

既然是解析JavaScript的深浅拷贝,那么必离不开其‘源头’,那就是JS的两大数据类型----基本数据类型引用数据类型

  1. 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt。
  2. 引用数据类型:Object。

image.png

3.JavaScript两大数据类型的特点:
  1. 基本数据类型的特点:数据直接存储在栈中

image.png

  1. 引用数据类型的特点:存储在栈中的是对象的引用地址,而真实的数据存放在堆内存中

image.png

4.何为深浅拷贝?

狭义上的深浅拷贝只是针对引用数据类型而言的,基本数据类型只有赋值操作。

  • 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
  • 深拷贝:会创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改到原对象
  1. 基本数据类型: image.png

  2. 引用数据类型: image.png

5.JavaScript之Object.assign()。

Object.assign({},obj):这就是文章开头所提及的我们日常开发中常用的一种涉及‘深浅拷贝’的方式。当然,当obj是单层对象时,此时的Object.assign()能实现“深拷贝”;而当obj为多层对象时,那么Object.assign({},obj)只能视为‘浅拷贝’。看以下案例:

  • 单层对象--‘深拷贝’:

image.png

  • 多层对象--‘浅拷贝’:

image.png

6.JavaScript之JSON.parse(JSON.stringify(xxx))。

JSON.parse(JSON.stringify(xxx)):这是我们日常开发中所常用的另一种能实现‘深浅拷贝’的方案。不过这种组合能实现普通对象(function,Symbol(),undefined这三种除外)的深拷贝;而实现function,Symbol(),undefined这三种时,会造成数据丢失,故也称不上是完美的深拷贝。

image.png

7.深拷贝的实现思想。

主要实现思想:通过递归思维,遍历所需进行深拷贝的对象,直到里边的都是基本数据类型,然后再进行赋值,就是深度拷贝。

8.深拷贝的手写实现。
  1. 方法一:适合常用对象。
    //定义检测数据类型的功能函数   

function checkedType(target) {

    return Object.prototype.toString.call(target).slice(8, -1)

}

//实现深度克隆---对象/数组 

function deepClone(target) {

    //判断拷贝的数据类型   

    //初始化变量result 成为最终克隆的数据     

    let result, targetType = checkedType(target)  //这里的意思是相当于let result; let 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] = deepClone(value)

        } else { //获取到value值是基本的数据类型或者是函数。

            result[i] = value;

        }

    }

    return result

}

  1. 方法二:适合通用对象。
// 深拷贝:对对象内部进行深拷贝,支持 Array、Date、RegExp、DOM

    function deepCopy(params) {

      // 如果不是对象则退出(可停止递归)

      if (typeof params !== 'object'return;

      // 深拷贝初始值:对象/数组

      let newObj = (params instanceof Array) ? [] : {};

      // 使用 for-in 循环对象属性(包括原型链上的属性)

      for (let i in params) {

        // 只访问对象自身属性

        if (params.hasOwnProperty(i)) {

          // 当前属性还未存在于新对象中时

          if (!(i in newObj)) {

            if (params[i] instanceof Date) {

              // 判断日期类型

              newObj[i] = new Date(params[i].getTime());

            } else if (params[i] instanceof RegExp) {

              // 判断正则类型

              newObj[i] = new RegExp(params[i]);

            } else if ((typeof params[i] === 'object') && params[i].nodeType === 1) {

              // 判断 DOM 元素节点

              let domEle = document.getElementsByTagName(params[i].nodeName)[0];

              newObj[i] = domEle.cloneNode(true);

            } else {

              // 当元素属于对象(排除 Date、RegExp、DOM)类型时递归拷贝

              newObj[i] = (typeof params[i] === 'object') ? deepCopy(params[i]) : params[i];

            }

          }

        }

      }

      return newObj;

    }

9.结语。

以上就是本文的全部内容,基于日常开发中最常用的案例角度来讲解了JavaScript深浅拷贝的前因后果以及深拷贝手写的实现。相信看完本文,各位童靴必定对深浅拷贝有更深的理解和认知,学会手写深拷贝的同时,也能真正做到‘知其然知其所以然’。