小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
我们上上篇已经讲了两种深复制对象的方法,少量代码实现了功能,各有优缺点。
上篇还自己手写了一个简单版本,也一起品读了jquery.extend方法的实现。
还是那句话哈,正常情况都满足需求,我们不妨再深入一点。
- 爆栈
- 循环引用
今天解决爆栈
用循环替代递归
采用深度遍历的机制,一个属性一个属性的遍历。
下面的代码修改自:github.com/jsmini/clon… 去掉了多余的代码和对第三方库的依赖,可以直接复制走,直接用。
const { toString, hasOwnProperty } = Object.prototype;
function hasOwnProp(obj, property) {
return hasOwnProperty.call(obj, property)
}
function getType(obj) {
return toString.call(obj).slice(8,-1).toLowerCase();
}
function isObject(obj) {
return getType(obj) === "object";
}
function isArray(arr) {
return getType(arr) === "array";
}
function isCloneObject(obj) {
return isObject(obj) || isArray(obj)
}
// 循环
function cloneDeep(x) {
// 先设置默认值
let root = x;
if (isArray(x)) {
root = [];
} else if (isObject(x)) {
root = {};
}
// 循环数组
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while (loopList.length) {
// 深度优先
// 出栈
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = isArray(data) ? [] : {};
}
if (isArray(data)) {
for (let i = 0; i < data.length; i++) {
// 避免一层死循环 a.b = a
if (data[i] === data) {
res[i] = res;
} else if (isCloneObject(data[i])) { // 需要深度复制的属性值
// 下一次循环, 入栈
loopList.push({
parent: res,
key: i,
data: data[i],
});
} else {
res[i] = data[i];
}
}
} else if (isObject(data)) {
for (let k in data) {
if (hasOwnProp(data, k)) {
// 避免一层死循环 a.b = a
if (data[k] === data) {
res[k] = res;
} else if (isCloneObject(data[k])) { // 需要深度复制的属性值
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
}
return root;
}
console.log(cloneDeep({ a: 1, b: { fn: function () { } } }))
// { a: 1, b: { fn: [Function: fn] } }
测试爆栈
生产测试数据
生产测试的方法:
function createData(deep) {
var data = {};
var temp = data;
for (var i = 0; i < deep; i++) {
temp = temp['data'] = {};
temp[i + 1] = i + 1
}
return data;
}
产生的数据结构如下:
{
"data": {
"1": 1,
"data": {
"2": 2,
"data": {
"3": 3
}
}
}
}
JSON.parse(JSON.stringify(data))
const data = createData(10000);
console.log(JSON.parse(JSON.stringify(data)))
结果是爆栈
基于循环实现的cloneDeep
const data = createData(10000);
console.log(cloneDeep(data))
果然爆栈不见了,666啊
小结
其实也还有用拓展运算符,Object.assign,Array.prototype.concat结合递归的方式来实现深拷贝方案,有兴趣的同学可以自己去尝试一波。
下一篇,我们一起解决循环引用。
今天你收获了吗?