title: js深拷贝浅拷贝与lodash date: 2022/9/27 14:46:25 categories:
ref:JS传递参数时的内部存储逻辑
JS 變數傳遞探討:pass by value 、 pass by reference 還是 pass by sharing?
在这个问题前,有另一个问题就是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)
阅览,我十分赞同这么一个说法:不论是传引用还是传值,定义都可以归结为 传值
即: 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 => 沒被改變
这个过程的执行顺序为
而对于引用类型,如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 } => 跟著改變
为什么会有这样的问题呢?因为js在储存数据的时候,对于原始数据类型跟引用类型方式稍有不同。
在原始数据类型当中,这些value会存在栈中。因此,在使用=进行赋值的时候,会直接将栈区的数据复制一份。而对于引用类型,栈区则是会存储其处于堆区,因此在传值的时候,则是会把堆区这一块数据地址传递进去。
所以,这也是为什么说将Object引用类型传递会与源数据共享一个地址。
有很多方式可以去解决深拷贝的问题,最简单的可以使用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而言,则是直接存入原始数据。