js深拷贝浅拷贝与lodash


title: js深拷贝浅拷贝与lodash date: 2022/9/27 14:46:25 categories:

  • lodash
  • js入门

深拷贝和浅拷贝

ref:JS传递参数时的内部存储逻辑

JS 變數傳遞探討:pass by value 、 pass by reference 還是 pass by sharing?

在这个问题前,有另一个问题就是Array类型的拷贝。

怎么样进行Array的深拷贝

这个问题是我在leetcode时候发现的,其实就在于,当我实现迭代的时候,函数需要改变参数目的地址的实际内容,这时候就会在想对于Array的储存结构是怎么样的,修改函数内部是否会一同影响外部的变量呢,还是说只是单纯地操作到值上,并没有对初始的内存地址进行变更。

ref: https://dev.to/samanthaming/how-to-deep-clone-an-array-in-javascript-3cig

首先对于 array,显然是浅拷贝

let array = [1, 2, 3]
let arr = array

arr[0] = 1999

console.log(array) //[1999, 2, 3]

可以使用解析式一种方式去clone数组

let array = [1, 2, 3]
let arr = [...array]

arr[0] = 1999

console.log(array) //[1, 2, 3]

而这种方法会在嵌套数组当中不起效

let nestedArray = [1, [2], 3];
let arrayCopy = [...nestedArray]; 

// Make some changes
arrayCopy[0] = ''; // change shallow element
arrayCopy[1][0] = ''; // change nested element
console.log(arrayCopy); // [ '', [ '' ], 3 ]

// ❌ Nested array got affected
console.log(nestedArray); // [ 1, [ '' ], 3 ]

当我们改动嵌套数组的时候,源数组也会收到affacted。因此我们可以通过这样的方式对数组进行深拷贝

let nestedArray = [1, [2], 3];
let arrayCopy = JSON.parse(JSON.stringify(nestedArray)); 

// Make some changes
arrayCopy[0] = ''; // change shallow element
arrayCopy[1][0] = ''; // change nested element
console.log(arrayCopy); // [ '', [ '' ], 3 ]

// ✅ Nested array NOT affected
console.log(nestedArray); //  1, [ 2 ], 3 ]

但是 JSON.parse(JSON.stringify(nestedArray))

这样的方法并不是万能的,会导致这样的问题。比如说undefined类型会转化为null,DOM类型会转化成空对象,而Date类型会转化为String。

function nestedCopy(array) {
    return JSON.parse(JSON.stringify(array));
}

// undefineds are converted to nulls
nestedCopy([1, undefined, 2]) // -> [1, null, 2]

// DOM nodes are converted to empty objects
nestedCopy([document.body, document.querySelector('p')]) // -> [{}, {}]

// JS dates are converted to strings
nestedCopy([new Date()]) // -> ["2019-03-04T10:09:00.419Z"]》

JSON.stringify/parse only work with Number and String and Object literal without function or Symbol properties.

因此这个时候可以引入lodash,帮助我们去解决这个问题

const lodashClonedeep = require("lodash.clonedeep");

const arrOfFunction = [() => 2, {
    test: () => 3,
}, Symbol('4')];

// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));

// function and symbol are copied by reference in deepClone
console.log(lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]);
console.log(lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]);

当然,还有另一种方法则是嵌套拷贝


function deepCopy(obj) {
  var newobj = obj.constructor === Array ? [] : {};
  if (typeof obj !== 'object') {
    return obj;
  } else {
  for (var i in obj) {
    if (typeof obj[i] === 'object'){ //判断对象的这条属性是否为对象
      newobj[i] = deepCopy(obj[i]);  //若是对象进行嵌套调用
    }else{
        newobj[i] = obj[i];
        }
    }
    }
    return newobj; //返回深度克隆后的对象
}

var obj1 = {
    name: 'shen',
    show: function (argument) {
        console.log(1)
    }
}
var obj2 = deepCopy(obj1)

如果对于js的深浅拷贝该怎么解释呢

阅览,我十分赞同这么一个说法:不论是传引用还是传值,定义都可以归结为 传值

即: pass by value,为什么可以这么归结呢?

原始数据类型传递

对于js在储存结构里不变的Number、String等,在传参数的时候,属于之间传递值。

function test(primitiveData) {
  primitiveData = primitiveData + 5;
  console.log(primitiveData); // 10
}

let a = 5; // Primitive type data
test(a);

console.log(a); // 5 => 沒被改變

这个过程的执行顺序为

  1. 声明function test()
  2. 将数值5赋给primitiveData
  3. 将primitiveData累加5
  4. 因为primitiveData相当于一个新的对象,因此不会修改原来a的值

js深拷贝浅拷贝与lodash_第1张图片

引用数据类型传递

而对于引用类型,如Object、Array等,可以视作为传递引用。因为对于非原始数据类型,引用变量储存的数据是真实数据所在的地址,其所经历过的过程为。


function test(objectData) {
  objectData.number = 10; // 改變物件內容
  console.log(objectData); // { number: 10 }
}

let a = { number: 5 }; // Object data
test(a);

console.log(a); // { number: 10 } => 跟著改變
  1. 声明function test()
  2. 将对象a的数值地址传递objectData
  3. 修改对应数值地址的变量属性number为10
  4. 源对象a的数值同样也被修改

js深拷贝浅拷贝与lodash_第2张图片

拷贝过程的内存地址走向

为什么会有这样的问题呢?因为js在储存数据的时候,对于原始数据类型跟引用类型方式稍有不同。

在原始数据类型当中,这些value会存在栈中。因此,在使用=进行赋值的时候,会直接将栈区的数据复制一份。而对于引用类型,栈区则是会存储其处于堆区,因此在传值的时候,则是会把堆区这一块数据地址传递进去。

所以,这也是为什么说将Object引用类型传递会与源数据共享一个地址。

三点(…)运算符 & Object.assign

有很多方式可以去解决深拷贝的问题,最简单的可以使用JSON库的序列化和反序列进行,不过这种方式会对比如说DOM类型,undefined类型不友好。

而对于只有一级属性的时候,则是可以通过…运算符或者 Object.assign()

const foo = {
    number : 1
}

const foo2 = Object.assign({}, foo)
const foo3 = {...foo}
foo2.number = 10
foo3.number = 20

console.log(foo3) //{number: 20}
console.log(foo2) //{number: 10}
console.log(foo)  //{number: 1}

总结

我的想法是,对于JS而言,pass by reference和pass by value都可以视为pass by value,因为在进行赋值操作的时候,JS都会复制一份栈区的数据。而对于Object类型栈区所存数据为「address地址」,而对于Primitive Type而言,则是直接存入原始数据。

你可能感兴趣的:(经验分享)