注意:拷贝只针对 Object 和 Array 等引用数据类型,而 赋值 并不是一种拷贝操作。
把一个对象 赋值 给一个新的变量时,只是将栈空间中存储的对象地址复制了一份给新的变量,新的变量和对象依旧指向同一个堆中的存储空间。
-
- 把数组的内存地址赋值给newArr,这里不叫拷贝,修改时互相影响。
- let arr = [1,2,3]
- let newArr = arr
-
-
-
把对象或数组,进行 浅拷贝 操作时,会在堆空间中生成一个新的对象,这个对象会精准复制原对象的所有属性值,并将新的变量指向这个堆中新产生的对象。
- 使用扩展运算符实现浅拷贝,还可以通过Object.assign(), Array.prototype.slice(), Array.prototype.concat()等方法实现对象和数组的浅拷贝。
- let obj = {
- name: 'Jerry'
- }
- let arr = [1,2,3]
-
- Object中的assign方法 将对象中所有可枚举的自有属性复制到目标对象,并返回修改后的目标对象
- let obj_1 = Object.assign({},obj)
-
- 展开运算符实现浅拷贝
- let arr_1 = [...arr]
- let obj_1 = {...obj}
-
- Array.prototype.concat() 用于合并两个或多个数组,并返回新的数组
- let arr_2 = arr.concat([])
-
- Array.prototype.slice() 回一个新的数组对象,这一对象是一个由 start 和 end 决定原数组的浅拷贝
- let arr_3 = arr.slice()
-
针对基础类型 对象 或者 数组 浅拷贝复制出的新数据是完全独立于旧数据。
针对复杂类型 对象 的属性和 数组 的元素也是引用数据类型时,浅拷贝 仍然只会复制该引用数据类型堆中的地址。
- let arr = [1,2,[3]]
- let obj = {
- name: 'Jerry'
- info: {
- age: 18,
- height: 180
- }
- }
- 展开运算符实现浅拷贝
- let arr_1 = [...arr]
- let obj_1 = {...obj}
- 修改新对象中的属性值
- arr_1[2] = [3,4]
- obj_1.info = 'null'
- 打印原对象数据 说明:复杂类型 新对象和原对象共享堆中的存储地址
- console.log(arr[2]) [3,4]
- console.log(obj.info) 'null'
深拷贝 在构造新的数据时,遇到引用所指向的引用数据类型会继续执行拷贝,直到所有的引用数据类型都被处理。
- JSON.stringify()将一个 JavaScript 对象或值转换为 JSON 字符串, 而 JSON.parse() 则正好相反,它将JSON字符串解析为值或对象,我们以JSON字符串作为桥梁,来实现 深拷贝。
-
- let obj = {
- name: 'Jerry',
- info: {
- age: 18,
- height: 180
- }
- }
- let obj_1 = JSON.parse(JSON.stringify(obj))
如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成 字符串
如果obj里有RegExp、Error对象,则序列化的结果将只得到 空对象
如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失
如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成 null
如果obj中的对象是由构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的 constructor
如果对象中存在循环引用的情况也无法正确实现 深拷贝。
- function deepClone(obj, cache = new WeakMap()) { // weakmap做缓存处理循环引用,且不影响垃圾回收
- if (typeof obj === 'symbol') return Symbol(obj.description) // 处理symbol
- if (obj === null || typeof obj !== 'object') return obj // 对于空对象,函数或者基本数据类型直接返回
- if (cache.get(obj)) return cache.get(obj) // 如果缓存中有该对象,说明有循环引用,直接返回该对象,不重复拷贝
- // 当为以下类型时直接new一个新对象
- const type = [Date, RegExp, Set, Map, WeakMap, WeakSet]
- if (type.includes(obj.constructor)) return new obj.constructor(obj)
- let cloneObj = new obj.constructor() // 让拷贝对象拥有原对象的构造函数类型
- cache.set(obj,cloneObj) // 拷贝对象写入缓存
- for(let key in obj) {
- if(obj.hasOwnProperty(key)){ // 如果该属性在对象上而不是其原型上
- cloneObj[key] = deepClone(obj[key],cache) // 递归调用
- }
- }
- return cloneObj
- }