JS里的深拷贝
前言
很多知识深挖会让人有豁然开朗的感觉,所以建议平时学习尽量也要深挖(遇到哪个词模糊就去查,一直到搞懂你原本要查的知识点里的每一句话),你会发现收获超级多!
对于学习前端的人来说,js中的深拷贝都很熟悉,我今天也来扒一扒它!
首先,明确概念,什么是深拷贝?
有时候,我们有一堆数据,需要对它进行一系列的操作,但是还不能改变它的原始数据,这时候你会想到把它拷贝一份,对备份进行操作,但是你要知道js中的数据类型不全都是可以直接复制拷贝的
OK,那就来复习一下JS中的数据结构:👇👇
JS中的数据类型:
数据类型分为两大类:原始数据类型和引用数据类型
原始数据类型:(6 种原始类型,使用 typeof 运算符检查:)
- Number ———64位双精度浮点型的数字数据类型 typeof instance === “number”
- String ——字符串 typeof instance === "string
- Boolean ——布尔值 typeof instance === “boolean”
- undefined —— 一个声明未定义的变量的初始值,或没有实际参数的形式参数 — typeof instance === “undefined”
- null —— 表示不存在或者引用地址为空 typeof instance === "object"
- Symbol —— ES6中新增的原始数据类型 typeof instance === “symbol”
引用数据类型: Object.prototype.toString.call ( ) 检测
- 对象 [ object Object ]
- 数组 [ object Array ]
- 正则 [ object RegExp]
- 时间 [ object Date]
- 函数 [ object Function]
好了,数据类型复习完了,大家应该也知道,引用类型的数据不能直接以赋值形式进行拷贝的,因为堆栈原因……(说烂了)看图!👇💡
所以,newObj 对房间1的所有操作,都会影响到obj 后续对房间1的操作,那么想个办法吧!
我再弄一间房,房间2,我把房间2布置的和房间1一模一样,只有钥匙不一样而已,这样就可以模仿对房间1的操作了
说干就干!
直接上代码了哈!(手写的简low版,但是对于理解还是够的)
function clone(data) {
let type = typeof data;//typeof判断不是很精确,函数的typeof值为“function”
if (type != "object") {
return data;
} else if (type == "object") {//
let fina = Object.prototype.toString.call(data);
const res = fina == "[object Array]" ? [] : {}
for (let key in data) {
if (data.hasOwnProperty(key)) {//要是需克隆对象自身的属性,才去拷贝
res[key] = clone(data[key]);
}
}
return res;
}
}
let o = {
a: "outer",
b: {
d: "inner"
},
c: [1, 3, 3, {
e: "inner inner"
}],
y: function() {
console.log("func")
},
g: Infinity,
};
let obj = clone(o);
obj.b.d = "change inner";
console.log(obj)
console.log(o)
但是这个版本不能拷贝函数、正则、时间、null这些
来看看大佬的完美版的深克隆(我认为各种情况都考虑到了)👇👇👇
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
function getType(target) {
return Object.prototype.toString.call(target);
}
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
export function clone(target, map = new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
以上是对深拷贝的自我实现,那有没有什么原生方法可以进行深克隆呢?(其实也算不上,问题太多了)
原生方法:
1. JSON对象的方法:这个问题太多了(每种问题对应的实际例子,大家自己试哈)
- 取不到值为 undefined 的 key
- NaN 和 无穷大,无穷小转变为 null
- 取不到原型的内容
- date 对象转变为 date 字符串
- 值为symbol类型会丢失
- 循环引用会报错
2. Object.assign()
3. 数组的splice 、 slice
4. 拓展运算符
以上三类方法,都有一个共同的毛病,就是只能处理只有一层结构的引用类型的数据
?????我这**说的是人话????
意思就是,比如:数组里面只有原始类型的数据,对象也是,里面再有其他引用类型的数据,就是两层了
当对象或数组里面还有引用类型的数据,那么里面的引用类型的数据就是浅拷贝,这个看一个例子(其他方法类似):👇👇👇
好了,相信看完,你又会成长一点,我们一起加油,一起奔向诗和远方!
看都看完了,给个一键三连加关注呗😁