前言
前段时间在掘金上看到 [深拷贝的终极探索(90%的人都不知道)] 这篇文章,深受启发,但看到了,理解了,并不代表就会写了,于是就诞生了这篇文章,本文核心的跟上文差不多,不过我增加了 从不明白到明白的 资料。
深拷贝—浅拷贝
简单的说 深拷贝就是浅拷贝 + 递归
爆栈: 指递归中,存储的信息量大于系统栈的内存(简单来讲就是遍历的深度超限了,10000就会报错)。
循环引用的问题解决思路有两种,一直是循环检测,一种是暴力破解
JSON.stringify内部做了循环引用的检测破解了循环引用得问题(死循环)
lodash是如何解决循环应用这个问题?它是用一个栈记录了所有被拷贝的引用值。如果再次碰到同样的引用值的时候,不会再去拷贝一遍。而是利用之前已经拷贝好的值。)。
JSON.parse(JSON.stringify(obj)) 会干掉undefined项和function项;
其实暴力破解递归爆栈的方法有两条路,
第一种是消除尾递归,但在这个例子中貌似行不通,
第二种方法就是干脆不用递归,改用循环,
let obj = {
x: 3,
y: undefined,
z: function(){
console.log(11111111)
}
};
JSON.parse(JSON.stringify(obj))
// {x: 3}
Object.prototype.toString.call() 方法
获取对象类型–比typeof全面
比instanceof 兼容性 好
Object.assign() 会将数组 的索引拿来处理成对象(或者说 类数组)
Object.assign({}, [{a:null}])
{0: {…}}
深拷贝
1/简单实现
function cloneDeep(obj) {
if(typeof(obj) != 'object') return obj;
let target = Object.prototype.toString.call(obj) == "[object Object]" ? {} : [];
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
target[i] = typeof(obj[i]) == 'object' ? cloneDeep(obj[i]) : obj[i];
}
}
return target;
}
let t = [{a:2,b:4}]
let k = cloneDeep(t);
t[0].a = 7;
console.log(k)
2/Object.assign() 和 Array.from()相结合
Array.from()将类数组转化为数组
const deepClone = obj => {
if (obj === null) return null;
let clone = Object.assign({}, obj);
Object.keys(clone).forEach(
key =>
(clone[key] =
typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
);
if (Array.isArray(obj)) {
clone.length = obj.length;
return Array.from(clone);
}
return clone;
};
解析:
const deepClone = obj => {
if(obj === null) return null;
let clone = Object.assign({}, obj); //如果obj是个数组也在此 换成对象
Object.keys(clone).forEach(key => {
clone[key] = typeof(clone[key])==‘object’?deepClone(clone[key]) : clone[key]
})
if(Array.isArray(obj)){ //如果obj是数组,就将 clone由对象换回数组格式
clone.length = obj.length;
return Array.from(clone)
}
return clone;
}
const a = [{ foo: ‘bar’, obj: { a: 1, b: 2 } }];
const b = deepClone(a); // a !== b, a.obj !== b.obj
console.log(b, 111)
3/借助栈遍历对象,若对象中还是对象就推入栈中,若对象中是基本数据类型则直接拷贝。
作为吃瓜群众看这个 root 为什么会产生变化? root与parent、parent与res 的关系 显得比较迷,看了一波,在这儿以自己的视角给后来者说一下:
- root 为什么会产生变化? 关键在于这儿 parent: root, parent 引用的是root,parent变了root 跟着变,两个是一样的;
- parent与res 的关系? 包含与被包含的关系,res是parent的一项。
关键在这儿
let res = parent;
if (typeof key !== ‘undefined’) {
res = parent[key] = {};
}
如果while遍历的不是初始值(undefined),则直接赋值
res = parent[key] = {};
function deepCloneSelf(obj) {
const target = {};
const loopList = [
{
parent: target,
key: undefined,
data: obj
}
]
while(loopList.length){
let node = loopList.pop();
let parent = node.parent;
let key = node.key;
let data = node.data;
let res = parent;
if(typeof(key) != 'undefined'){
res = parent[key] = {}
}
for (const k in data) {
if (data.hasOwnProperty(k)) {
// const element = object[key];
if(typeof(data[k]) == 'object'){
loopList.push({
parent: res,
key: k,
data: data[k]
})
}else{
res[k] = data[k]
}
}
}
}
return target
}
备注:
js 中一切都是对象
const obj = {};
Object.prototype.toString.call(obj) === “[object Object]” //true
注意第二个变量(Object)的首字母大写(隐式调用 toString),小写的是方法返回的值的格式中默认的(写死的),大写的是对象类型的名称(数据类型)。
默认 返回 “[object objectname]”,其中 objectname 是对象类型的名称。
遍历对象属性的方法
for…in
Object.keys(obj)
Object.values(obj)
Object.getOwnPropertyNames(obj)
Object.getOwnPropertySymbols(obj)
Reflect.Ownkeys()
for…in(在Chrome Opera 中)遍历的时候会按照以下规则进行遍历
1.首先遍历数值建,按照升序排序
2.其次遍历字符串,按照加入时间的升序排列
3.最后遍历Symbol