The three implementation methods of deep copy are: 1. Recursively copy all hierarchical attributes; 2. Use parse and stringify of JSON objects; 3. Borrow the extend method of JQ.
The three implementation methods of deep copy are:
1, recursively copy all Hierarchical attributes
function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a=[1,2,3,4], b=deepClone(a); a[0]=2; console.log(a,b);
As imagined before, now b is out of the control of a and is no longer affected by a.
I emphasize again that deep copy is to copy the attributes of each level of the object. You can see an example. There is an extend method in JQ that can also copy objects. Let’s take a look.
let a=[1,2,3,4], b=a.slice(); a[0]=2; console.log(a,b);
Does that mean that the slice method is also a deep copy? After all, b is not affected by a. , as mentioned above, will deep copy copy the attributes of all levels? In this example, we change a
let a=[0,1,[2,3],4], b=a.slice(); a[0]=1; a[2][0]=1; console.log(a,b);
The copy is not complete, and one of the b objects The first-level attributes are indeed unaffected, but the second-level attributes still fail to be copied successfully and are still unable to escape the control of a, indicating that slice is not a true deep copy at all.
Here is a picture quoted from Zhihu Q&A
The attributes of the first layer are indeed deeply copied and have independent memory, but the deeper attributes But the address is still shared, which is why the above problem is caused.
Similarly, the concat method and slice also have this situation. They are not true deep copies. You need to pay attention here.
2. In addition to recursion, we can also borrow parse and stringify of JSON objects
function deepClone(obj){ let _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone } let a=[0,1,[2,3],4], b=deepClone(a); a[0]=1; a[2][0]=1; console.log(a,b);
As you can see, here b It is completely unaffected by a.
By the way, in addition to implementing deep copy, JSON.stringify and JSON.parse can also be combined with localStorage to implement object array storage. If you are interested, you can read this article by the blogger.
localStorage stores arrays, objects, localStorage, sessionStorage stores array objects
3. In addition to the above two methods, we can also borrow JQ's extend method.
$.extend( [deep ], target, object1 [, objectN ] )
deep indicates whether to copy deeply. If it is true, it is a deep copy. If it is false, it is a shallow copy.
target Object Type target object, the member properties of other objects will be attached to this object.
object1 objectNOptional. Object type The first and Nth merged object.
let a=[0,1,[2,3],4], b=$.extend(true,[],a); a[0]=1; a[2][0]=1; console.log(a,b);
You can see that the effect is the same as the above method, but it only needs to rely on the JQ library.
Having said so much, understanding deep copy is not only for coping with interview questions, it is also very useful in actual development. For example, a bunch of data is returned in the background, and you need to perform operations on the data. However, in the case of multi-person development, you have no way to know whether this pile of data has other functions that need to be used. Direct modification may cause hidden problems. Copying can help you operate data more safely and securely. Use deep copy according to the actual situation, which is probably what it means.
4.lodash's _.cloneDeep()
The following is a solution to the problem of deep copy that I read.
First convert an object into a json object. Then parse this json object.
let obj = {a:{b:22}};let copy = JSON.parse(JSON.stringify(obj));
The advantage of this method is that the code is relatively simple to write. But the shortcomings are also obvious. You first create a temporary, possibly large string, only to put it back into the parser. Another disadvantage is that this method cannot handle cyclic objects.
The following loop object will throw an exception when using this method
let a = {};let b = {a};a.b = b;let copy = JSON.parse(JSON.stringify(a));
let a = {};let b = new Set();b.add(11);a.test = b;let copy = JSON.parse(JSON.stringify(a));
copy的值打印如下
对比发现,Set已丢失。
MessageChannel
建立两个端,一个端发送消息,另一个端接收消息。
function structuralClone(obj) { return new Promise(resolve =>{ const {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }) } const obj = /* ... */; structuralClone(obj).then(res=>{ console.log(res); })
这种方法的优点就是能解决循环引用的问题,还支持大量的内置数据类型。缺点就是这个方法是异步的。
History API
利用history.replaceState。这个api在做单页面应用的路由时可以做无刷新的改变url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页面中不要把原有的路由逻辑搞乱了。所以我们在克隆完一个对象的时候,要恢复路由的原状。
function structuralClone(obj) { const oldState = history.state; history.replaceState(obj, document.title); const copy = history.state; history.replaceState(oldState, document.title); return copy; }var obj = {};var b = {obj}; obj.b = bvar copy = structuralClone(obj); console.log(copy);
这个方法的优点是。能解决循环对象的问题,也支持许多内置类型的克隆。并且是同步的。但是缺点就是有的浏览器对调用频率有限制。比如Safari 30 秒内只允许调用 100 次
Notification API
这个api主要是用于桌面通知的。如果你使用Facebook的时候,你肯定会发现时常在浏览器的右下角有一个弹窗,对就是这家伙。我们也可以利用这个api实现js对象的深拷贝。
function structuralClone(obj) { return new Notification('', {data: obj, silent: true}).data; }var obj = {};var b = {obj}; obj.b = bvar copy = structuralClone(obj); console.log(copy)
同样是优点和缺点并存,优点就是可以解决循环对象问题,也支持许多内置类型的克隆,并且是同步的。缺点就是这个需要api的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在http协议的情况下会提示你再https的场景下使用。
lodash的_.cloneDeep()
支持循环对象,和大量的内置类型,对很多细节都处理的比较不错。推荐使用。
支持的类型有很多
我们这里再次关注一下lodash是如何解决循环应用这个问题的?
从相关的代码中。我们可以发现。lodash是用一个栈记录了。所有被拷贝的引用值。如果再次碰到同样的引用值的时候,不会再去拷贝一遍。而是利用之前已经拷贝好的值。
我们仅仅实现一个简易点的深拷贝。能优雅的处理循环引用的即可。在实现深拷贝之前,我们首先温习回顾一下js中的遍历对象的属性的方法和各种方法的优缺点。
js中遍历一个对象的属性的方法
实现深拷贝,解决循环引用问题
/** * 判断是否是基本数据类型 * @param value */function isPrimitive(value){ return (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean') }/** * 判断是否是一个js对象 * @param value */function isObject(value){ return Object.prototype.toString.call(value) === "[object Object]"}/** * 深拷贝一个值 * @param value */function cloneDeep(value){ // 记录被拷贝的值,避免循环引用的出现 let memo = {}; function baseClone(value){ let res; // 如果是基本数据类型,则直接返回 if(isPrimitive(value)){ return value; // 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值 }else if(Array.isArray(value)){ res = [...value]; }else if(isObject(value)){ res = {...value}; } // 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝 Reflect.ownKeys(res).forEach(key=>{ if(typeof res[key] === "object" && res[key]!== null){ //此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题 if(memo[res[key]]){ res[key] = memo[res[key]]; }else{ memo[res[key]] = res[key]; res[key] = baseClone(res[key]) } } }) return res; } return baseClone(value) }
验证我们写的cloneDeep是否能解决循环应用的问题
var obj = {};var b = {obj}; obj.b = bvar copy = cloneDeep(obj); console.log(copy);
The above is the detailed content of What are the three implementation methods of deep copy?. For more information, please follow other related articles on the PHP Chinese website!