深浅拷贝函数

168 阅读4分钟

因为在js中基础类型是把值存在栈里面,引用类型存在堆里面的,再把引用地址存在栈中,所以拷贝通常拷贝引用类型。拷贝创建一份新数据,让新数据拥有和原数据一样的属性值。拷贝分为浅拷贝和深拷贝。

浅拷贝

浅拷贝是指创建一个新对象,这个新对象复制了原对象的属性值。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,公用一个地址。只拷贝一层。

  1. 普通手搓法拷贝得创建一个新的对象 在函数里面我们定义一个新对象,将原对象的值直接赋值到新对象里面去。
function shallowCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
      return obj;
    }
    let newObj = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        newObj[key] = obj[key];  
      }
    }
    return newObj;
  }
  const original = { a: 1, b: { c: 2 } };
  const copied = shallowCopy(original);
  console.log(copied); 

for...in 循环会遍历对象 所有可枚举的属性,包括:

1 对象自身的属性 2. 从原型链继承来的属性 obj.hasOwnProperty(key)就是为了过滤掉继承来的属性,只保留对象自己拥有的属性

obj2是一个“自身为空但有原型”的对象,它可以通过原型链访问 obj 的所有属性。console.log 只显示自身属性,所以打印是 {}。

let obj2 = Object.create(obj)
  1. concat通过原数组与空的数组进行一个拼接
let a =[1,2,3,4,5]
let d = [].concat(a)

image.png

  1. 对象的拼接Obiect.assign()
let newObj = Obiect.assign({},obj)

5.数组解构

[...arr]
  1. 数组slice方法
arr.slice(0,arr.length)

7.翻转数组reverse组合

arr.toReversed().reverse()

深拷贝

深拷贝会创建一个全新的对象,完全复制原对象的所有属性,包括嵌套的引用类型,新对象和原对象完全独立,互不影响。

  1. JSON方法
let newObj = JSON.parse(JSON.stringify(obj));

JSON.stringify()将对象转化为字符串,JSON.parse()将字符串转化为对象 但是这种方法有一个问题,就是如果对象中有函数,那么就会被忽略掉,因为函数是不能被转化为字符串的。 所以可以拷贝引用类型(如对象、数组),但不能拷贝函数、Symbol、undefined 等特殊值。

  1. structuredClone API
let newObj = structuredClone(obj);

structuredClone()是一个浏览器的API,用于深拷贝对象,返回一个新对象。但是他还是有一些东西拷贝不了,比如函数,正则表达式,日期对象,Map和Set,类数组对象,循环引用。 但是他的性能比JSON.stringify()和JSON.parse()要高,因为他是用C++实现的,而JSON.stringify()和JSON.parse()是用JavaScript实现的。

  1. 手搓法

function deepCopy(obj,map = new WeakMap()){
    //基础类型直接返回
    if(obj===null||typeof obj!=='object'){
        return obj;
    }
    //判断是不是函数
    if(typeof obj ==='function'){
        return obj;
    }
    if(map.has(obj)){
        return map.get(obj);
    }
    //map,set,RegExp,Date
    if(obj instanceof Date){
        return new Date(obj);
    }
    if(obj instanceof RegExp){
        return new RegExp(obj.source,obj.flags);
    }
    if(obj instanceof Map){
        const tmp = new Map();
        map.set(obj,tmp);
        obj.forEach((value,key)=>{
            tmp.set(deepCopy(key,map),deepCopy(value,map))
        })
        return tmp;
    }
    if(obj instanceof Set){
        const tmp = new Set();
        map.set(obj,tmp);
        obj.forEach((v)=>{
            tmp.add(deepCopy(v,map))
        })
        return tmp;
    }
    //判断数组,对象
    const newObj = Array.isArray(obj)?[]:{};
    map.set(obj,newObj);//存储obj和newObj的映射关系
    for(let key in obj){
        if(obj.hasOwnproperty(key)){
            newObj[key] = deepCopy(obj[key],map);
        }
    }
    return newObj;
}
const obj = { name: "Alice" };
obj.self = obj;
obj.friend = { name: "Bob", boss: obj }; // 间接循环

const copied = deepClone(obj);

console.log(copied.name);         // "Alice"
console.log(copied.self.name);    // "Alice"
console.log(copied.friend.boss.name); // "Alice"

// 验证是否打破循环
console.log(copied.self === copied); // true ✅ 不会无限递归

4. MessageChannel(利用浏览器的结构化克隆)

这是利用浏览器内部的“结构化克隆算法”(Structured Clone Algorithm)来实现深拷贝,和 structuredClone 底层原理相同。

原理:

postMessage 在跨线程通信时会自动深拷贝数据(使用结构化克隆)。

代码示例:
function deepCloneWithChannel(obj) {
  return new Promise((resolve) => {
    const channel = new MessageChannel();
    channel.port1.onmessage = (event) => {
      resolve(event.data);
    };
    channel.port2.postMessage(obj);
  });
}

// 使用(异步)
deepCloneWithChannel({ a: 1, b: { c: 2 } }).then(copied => {
  console.log(copied);
});

数组

  1. slice(a,b) 不改变原数组的值,括号内的两个参数表示的是左闭右开的区间,在这个取这个区间内的值组合成一个新的数组
  2. splice(a,b,c)表示从a位置(数组坐标从0开始)删除b个元素,并且加入新元素c
let a=[1,2,3,4,5]
let b=[5,9,6,7,5,2]
let c=a.splice(2,1,6)
let d=b.slice(2,4)
console.log(a)
console.log(c)
console.log(b)
console.log(d)

image.png