您好,登錄后才能下訂單哦!
這篇文章主要介紹深拷貝怎么實現,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
深拷貝的三種實現方式分別是: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對象。然后再解析這個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已丟失。
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);
以上是“深拷貝怎么實現”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。