网上看到很多
js深克隆
的文章,写法各有千秋,但大多考虑不够全面; 并且很多一测试就报错,或者返回结果不对。在下不才,整理了下写了个比较完善的方法,全篇带注释讲解
,并附测试用例。欢迎各路牛鬼蛇神深入探讨。
递归函数会导致栈溢出
,而此处又无法使用尾递归,该怎么处理typeof
Date,Math,RegExp,Function,Null 都返回Object
该怎么处理Date,RegExp,Function
应该如何克隆v,s
引用同一个对象时,克隆之后也应该引用同一个对象prototype
如何克隆getOwnPropertyDescriptor
如何克隆for-in
遍历的是原型链,需要用hasOwnProperty
判断是否是自有属性function _getDataType(data) {
return Object.prototype.toString.call(data).slice(8, -1);
}
function copyRegExp(regExp) {
let attrs = '';
if (regExp.global) attrs += 'g';
if (regExp.ignoreCase) attrs += 'i';
if (regExp.multiline) attrs += 'm';
let newRegExp = new RegExp(regExp, attrs);
newRegExp.lastIndex = regExp.lastIndex;
return newRegExp;
}
function clone(x) {
// String Number Boolean Undefined Null 返回自身
if (x == null || typeof x !== 'object') return x;
// RegExp Date Function 克隆
let type = _getDataType(x);
let root;
switch (type) {
case 'RegExp':
return copyRegExp(x);
case 'Date':
return new Date(x.getTime());
case 'Function':
return x;
case 'Array':
root = [];
break;
default:
root = Object.create(Object.getPrototypeOf(x));
}
// Array Object 克隆
// 用来去重 解决原数据中多个属性引用同一对象克隆后不相同问题
const uniqueList = [];
// 使用栈结构解决递归爆栈问题
const stack = [
{
parent: root,
key: undefined,
data: x,
}
];
// 深度优先循环
while (stack.length) {
const {
parent, key, data} = stack.pop();
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
let type = _getDataType(data);
switch (type) {
case 'RegExp':
parent[key] = copyRegExp(data);
continue;
case 'Date':
parent[key] = new Date(data.getTime());
continue;
case 'Function':
parent[key] = data;
continue;
case 'Array':
res = parent[key] = [];
break;
default:
let proto = Object.getPrototypeOf(data);
res = parent[key] = Object.create(proto);
}
}
//数据引用已经存在则赋值并退出本次循环,不存在则缓存
let uniqueData = uniqueList.find(item => item.source === data);
if (uniqueData) {
parent[key] = uniqueData.target;
continue;
} else {
uniqueList.push({
source: data,
target: res,
});
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (data[k] == null || typeof data[k] !== 'object') {
// 基础类型克隆
let descriptor=Object.getOwnPropertyDescriptor(data,k);
Object.defineProperty(res,k,descriptor);
} else {
// 引用类型加入stack循环处理
stack.push({
parent: res,
key: k,
data: data[k],
});
}
}
}
}
return root;
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let obj = {
a: new Date(),
b: undefined,
c: /234/igm,
d: {
},
h: true,
i: 234,
k: 'sdfsd',
m: [1, 2, 3],
n: {
f: 3, g: [3, new Date(), /ewrew/ig, 45, 8, {
d: 33}]},
o: /sdf/i,
w: function () {
console.log(44)
},
r: null,
v: arr,
s: arr,
};
let cloneObj=clone(obj);
arr[2]=3333;
cloneObj.s[3]=5555;
console.log(obj);
console.log(cloneObj);
{
a: 2019-01-22T12:54:24.955Z,
b: undefined,
c: /234/gim,
d: {
},
h: true,
i: 234,
k: 'sdfsd',
m: [ 1, 2, 3 ],
n:
{
f: 3,
g: [ 3, 2019-01-22T12:54:24.955Z, /ewrew/gi, 45, 8, [Object] ] },
o: /sdf/i,
w: [Function: w],
r: null,
v: [ 1, 2, 3333, 4, 5, 6, 7, 8, 9 ],
s: [ 1, 2, 3333, 4, 5, 6, 7, 8, 9 ] }
{
b: undefined,
h: true,
i: 234,
k: 'sdfsd',
w: [Function: w],
r: null,
s: [ 1, 2, 3, 5555, 5, 6, 7, 8, 9 ],
v: [ 1, 2, 3, 5555, 5, 6, 7, 8, 9 ],
o: /sdf/i,
n:
{
f: 3,
g: [ 3, 2019-01-22T12:54:24.955Z, /ewrew/gi, 45, 8, [Object] ] },
m: [ 1, 2, 3 ],
d: {
},
c: /234/gim,
a: 2019-01-22T12:54:24.955Z }
let obj={
};
let cloneObj=JSON.parse(JSON.stringify(obj));
let o0 = [1, 2, 3, 4];
let o1 = {
a: 1, b: 1, c: 1, d: o0};
let o2 = {
b: 2, c: 2};
let obj = Object.assign({
}, o1, o2);
o1.d[1]=444;
console.log(obj); // { a: 1, b: 2, c: 2, d: [ 1, 444, 3, 4 ] }
Object.prototype.clone=function(){
//原型指向保持一致
var newobj=Object.create(Object.getPrototypeOf(this));
//自身属性保持一样
var propNames=Object.getOwnPropertyNames(this);
propNames.forEach(function(item){
//保持每个属性的特性也一样
var des=Object.getOwnPropertyDescriptor(this,item);
Object.defineProperty(newobj,item,des);
},this);
return newobj;
}
//测试
let o0 = [1, 2, 3, 4];
let o1 = {
a: 1, b: 2, c: 1, d: o0};
let obj = o1.clone();
o1.d[1]=444;
console.log(obj); // { a: 1, b: 2, c: 1, d: [ 1, 444, 3, 4 ] }
(ps:如有考虑不周或错误之处,请指正。如果有用,请点赞o( ̄▽ ̄)d
)