js深克隆一直是一个很奇妙的问题。也是面试中常问的一个问题。但是可能大多数人的解决方案都是有问题的。但是并不是解决方案本身的问题,是这个问题本身的问题。从一个角度来看,这个问题其实是无解的。但是换一个角度,这个问题已然有了最优解。下面就来说一下这个问题的各种** 解决方案 **。
1.使用JSON
JSON.parse(JSON.stringify(obj))
看一个例子
obj={a:1,b:{b:1}}
JSON.parse(JSON.stringify(obj))//obj={a:1,b:{b:1}}
深克隆完毕。。是不是觉得很简单,很惊艳,从某种程度来说,没错,这种方法是很好,而且如果是简单结构的话比jquery的extend效率要高10%-20%。但是问题出在哪里呢,JSON的深克隆不会克隆NAN,undefined这些,更别说function,Data了。所以又有了递归遍历这种操作。
2.递归
//返回传递给他的任意对象的类
function isClass(o) {
return Object.prototype.toString.call(o).slice(8, -1);
}
//深度克隆
function deepClone(obj) {
var result, oClass = isClass(obj);
//确定result的类型
if (oClass === "Object") {
result = {};
} else if (oClass === "Array") {
result = [];
} else {
return obj;
}
for (key in obj) {
if (obj.hasOwnProperty(key)) { //原型链属性不克隆
var copy = obj[key];
if (isClass(copy) === "Object") {
result[key] = arguments.callee(copy);//递归调用
} else if (isClass(copy) === "Array") {
result[key] = arguments.callee(copy);
} else {
result[key] = obj[key];
}
}
}
return result;
}
突然就感觉解决了所有问题吧。数组,和对象的情况都考了到了呢。而且原型链上的繁杂东西也没有带出来。是不是觉得又完美了。
然而,打击总是一直存在的,在已知数据结构的情况下你可以这样使用,但是,如果不知道就不行。比如。如果有环?然后就有了死循环,无法避免的栈溢出报错。。。
而且,原型链也没有复制,从一定程度来说也不算深克隆。。那如果已知数据结构,还不如直接用for in循环来的快一些。。
所以说真正的深克隆需要考虑的问题有这些
1.JSON 克隆不支持函数、引用、undefined、Date、RegExp 等
2.递归克隆要考虑环
3.要考虑 等特殊对象的克隆方式
4.要不要克隆 proto,如果要克隆,就非常浪费内存;如果不克隆,就不是深克隆。
3.JQuery中较为完美的实现
仔细想想这些问题可能会爆炸。。。。看看jquery的那些大佬怎么实现的吧。。代码挺长的,这里有一个相近的实现。 下面的代码可以先跳过。
$ = function() {
var copyIsArray,
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty;
class2type = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Object]' : 'object'
},
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isWindow = function(obj) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
},
isPlainObject = function(obj) {
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
},
extend = function(deep, target, options) {
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) { continue; }
if (deep && copy
&& (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
return target;
};
return { extend : extend };
}();
与之前相比。多了这些有趣的代码
if (target === copy) { continue; } //避免了死循环
在isPlainObject函数中
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
//以下情况不复制
1. 对象为undefined;
2. 转为String时不是"[object Object]";
3. obj是一个DOM元素;
4. obj是window。
之所以不对DOM元素和window进行深复制,可能是因为它们包含的属性太多了;尤其是window对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
//如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过prototye继承来的,这种情况也不进行深复制。
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
// 这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,
会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。
上面的代码和和jQuery的实现就很相似。做到了,各种类型的判断,死循环的避免,而对于复杂的原型拷贝问题,它做了部分浅复制(关于原型做深复制很不必要,而且浪费大量时间和内存)。大部分情况下是真深拷贝,也是一种较为完美的解决方案了。
4.优雅实现-immutable.js
深复制已经解决到这个层面了,可以说在这个角度没有更优的解决方案了吧。回归到问题本身。我们为什么要深复制这个对象。如果要求一样,直接使用原来的对象不行吗?真的不一样的话,为什么不新建一个对象,为什么要用这么浪费性能和内存的深复制?可能你复制之后的对象的大部分属性还是没有用的。这就牵扯到了一个js库。。。
在React出来的同时出了一个js库-immutable.js,怎么说呢,它真的是一个很厉害的库,也是一个很厉害的想法,但是react优点遮挡了他的光辉吧。这也是一个react的官方推荐库。一般它会和react一起使用,但是它单独也可以使用,它是一种结构复用的思想。也就是数据不可变,什么意思呢,就是用immutable建立的对象不会被改变,会产生一个新分支来共用它的结构,下图这样。
immutable.gif具体了解的话来看一下这两篇文章。
Immutable 详解及 React 中实践
官方文档
const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50
这种结构复用的解决方案基本上已经脱离了深复制的层面,不是从解决深复制,而是单纯的不让深复制产生。从根源上解决了这个问题,实现了时间复杂度和空间复杂度的双优化。但是对于已有的含复杂原型链的对象,可能它也没有太好的解决方法
网友评论