搜索
首页常见问题深拷贝的三种实现方式是什么

深拷贝的三种实现方式分别是:1、递归递归去复制所有层级属性;2、用JSON对象的parse和stringify实现;3、借用JQ的extend方法。

深拷贝的三种实现方式是什么

深拷贝的三种实现方式分别是:

1、递归递归去复制所有层级属性

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);

跟之前想象的一样,现在b脱离了a的控制,不再受a影响了。

这里再次强调,深拷贝,是拷贝对象各个层级的属性,可以看个例子。JQ里有一个extend方法也可以拷贝对象,我们来看看

let a=[1,2,3,4],
    b=a.slice();
a[0]=2;
console.log(a,b);

那是不是说slice方法也是深拷贝了,毕竟b也没受a的影响,上面说了,深拷贝是会拷贝所有层级的属性,还是这个例子,我们把a改改

let a=[0,1,[2,3],4],
        b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);

拷贝的不彻底啊,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。

这里引用知乎问答里面的一张图

 

第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

2.除了递归,我们还可以借用JSON对象的parse和stringify

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);

可以看到,这下b是完全不受a的影响了。

附带说下,JSON.stringify与JSON.parse除了实现深拷贝,还能结合localStorage实现对象数组存储。有兴趣可以阅读博主这篇文章。

localStorage存储数组,对象,localStorage,sessionStorage存储数组对象

3.除了上面两种方法之外,我们还可以借用JQ的extend方法。

$.extend( [deep ], target, object1 [, objectN ] )

  • deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

  • target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

  • object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 

let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

可以看到,效果与上面方法一样,只是需要依赖JQ库。

说了这么多,了解深拷贝也不仅仅是为了应付面试题,在实际开发中也是非常有用的。例如后台返回了一堆数据,你需要对这堆数据做操作,但多人开发情况下,你是没办法明确这堆数据是否有其它功能也需要使用,直接修改可能会造成隐性问题,深拷贝能帮你更安全安心的去操作数据,根据实际情况来使用深拷贝,大概就是这个意思。

4.lodash的_.cloneDeep()

以下是我参看的一位关于深拷贝的问题解决。

JSON.parse

先将一个对象转为json对象。然后再解析这个json对象。

let obj = {a:{b:22}};let copy = JSON.parse(JSON.stringify(obj));

这种方法的优点就是代码写起来比较简单。但是缺点也是显而易见的。你先是创建一个临时的,可能很大的字符串,只是为了把它重新放回解析器。另一个缺点是这种方法不能处理循环对象。

如下面的循环对象用这种方法的时候会抛出异常

let a = {};let b = {a};a.b = b;let copy = JSON.parse(JSON.stringify(a));

深拷贝的三种实现方式是什么

诸如 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失。

let a = {};let b = new Set();b.add(11);a.test = b;let copy = JSON.parse(JSON.stringify(a));

a 的值打印如下

深拷贝的三种实现方式是什么

copy的值打印如下

深拷贝的三种实现方式是什么

对比发现,Set已丢失。

Structured Clone 结构化克隆算法

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中遍历一个对象的属性的方法

  • Object.keys() 仅仅返回自身的可枚举属性,不包括继承来的,更不包括Symbol属性
  • Object.getOwnPropertyNames() 返回自身的可枚举和不可枚举属性。但是不包括Symbol属性
  • Object.getOwnPropertySymbols() 返回自身的Symol属性
  • for...in 可以遍历对象的自身的和继承的可枚举属性,不包含Symbol属性
  • Reflect.ownkeys() 返回对象自身的所有属性,不管是否可枚举,也不管是否是Symbol。注意不包括继承的属性

实现深拷贝,解决循环引用问题

/**
 * 判断是否是基本数据类型
 * @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);

以上是深拷贝的三种实现方式是什么的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
深度求索deepseek官网入口深度求索deepseek官网入口Mar 12, 2025 pm 01:33 PM

2025年开年,国产AI“深度求索”(deepseek)惊艳亮相!这款免费开源的AI模型,性能堪比OpenAI的o1正式版,并已在网页端、APP和API全面上线,支持iOS、安卓和网页版多端同步使用。深度求索deepseek官网及使用指南:官网地址:https://www.deepseek.com/网页版使用步骤:点击上方链接进入deepseek官网。点击首页的“开始对话”按钮。首次使用需进行手机验证码登录。登录后即可进入对话界面。deepseek功能强大,可进行代码编写、文件读取、创

deepseek网页版官方入口deepseek网页版官方入口Mar 12, 2025 pm 01:42 PM

国产AI黑马DeepSeek强势崛起,震撼全球AI界!这家成立仅一年半的中国人工智能公司,凭借其免费开源的大模型DeepSeek-V3和DeepSeek-R1,在性能上与OpenAI等国际巨头比肩,甚至在成本控制方面实现了突破性进展,赢得了全球用户的广泛赞誉。DeepSeek-R1现已全面上线,性能媲美OpenAIo1正式版!您可以在网页端、APP以及API接口体验其强大的功能。下载方式:支持iOS和安卓系统,用户可通过应用商店下载;网页版也已正式开放!DeepSeek网页版官方入口:ht

deepseek服务器繁忙怎么解决deepseek服务器繁忙怎么解决Mar 12, 2025 pm 01:39 PM

DeepSeek:火爆AI遭遇服务器拥堵,如何应对?DeepSeek作为2025年开年爆款AI,免费开源且性能媲美OpenAIo1正式版,其受欢迎程度可见一斑。然而,高并发也带来了服务器繁忙的问题。本文将分析原因并提供应对策略。DeepSeek网页版入口:https://www.deepseek.com/DeepSeek服务器繁忙的原因:高并发访问:DeepSeek的免费和强大功能吸引了大量用户同时使用,导致服务器负载过高。网络攻击:据悉,DeepSeek对美国金融界造成冲击,

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具