Skip to main content

深拷贝 浅拷贝 in js

今天在项目中用到了深拷贝,以此来记录一下。


介绍

JS中数据分为原始类型和引用类型。

对于原始类型来说,并没有深浅拷贝的区别。所以深浅拷贝只针对于引用类型来说。


浅拷贝: 只进行一层复制。对于引用类型的数据,只拷贝了地址,所以复制的引用类型的数据还是共享地址的。

深拷贝: 深拷贝之后的对象不会和原拷贝对象相互影响


深拷贝的库: lodash

浅拷贝方法: clone

深拷贝方法: cloneDeep


例子:

1) 两个对象指向同一地址, == 运算符会返回true

const obj = {};

const newObj = obj;

console.log(obj == newObj); // true

2) 两个对象指向不同的值, == 运算符会返回false

const obj = {};

const newObj = {};

console.log(obj == newObj); // false


将引用类型直接赋值给另外一个变量,会影响原来的数据

这时候使用lodash中的clone, 就可以实现一层的浅拷贝。obj.person和newObj.person指向同一个对象

import { clone } from "lodash";

const obj = {
person: {
name: "yingxu",
},
};

const newObj = clone(obj);

obj.person.name = "xxx"; // 改变原来的对象

console.log("原来的对象", obj);

console.log("新的对象", newObj);

console.log("更深层的指向同一地址", obj.person == newObj.person); // true

这时候,如果要让他们指向不同的对象,可以使用cloneDeep

import { cloneDeep } from "lodash";

const obj = {
person: {
name: "yingxu",
},
};

const newObj = cloneDeep(obj);
obj.person.name = "xxx"; // 改变原来的对象

console.log("原来的对象", obj);
console.log("新的对象", newObj);

console.log("更深层的对象指向同一地址", obj.person == newObj.person); // false


实现浅拷贝的方法:

1) Object.assign

const obj = {
name: "yingxu",
};

const newObj = Object.assign({}, obj);

obj.name = "xxx"; // 改变原来的对象

console.log(newObj); // { name: 'yingxu' } 新对象不变

console.log(obj == newObj); // false 两者指向不同地址


2) 数组的 contat 和 slice方法

const arr = ['yingxu', 'is', 'handsome']
const newArr = arr.slice(0)

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['yingxu', 'is', 'handsome']

console.log(arr == newArr) // false 两者指向不同地址

// -----------------------------------------------
const arr = ["yingxu", "is", "handsome"];
const newArr = [].concat(arr);

arr[2] = "rich"; // 改变原来的数组

console.log(newArr); // ['yingxu', 'is', 'handsome'] // 新数组不变

console.log(arr == newArr); // false 两者指向不同地址

3) Array.from

const arr = ["yingxu", "is", "handsome"];
const newArr = Array.from(arr);

arr[2] = "rich"; // 改变原来的数组

console.log(newArr); // ['yingxu', 'is', 'handsome']


console.log(arr == newArr); // false 两者指向不同地址

4) 扩展运算符

const arr = ["yingxu", "is", "handsome"];
const newArr = [...arr];

arr[2] = "rich"; // 改变原来的数组

console.log(newArr); // ['yingxu', 'is', 'handsome'] // 新数组不变

console.log(arr == newArr); // false 两者指向不同地址


实现深拷贝的方法

1) JSON.parse(JSON.stringify(obj));

const obj = {
person: {
name: "yingxu",
},
};

const newObj = JSON.parse(JSON.stringify(obj));
obj.person.name = "xxx"; // 改变原来的深层对象

console.log(newObj); // { person: { name: 'yingxu' } } 新的深层对象不变

这种实现方法有三种弊端

    1> 会忽略undefined, Symbol和函数

const obj = {
a: undefined,
b: Symbol("b"),
c: function () {},
};

const newObj = JSON.parse(JSON.stringify(obj));

console.log(newObj); // {}

   

    2> NaN, Ininity, -Infinity会被序列化为null

const obj = {
a: NaN,
b: Infinity,
c: -Infinity,
};

const newObj = JSON.parse(JSON.stringify(obj)); // {a: null, b: null, c:null}

console.log(newObj);

    3> 而且无法解决循环引用的问题    

const obj = {
a: 1,
};

obj.obj = obj;

const newObj = JSON.parse(JSON.stringify(obj)); // 报错

所以只适合日常开发中一些简单的对象。


2) 简单版本的深拷贝: 递归调用

function deepClone(target) {
if (typeof target !== "object") {
// 如果是原始类型,无需继续拷贝,直接返回
return target;
}
// 如果是引用类型,递归实现每一层的拷贝
const cloneTarget = {}; // 定义一个克隆对象
for (const key in target) {
// 遍历原对象
cloneTarget[key] = deepClone(target[key]); // 递归拷贝每一层
}
return cloneTarget; // 返回克隆对象
}

但是这里没有解决各种类型比如数组,日期,正则和null这些常用的类型,会将这些类型都序列化为{}。使用如下代码可以解决这些。

function deepClone(target) {
if (target === null) return target; // 处理 null
if (target instanceof Date) return new Date(target); // 处理日期
if (target instanceof RegExp) return new RegExp(target); // 处理正则

if (typeof target !== "object") return target; // 处理原始类型

// 处理对象和数组
const cloneTarget = new target.constructor(); // 创建一个新的克隆对象或克隆数组
for (const key in target) {
// 递归拷贝每一层
cloneTarget[key] = deepClone(target[key]);
}
return cloneTarget;
}

还有Symbol和循环引用等的处理,待之后学到这里再继续看

https://juejin.cn/post/7072528644739956773


Comments

Popular posts from this blog

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...

Valgrind

  Using Valgrind to check memory How to use Valgrind -To valgrind the program, run the valgrind command and give it our program name as an argument. -For example, if we want to run ./myProgram hello 42, we can simply run Valgrind ./myProgram hello 42.  Uninitialized Values -When we run the program, we may use uninitialized values. It needs to be fixed. Valgrind can tell us about the use of uninitialized values. But it only tell when the control flow of the program depends on the unitialized value. For example, uninitialized value appears in the conditional expression of an if, or a loop, or in the switch statement. -If we want to know where the uninitialized value is from, we can use Valgrind    --track-origins=yes ./myProgram -Using -fsanitize=address can find a lot of problems that Memcheck cannot.  -We can use Valgrind with GDB to debug. We can run: --vgdb=full --vgdb-error=0., then Valgrind will stop on the first error that it encounters and give control to ...