【学习笔记】JavaScript – 深浅拷贝

拷贝的问题主要是针对引用类型

浅拷贝和深拷贝区别

对于这个问题,首先让我们先简单回顾一下 JavaScript 的基本知识

1、JavaScript 包含两种不同数据类型的值:基本类型(原始值)引用类型

基本类型有以下几种,具体如下:
string、number、boolean、null、undefined、symbol、bigInt

引用类型具体有:
Object(Object、Array、Function…)

在将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型

  • 基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值
  • 引用类型的值是保存在内存中的对象,栈内存存储的是变量的标识符以及对象在堆内存中的存储地址。JavaScript 不允许直接访问内存中的位置,即不能直接操作对象的内存空间。因此在操作对象时实际上是对操作对象的引用而不是实际的对象。当需要访问引用类型(如对象、数组等)的值时,首先从栈中获得该对象的地址指针,然后再从对应的堆内存中取得所需的数据

2、JavaScript 的变量存储方式 — 栈(stack)堆(heap)

  • :自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址指针
  • :动态分配内存,大小不定,也不会自动释放,里面存放引用类型的值

image.png

3、JavaScript 值传递与址传递
基本类型与引用类型最大的区别实际就是传值与传址的区别

  • 值传递:基本类型采用的是值传递
let a = 1;
let b = a;
b++;
console.log(a, b) // 1, 2
复制代码
  • 址传递:引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量
let a = ['a', 'b', 'c'];
let b = a; 
b.push('d');
console.log(a) // ['a', 'b', 'c', 'd']
console.log(b) // ['a', 'b', 'c', 'd']
复制代码

分析:

  • a 是数组是引用类型,赋值给 b 就是将 a 的地址赋值给 b,因此 a 和 b 指向同一个地址(该地址都指向了堆内存中引用类型的实际的值)
  • 当 b 改变了这个值的同时,因为 a 的地址也指向了这个值,故 a 的值也跟着变化,就好比 a 租了一间房,将房间的地址给了 b,b 通过地址找到了房间,那么 b 对房间做的任何改变对 a 来说肯定同样是可见的

那么如何解决上面出现的问题,这里就引出了浅拷贝或者深拷贝了。JS 的基本类型不存在浅拷贝还是深拷贝的问题,主要是针对引用类型

浅拷贝:拷贝的级别浅。浅拷贝是指复制对象时只对第一层键值对进行复制,若对象内还有对象则只能复制嵌套对象的地址指针

  • 缺点:当有一个属性是引用值(数组或对象)时,按照这种克隆方式,只是把这个引用值的指向赋给了新的目标对象,即一旦改变了源对象或目标对象的引用值属性,另一个也会跟着改变

深拷贝:拷贝级别更深。深拷贝是指复制对象时是完全拷贝,即使嵌套了对象,拷贝后两者也相互不影响,修改一个对象的属性不会影响另一个。原理其实是递归把那些值是对象的属性再次进入对象内部进行复制

浅拷贝

sliceconcat
若是数组,数组元素均为基本数据类型,可利用数组的一些方法如 sliceconcat 返回一个新数组的特性来实现拷贝(此时相当于深拷贝)

若数组的元素是引用类型(Object,Array),sliceconcat 对对象数组的拷贝还是浅拷贝,拷贝之后数组各个元素的指针还是指向相同的存储地址

let arr = ['one', 'two', 'three'];
let newArr = arr.concat();
newArr.push('four')

console.log(arr)    // ["one", "two", "three"]
console.log(newArr) // ["one", "two", "three", "four"]

let arr = ['one', 'two', 'three'];
let newArr = arr.slice();
newArr.push('four')

console.log(arr)    // ["one", "two", "three"]
console.log(newArr) // ["one", "two", "three", "four"]

let arr = [{a:1}, 'two', 'three'];
let newArr = arr.concat();
newArr[0].a = 2;

console.log(arr)    // [{a: 2},"two","three"]
console.log(newArr) // [{a: 2},"two","three"]
复制代码

Object assign()
该方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。Object assign() 对对象的拷贝还是浅拷贝

let arr = {
  a: 'one', 
  b: 'two', 
  c: 'three'
};

let newArr = Object.assign({}, arr)
newArr.d = 'four'
console.log(arr);    // {a: "one", b: "two", c: "three"}
console.log(newArr); // {a: "one", b: "two", c: "three", d: "four"}

let arr = {
  a: 'one', 
  b: 'two', 
  c: {a: 1}
};

let newArr = Object.assign({}, arr)
newArr.c.a = 3;
console.log(arr);    // {a: "one", b: "two", c: {a: 3}}
console.log(newArr); // {a: "one", b: "two", c: {a: 3}}
复制代码

浅拷贝封装

原理:遍历对象,然后把属性和属性值放在一个新对象并返回

function clone(obj) {
    // 只拷贝对象
    if (typeof src !== 'object') return;
    // 根据 obj 的类型判断是新建一个数组还是对象
    let newObj = Obejct.prototype.toString.call(obj) == '[object Array]' ? [] : {};
    for(let prop in newObj) {
        if(newObj.hasOwnProperty(prop)) {
            newObj[prop] = obj[src];
        }
    }
    return newObj;
}
复制代码

深拷贝

JSON.parse(JSON.stringify(arr)):不仅适用于数组还适用于对象,但该方法有局限性

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

ES6 扩展运算符[...]:不仅适用于数组还适用于对象,只有原始值可以深拷贝,当含有引用值时进行浅拷贝

lodash 的深拷贝函数

深拷贝封装

原理:在拷贝时判断一下属性值的类型,若是对象则递归调用深拷贝函数,深拷贝是完全拷贝了原对象的内容并寄存在新的内存空间,指向新的内存地址

function deepClone(src, target) {
    var target = target || {};
    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
            if(src[prop] !== 'null' && typeof(src[prop]) === 'object') {
                target[prop] = Object.prototype.toString.call(src[prop]) == '[object Array]' ? [] : {};
                deepClone(src[prop], target[prop]);
            } else {
                target[prop] = src[prop];
            }
        }
    }
    return target;
}
复制代码

库实现

上面的方式可以满足基本的场景的需求,若有更复杂的需求可自己实现。一些框架和库的也有对应的解决方案,如:jQuery.extend()lodash

应用场景

浅拷贝
对于一层结构的 ArrayObject 想要拷贝一个副本时使用
vuemixin 是浅拷贝的一种复杂型式

深拷贝
复制深层次的 object 数据结构,如想对某个数组或对象的值进行修改,但又要保留原数组或对象的值不被修改,此时就可以用深拷贝来创建一个新的数组或对象

参考资料

javascript中的深拷贝和浅拷贝?
JavaScript 如何完整实现深度Clone对象?
ithub lodash源码
MDN 结构化克隆算法
jQuery v3.2.1 源码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享