因为在js中基础类型是把值存在栈里面,引用类型存在堆里面的,再把引用地址存在栈中,所以拷贝通常拷贝引用类型。拷贝创建一份新数据,让新数据拥有和原数据一样的属性值。拷贝分为浅拷贝和深拷贝。
浅拷贝
浅拷贝是指创建一个新对象,这个新对象复制了原对象的属性值。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,公用一个地址。只拷贝一层。
- 普通手搓法拷贝得创建一个新的对象 在函数里面我们定义一个新对象,将原对象的值直接赋值到新对象里面去。
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)
- concat通过原数组与空的数组进行一个拼接
let a =[1,2,3,4,5]
let d = [].concat(a)
- 对象的拼接Obiect.assign()
let newObj = Obiect.assign({},obj)
5.数组解构
[...arr]
- 数组slice方法
arr.slice(0,arr.length)
7.翻转数组reverse组合
arr.toReversed().reverse()
深拷贝
深拷贝会创建一个全新的对象,完全复制原对象的所有属性,包括嵌套的引用类型,新对象和原对象完全独立,互不影响。
- JSON方法
let newObj = JSON.parse(JSON.stringify(obj));
JSON.stringify()将对象转化为字符串,JSON.parse()将字符串转化为对象 但是这种方法有一个问题,就是如果对象中有函数,那么就会被忽略掉,因为函数是不能被转化为字符串的。 所以可以拷贝引用类型(如对象、数组),但不能拷贝函数、Symbol、undefined 等特殊值。
- structuredClone API
let newObj = structuredClone(obj);
structuredClone()是一个浏览器的API,用于深拷贝对象,返回一个新对象。但是他还是有一些东西拷贝不了,比如函数,正则表达式,日期对象,Map和Set,类数组对象,循环引用。 但是他的性能比JSON.stringify()和JSON.parse()要高,因为他是用C++实现的,而JSON.stringify()和JSON.parse()是用JavaScript实现的。
- 手搓法
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);
});
数组
- slice(a,b) 不改变原数组的值,括号内的两个参数表示的是左闭右开的区间,在这个取这个区间内的值组合成一个新的数组
- 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)