目錄
JSON.parse" >JSON.parse
Structured Clone 结构化克隆算法" >Structured Clone 结构化克隆算法
实现一个简易点的深拷贝,以解决循环引用的问题为目标
首頁 常見問題 深拷貝的三種實作方式是什麼

深拷貝的三種實作方式是什麼

Dec 01, 2020 pm 12:01 PM
深拷貝

深拷貝的三種實作方式分別是: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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1665
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24