「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战」
1 赋值、浅拷贝和深拷贝的区别
| 和原数据是否指向同一对象 | 第一层数据为基本数据类型,改变是否会使原数据一同改变 | 原数据中包含子对象,改变是否会使原数据一同改变 | |
|---|---|---|---|
| 赋值 | 是 | 会 | 会 |
| 浅拷贝 | 否 | 不会 | 会 |
| 深拷贝 | 否 | 不会 | 不会 |
2 浅拷贝的4种方法
const obj = {
name: 'AAA',
age: 18,
hobbies: {
h1: 'ball',
h2: 'chess'
}
}
// 1.逐个属性赋值
const obj1 = {
name: obj.name,
age: obj.age
}
obj1.name = 'BBB'
console.log(obj1);
console.log(obj);
// 2.遍历属性赋值
const obj2 = {}
for (let key in obj) {
obj2[key] = obj[key]
}
obj2.hobbies.h1 = 'CCC'
console.log(obj2);
console.log(obj);
// 3.Object.assign
const obj3 = Object.assign({}, obj)
obj3.name = 'DDD'
console.log(obj3 === obj);
console.log(obj3);
// 4.展开语法
const obj4 = {...obj}
console.log(obj4 === obj);
3 深拷贝
3.1 JSON序列化
const targetObj = JSON.parse(JSON.stringify(copyObj))
缺点:
-
只能拷贝对象中能用JSON表示的数据结构,JSON只支持
object,array,string,number,true,false,null等,不支持函数、undefined、Date、RegExp等;对于不支持的数据会直接忽略该属性 -
JSON.stringify()只能序列化对象的可枚举的自有属性,即无法拷贝copyObj对象原型链上的属性和方法
-
如果对象中存在循环引用,会抛出错误,比如
obj.z = obj
3.2 递归克隆
3.2.1 拷贝原始数据类型
function deepClone(target) {
return target
}
3.2.2 拷贝简单的对象
简单的对象不包括Array、Function、Date等子类型
let obj1 = {
name: "hello",
child: {
name: "小明"
}
}
function deepClone(target) {
if (target instanceof Object) {
let dist = {}
// for in用于遍历对象属性,不建议和数组一起使用
// 数组可以用foreach 或 for of
for (let key in target) {
dist[key] = deepClone(target[key])
}
return dist
} else {
return target
}
}
3.2.3 拷贝数组
数组如果用上面的方法拷贝,得到的是对象的形式
所以对deepClone方法进行改进
function deepClone(target) {
if (target instanceof Object) {
let dist;
if (target instanceof Array) {
dist = [];
} else {
dist = {};
}
// for in用于遍历对象属性,不建议和数组一起使用
// 数组可以用foreach 或 for of
for (let key in target) {
dist[key] = deepClone(target[key]);
}
return dist;
} else {
return target;
}
}
3.2.4 拷贝函数
function deepClone(target){
if(target instanceof Object){
let dist ;
if(target instanceof Array){
dist = []
}else if(target instanceof Function){
dist = function(){
// 在函数中去执行原来的函数,确保返回的值相同
return target.call(this, ...arguments);
}
}else{
dist = {};
}
for(let key in target){
dist[key] = deepClone(target[key]);
}
return dist;
}else{
return target;
}
}
3.2.5 拷贝正则表达式
const a = /hi\d/ig
一个正则由两部分组成:正则的模式(斜杠之间的内容)以及参数ig
通过正则的source属性能够拿到正则模式,通过正则的flags属性能够拿到正则的参数
function deepClone(target){
if(target instanceof Object){
let dist ;
if(target instanceof Array){
// 拷贝数组
dist = [];
}else if(target instanceof Function){
// 拷贝函数
dist = function () {
return target.call(this, ...arguments);
};
}else if(target instanceof RegExp){
// 拷贝正则表达式
dist = new RegExp(target.source,target.flags);
}else{
// 拷贝普通对象
dist = {};
}
for(let key in target){
dist[key] = deepClone(target[key]);
}
return dist;
}else{
return target;
}
}
3.2.6 拷贝日期
如果日期用上述方法拷贝,返回的是一个字符串
我们需要重新创建日期对象
dist = new Date(target)
3.3 优化deepClone
到目前为止,我们虽然写出了一个可使用的深拷贝函数,但是这个函数仍然存在着许多可优化的地方。(这些优化的地方也是面试官容易问到的地方)。
3.3.1 忽略原型上的属性
for in会遍历包括原型上的所有可迭代属性,深拷贝时一般不应该去遍历原型上的属性,使用for in遍历时最好把原型上的属性和自身属性区分开来,通过hasOwnProperty筛选自身的属性进行遍历
优化后代码:
function deepClone(target){
if(target instanceof Object){
let dist ;
if(target instanceof Array){
// 拷贝数组
dist = [];
}else if(target instanceof Function){
// 拷贝函数
dist = function () {
return target.call(this, ...arguments);
};
}else if(target instanceof RegExp){
// 拷贝正则表达式
dist = new RegExp(target.source,target.flags);
}else if(target instanceof Date){
dist = new Date(target);
}else{
// 拷贝普通对象
dist = {};
}
for(let key in target){
// 过滤掉原型身上的属性
if (target.hasOwnProperty(key)) {
dist[key] = deepClone(target[key]);
}
}
return dist;
}else{
return target;
}
}
3.3.2 环状对象的爆栈问题
如果一个对象有属性指向自身,那么会形成一个环,比如:
let a = { name: "小明" }
a.self = a
这样递归调用的过程会无限循环,最终爆栈
所以,我们需要添加递归终止条件:如果一个对象已经被克隆过了就直接使用克隆后的对象,不再进行递归
我们用map来保存可能重复的地址以及它的克隆地址
// 以target为键,以拷贝后的地址为值进行保存
let cache = new Map();
function deepClone(target){
if(cache.get(target)){
return cache.get(target)
}
if(target instanceof Object){
let dist ;
if(target instanceof Array){
// 拷贝数组
dist = [];
}else if(target instanceof Function){
// 拷贝函数
dist = function () {
return target.call(this, ...arguments);
};
}else if(target instanceof RegExp){
// 拷贝正则表达式
dist = new RegExp(target.source,target.flags);
}else if(target instanceof Date){
dist = new Date(target);
}else{
// 拷贝普通对象
dist = {};
}
// 将属性和拷贝后的值作为一个map
cache.set(target, dist);
for(let key in target){
// 过滤掉原型身上的属性
if (target.hasOwnProperty(key)) {
dist[key] = deepClone(target[key]);
}
}
return dist;
}else{
return target;
}
}
3.3.3 共用缓存导致的互相影响问题
上面我们新增了cache来进行缓存,存在一个问题:每次深拷贝一个对象都会用到这个cache,那么不同拷贝可能会互相影响,示例:
let a = {
name:"hello",
}
let a1 = deepClone(a);
console.log(map); //{ name: 'hello' } => { name: 'hello' }
let b = {
age:24
}
let b1 = deepClone(b);
console.log(map); // { name: 'hello' } => { name: 'hello' },{ age: 24 } => { age: 24 } }
解决办法:在调用函数时,每次都创建一个新的map(默认参数),然后如果需要递归,就把这个map往下传。
function deepClone(target,cache = new Map()){
if(cache.get(target)){
return cache.get(target)
}
if(target instanceof Object){
let dist ;
if(target instanceof Array){
// 拷贝数组
dist = [];
}else if(target instanceof Function){
// 拷贝函数
dist = function () {
return target.call(this, ...arguments);
};
}else if(target instanceof RegExp){
// 拷贝正则表达式
dist = new RegExp(target.source,target.flags);
}else if(target instanceof Date){
dist = new Date(target);
}else{
// 拷贝普通对象
dist = {};
}
// 将属性和拷贝后的值作为一个map
cache.set(target, dist);
for(let key in target){
// 过滤掉原型身上的属性
if (target.hasOwnProperty(key)) {
dist[key] = deepClone(target[key], cache);
}
}
return dist;
}else{
return target;
}
}
3.4 最终函数
function deepClone(target,cache = new Map()){
if(cache.get(target)){
return cache.get(target)
}
if(target instanceof Object){
let dist ;
if(target instanceof Array){
// 拷贝数组
dist = [];
}else if(target instanceof Function){
// 拷贝函数
dist = function () {
return target.call(this, ...arguments);
};
}else if(target instanceof RegExp){
// 拷贝正则表达式
dist = new RegExp(target.source,target.flags);
}else if(target instanceof Date){
dist = new Date(target);
}else{
// 拷贝普通对象
dist = {};
}
// 将属性和拷贝后的值作为一个map
cache.set(target, dist);
for(let key in target){
// 过滤掉原型身上的属性
if (target.hasOwnProperty(key)) {
dist[key] = deepClone(target[key], cache);
}
}
return dist;
}else{
return target;
}
}