摘要:深拷貝是一件看起來很簡單的事情,但其實一點兒也不簡單。我們也可以利用這個實現(xiàn)對象的深拷貝。而是利用之前已經(jīng)拷貝好的值。深拷貝的詳細(xì)的源碼可以在這里查看。大功告成我們雖然的確解決了深拷貝的大部分問題。
js深拷貝是一件看起來很簡單的事情,但其實一點兒也不簡單。對于循環(huán)引用的問題還有一些內(nèi)置數(shù)據(jù)類型的拷貝,如Map, Set, RegExp, Date, ArrayBuffer 和其他內(nèi)置類型。處理起來并非像想象的那么簡單。下面終結(jié)一下在實際的項目中,使用的一些深拷貝的方法的優(yōu)缺點。大家也可以關(guān)注我的GitHub,互相交流學(xué)習(xí)進(jìn)步。JSON.parse
先將一個對象轉(zhuǎn)為json對象。然后再解析這個json對象。
let obj = {a:{b:22}}; let copy = JSON.parse(JSON.stringify(obj));
這種方法的優(yōu)點就是代碼寫起來比較簡單。但是缺點也是顯而易見的。你先是創(chuàng)建一個臨時的,可能很大的字符串,只是為了把它重新放回解析器。另一個缺點是這種方法不能處理循環(huán)對象。
如下面的循環(huán)對象用這種方法的時候會拋出異常
let a = {}; let b = {a}; a.b = b; let copy = JSON.parse(JSON.stringify(a));
諸如 Map, Set, RegExp, Date, ArrayBuffer 和其他內(nèi)置類型在進(jìn)行序列化時會丟失。
let a = {}; let b = new Set(); b.add(11); a.test = b; let copy = JSON.parse(JSON.stringify(a));
a 的值打印如下
copy的值打印如下
對比發(fā)現(xiàn),Set已丟失。
Structured Clone 結(jié)構(gòu)化克隆算法 MessageChannel建立兩個端,一個端發(fā)送消息,另一個端接收消息。
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); })
這種方法的優(yōu)點就是能解決循環(huán)引用的問題,還支持大量的內(nèi)置數(shù)據(jù)類型。缺點就是這個方法是異步的。
History API利用history.replaceState。這個api在做單頁面應(yīng)用的路由時可以做無刷新的改變url。這個對象使用結(jié)構(gòu)化克隆,而且是同步的。但是我們需要注意,在單頁面中不要把原有的路由邏輯搞亂了。所以我們在克隆完一個對象的時候,要恢復(fù)路由的原狀。
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 = b var copy = structuralClone(obj); console.log(copy);
這個方法的優(yōu)點是。能解決循環(huán)對象的問題,也支持許多內(nèi)置類型的克隆。并且是同步的。但是缺點就是有的瀏覽器對調(diào)用頻率有限制。比如Safari 30 秒內(nèi)只允許調(diào)用 100 次
Notification API這個api主要是用于桌面通知的。如果你使用Facebook的時候,你肯定會發(fā)現(xiàn)時常在瀏覽器的右下角有一個彈窗,對就是這家伙。我們也可以利用這個api實現(xiàn)js對象的深拷貝。
function structuralClone(obj) { return new Notification("", {data: obj, silent: true}).data; } var obj = {}; var b = {obj}; obj.b = b var copy = structuralClone(obj); console.log(copy)
同樣是優(yōu)點和缺點并存,優(yōu)點就是可以解決循環(huán)對象問題,也支持許多內(nèi)置類型的克隆,并且是同步的。缺點就是這個需要api的使用需要向用戶請求權(quán)限,但是用在這里克隆數(shù)據(jù)的時候,不經(jīng)用戶授權(quán)也可以使用。在http協(xié)議的情況下會提示你再https的場景下使用。
lodash的_.cloneDeep()支持循環(huán)對象,和大量的內(nèi)置類型,對很多細(xì)節(jié)都處理的比較不錯。推薦使用。
支持的類型有很多
我們這里再次關(guān)注一下lodash是如何解決循環(huán)應(yīng)用這個問題的?
從相關(guān)的代碼中。我們可以發(fā)現(xiàn)。lodash是用一個棧記錄了。所有被拷貝的引用值。如果再次碰到同樣的引用值的時候,不會再去拷貝一遍。而是利用之前已經(jīng)拷貝好的值。
lodash深拷貝的詳細(xì)的源碼可以在這里查看。
https://github.com/lodash/lod...
我們僅僅實現(xiàn)一個簡易點的深拷貝。能優(yōu)雅的處理循環(huán)引用的即可。在實現(xiàn)深拷貝之前,我們首先溫習(xí)回顧一下js中的遍歷對象的屬性的方法和各種方法的優(yōu)缺點。
js中遍歷一個對象的屬性的方法Object.keys() 僅僅返回自身的可枚舉屬性,不包括繼承來的,更不包括Symbol屬性
Object.getOwnPropertyNames() 返回自身的可枚舉和不可枚舉屬性。但是不包括Symbol屬性
Object.getOwnPropertySymbols() 返回自身的Symol屬性
for...in 可以遍歷對象的自身的和繼承的可枚舉屬性,不包含Symbol屬性
Reflect.ownkeys() 返回對象自身的所有屬性,不管是否可枚舉,也不管是否是Symbol。注意不包括繼承的屬性
實現(xiàn)深拷貝,解決循環(huán)引用問題/** * 判斷是否是基本數(shù)據(jù)類型 * @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){ // 記錄被拷貝的值,避免循環(huán)引用的出現(xiàn) let memo = {}; function baseClone(value){ let res; // 如果是基本數(shù)據(jù)類型,則直接返回 if(isPrimitive(value)){ return value; // 如果是引用數(shù)據(jù)類型,我們淺拷貝一個新值來代替原來的值 }else if(Array.isArray(value)){ res = [...value]; }else if(isObject(value)){ res = {...value}; } // 檢測我們淺拷貝的這個對象的屬性值有沒有是引用數(shù)據(jù)類型。如果是,則遞歸拷貝 Reflect.ownKeys(res).forEach(key=>{ if(typeof res[key] === "object" && res[key]!== null){ //此處我們用memo來記錄已經(jīng)被拷貝過的引用地址。以此來解決循環(huán)引用的問題 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是否能解決循環(huán)應(yīng)用的問題
var obj = {}; var b = {obj}; obj.b = b var copy = cloneDeep(obj); console.log(copy);
完美。大功告成
我們雖然的確解決了深拷貝的大部分問題。不過很多細(xì)節(jié)還沒有去處理。在生產(chǎn)環(huán)境,我們還是要使用lodash的cloneDeep。cloneDeep對每個數(shù)據(jù)類型都多帶帶處理的非常好。比如ArrayBuffer什么的。我們都沒有處理。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/95887.html
摘要:所以,深拷貝是對對象以及對象的所有子對象進(jìn)行拷貝實現(xiàn)方式就是遞歸調(diào)用淺拷貝對于深拷貝的對象,改變源對象不會對得到的對象有影響。 上一篇 JavaScript中的繼承 前言 文章開始之前,讓我們先思考一下這幾個問題: 為什么會有淺拷貝與深拷貝 什么是淺拷貝與深拷貝 如何實現(xiàn)淺拷貝與深拷貝 好了,問題出來了,那么下面就讓我們帶著這幾個問題去探究一下吧! 如果文章中有出現(xiàn)紕漏、錯誤之處...
摘要:所以,深拷貝是對對象以及對象的所有子對象進(jìn)行拷貝實現(xiàn)方式就是遞歸調(diào)用淺拷貝對于深拷貝的對象,改變源對象不會對得到的對象有影響。 為什么會有淺拷貝與深拷貝什么是淺拷貝與深拷貝如何實現(xiàn)淺拷貝與深拷貝好了,問題出來了,那么下面就讓我們帶著這幾個問題去探究一下吧! 如果文章中有出現(xiàn)紕漏、錯誤之處,還請看到的小伙伴多多指教,先行謝過 以下↓ 數(shù)據(jù)類型在開始了解 淺拷貝 與 深拷貝 之前,讓我們先...
摘要:實現(xiàn)實現(xiàn)一個深拷貝函數(shù),就不得不說的數(shù)值類型。類型來看下面代碼,結(jié)果會返回啥呢答案是有時候保存了元素,一不小心進(jìn)行深拷貝,上面的深拷貝函數(shù)就缺少了對元素的判斷。在不同的場景下,要根據(jù)業(yè)務(wù)場景,判斷是否需要使用深拷貝。 javascript深拷貝是初學(xué)者甚至有經(jīng)驗的開發(fā)著,都會經(jīng)常遇到問題,并不能很好的理解javascript的深拷貝。 深拷貝(deepClone)? 與深拷貝相對的就是...
摘要:前言里面淺拷貝和深拷貝是非常關(guān)鍵的知識點,今天就來通過本文清楚的了解深淺拷貝以及該如何實現(xiàn)這兩種拷貝方式。對象的拷貝又分為淺拷貝和深拷貝。印證了上述所說的對于所有的基本類型,簡單的賦值已經(jīng)是實現(xiàn)了深拷貝。 前言 JavaScript里面淺拷貝和深拷貝是非常關(guān)鍵的知識點,今天就來通過本文清楚的了解深淺拷貝以及該如何實現(xiàn)這兩種拷貝方式。 深淺拷貝的區(qū)別 拷貝:其實就是一個對象復(fù)制給另外...
摘要:而在這個運算符的相關(guān)用例中,往往會涉及到其他知識點,深拷貝和淺拷貝就是其中之一。即對象的淺拷貝會對主對象的值進(jìn)行拷貝,而該值有可能是一個指針,指向內(nèi)存中的同一個對象。,可以看到深拷貝和淺拷貝是對復(fù)制引用類型變量而言的。 在ES6的系列文章中,基本都會提到Spread——擴展運算符(...)。而在這個運算符的相關(guān)用例中,往往會涉及到其他知識點,深拷貝和淺拷貝就是其中之一。 背景知識 在討...
摘要:原文地址基礎(chǔ)心法深淺拷貝歡迎。上面的代碼是最簡單的利用賦值操作符實現(xiàn)了一個淺拷貝,可以很清楚的看到,隨著和改變,和也隨著發(fā)生了變化。展開運算符結(jié)論實現(xiàn)的是對象第一層的深拷貝。 原文地址:JavaScript基礎(chǔ)心法——深淺拷貝 歡迎star。 如果有錯誤的地方歡迎指正。 淺拷貝和深拷貝都是對于JS中的引用類型而言的,淺拷貝就只是復(fù)制對象的引用,如果拷貝后的對象發(fā)生變化,原對象也會發(fā)生...
閱讀 2079·2021-09-22 15:54
閱讀 1838·2021-09-04 16:40
閱讀 864·2019-08-30 15:56
閱讀 2630·2019-08-30 15:44
閱讀 2156·2019-08-30 13:52
閱讀 1129·2019-08-29 16:35
閱讀 3350·2019-08-29 16:31
閱讀 2570·2019-08-29 13:48