摘要:想要簡單點難道我深拷貝一個變量還要引入這么麻煩嗎沒有簡單點的辦法嗎嗯,可能有點不是那么酷炫,但是他確實可以滿足要求,而且也無須引入其他的庫。
問題
由于 js 的傳參方式有時會遇到這樣的場景:
function setTime(data) { let result = {}; result.obj = data.obj || {}; result.obj.time = Date.now(); return result } let data = { title:"loooook!", obj: { name: "keo", age: "12" } } let res = setTime(data); console.log("res",res); //res { obj: { name: "keo", age: "12", time: 1533625350183 } } console.log("data",data); //data { title: "loooook!", obj: { name: "keo", age: "12", time: 1533625350183 } }
我只是想繼承參數的部分數據,并在此基礎添加一些東西,但是參數 data 的源數據也被我改動了,如果之后有其他人想要從data獲取數據,他可能還需要注意是否有像 setTime 這樣的函數調用它。
一點修改function setTime(data) { let result = {}; result.obj = {}; Object.assign(result.obj,data.obj) result.obj.time = Date.now(); return result }
嗯,或者你也可以用 for...in,注意下二者的不同。
我們知道 Object.assign 只是淺拷貝,如果 data.obj 的屬性值仍然有引用類型的話,那么還是會遇見同樣的問題。
那要怎么辦?難道要遍歷data下每個屬性的值?一個個復制過來?我們看看 lodash 是怎么做的
你猜的沒錯,的確是要深度遍歷的。
在 baseClone方法內,拿到要拷貝的對象 value 后,先檢查其類型,然后由對應的 handler 來處理,比如value是數組類型,則使 result 為同樣長度的數據,然后對每一項都遞歸調用 baseClone,直到 value 是非引用類型,返回 value的值;如果是普通對象類型,則使 result 為空數組,然后拿取value的key,對每個key的賦值也是遞歸調用baseClone。
難道我深拷貝一個變量還要引入 lodash 這么麻煩嗎 ?沒有簡單點的辦法嗎?
JSON.parse(JSON.stringify(param))
嗯,可能有點不是那么酷炫,但是他確實可以滿足要求,而且也無須引入其他的庫。但如果它真的這么完美,為什么 lodash 不這么寫呢?
的確,它的缺點還挺多的,這里取幾個我覺得比較重要的:
Set 類型、Map 類型以及 Buffer 類型會被轉換成 {}
undefined、任意的函數以及 symbol 值,在序列化過程中會被忽略(出現在非數組對象的屬性值中時)或者被轉換成 null(出現在數組中時)
對包含循環引用的對象(對象之間相互引用,形成無限循環)執行此方法,會拋出錯誤
所有以 symbol 為屬性鍵的屬性都會被完全忽略掉,即便 replacer 參數中強制指定包含了它們
是啊,畢竟JSON的兩個方法本身就只是用來轉換 js 內的對象為 JSON 格式的,上述幾點甚至都不是缺點,是我們想借用其他方法做深拷貝時遇到的問題。
既然是問題那應該可以解決吧,比如第一條和第二條,在 stringify 時判斷類型,轉化成 帶類型標識符的對象字符串如:Set [1,2,3,4,5],然后在parse的時候對字符串進行解析,特別的類型調用對應的構造函數... 聽起來變得更麻煩了,沒關系,忍忍把各個類型的處理都寫了;針對第三條,拋錯了?沒關系,我 try catch 包起來...,什么?循環引用?
循環引用?function parse (param){ return JSON.parse(JSON.stringify(param)) } var a = {} var b = {} a["b"] = b b["a"] = a console.log(parse(a)) //TypeError: Converting circular structure to JSON at JSON.stringify
如上代碼, 變量a 和 b 互相引用對方,此時如果借用 JSON 的方法來進行深拷貝的話,會報循環結構轉換轉換 JSON 錯誤。這個問題怎么解決呢?我們再翻出 lodash 的源碼看看...
// Check for circular references and return its corresponding clone. stack || (stack = new Stack); var stacked = stack.get(value); if (stacked) { return stacked; } stack.set(value, result);
這里的 value 和 result 分別是是一次遍歷中 要拷貝的值 和 拷貝的結果。stack 是一個用來儲存每次對應的 value 和 result 的對象, stack下有一塊用于儲存的數組結構,該數組的每一項記錄了單次遍歷中的 value 和 result,后二者再次以數組的形式存儲,以 value 做為下標 0 的項,result 為下標 1 的項(這里不用對象的 key-value 形式可能是因為循環引用的變量無法使用 JSON.stringify 轉換成字符串,只能 toString 轉成 object Object);stack 是做為參數貫穿整個遍歷過程的,每次遍歷時都會以當前的 value 值進行查找(這里的查找直接是判斷內存地址相等),如果能在 stack 中查到到對應的結果,則直接返回記錄中的result,不再繼續遞歸。
好了,循環引用的問題我們解決了,鼓掌!但是我也放棄使用 JSON 方法了...還有沒有其他直接點的方法呢?
結構化克隆算法是由HTML5規范定義的用于復制復雜JavaScript對象的算法,它通過遞歸輸入對象來構建克隆,同時保持先前訪問過的引用的映射,以避免無限遍歷循環。
怎么用?
emmm... 它還不能直接使用,你得依靠一些其他的 API ,間接的使用它。
postMessage()
function StructuredClone(param) { return new Promise(function (res, rej) { const {port1, port2} = new MessageChannel(); port2.onmessage = ev => res(ev.data); port1.postMessage(param); }) } StructuredClone(objects).then(result => console.log(result))
什么??還是異步的... 不,我希望能使用同步的方法使用它。
history()
function structuralClone(obj) { const oldState = history.state; history.replaceState(obj, document.title); const copy = history.state; history.replaceState(oldState, document.title); return copy; } const clone = structuralClone(objects);
如你所見,我們要借用一下 history.replaceState 這個方法,但是我們不能改變 history 原有的狀態,所以用完就要恢復原狀,當無事發生過。
至少,這是個同步的方法...,如果是同步的場景可以考慮一下...
這里的測試代碼是使用的 [Deep-copying in JavaScript] (https://dassur.ma/things/deep... 一文中的,并再次基礎做了一些修改。
結果!單位 μs (繆斯),計算時間的用的接口是 performance.now()結果精確到5微秒。
chrome
safari
...em...Safari瀏覽器在調用完 postMessage 方法后就...沒有然后了...表格都沒刷出來...等了 40 s 終于刷出第一欄...
注釋完 postMessage 又發現不能頻繁的調用 history 。
firefox
...em.. 調用 history 相關 api 對 firefox 好像壓力很大,以至于循環都有些錯亂...于是注釋了相關代碼
就結果而言好像看不出什么區別,可能是我的數據不好,大家可以去看看原文,有展示閱讀性更好的圖表,盡管沒有 lodash 就是了。
結果回到我們最初的問題,我們只是想深拷貝一個 js 對象,如果只是一個比較"普通"的對象,用JSON的方法簡單又快捷,但是如果這個對象有些“復雜”,似乎使用 lodash 的方法是比較好的選擇,而且 lodash 連 Structured Clone 算法忽視的 symbol 類型 和 Function 也考慮其中,兼容性也沒問題,也不會在不同的瀏覽器發生意外的狀況...
lodash 萬歲!lol!!
參考閱讀:
Deep-copying in JavaScript
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96722.html
let和const webpack構建的項目,直接廢棄var,直接使用let代替var for循環中使用let而不是var 變量聲明之后就不會改變,請使用const 解構賦值 概念: 先解構再賦值,先從一堆數據中找出自己需要的數據,然后將找到的數據賦值給事先定義好的變量 // 對象的解構賦值 // 使用場景 // 1,等號右邊是大json,等號左邊是變量,這樣可快速獲取大json中數據,后續可...
本文收集學習過程中使用到的資源。 持續更新中…… 項目地址 https://github.com/abc-club/f... 目錄 vue react react-native Weex typescript Taro nodejs 常用庫 css js es6 移動端 微信公眾號 小程序 webpack GraphQL 性能與監控 高質文章 趨勢 動效 數據結構與算法 js core 代碼規范...
摘要:這里為什么是一個數組呢因為這就是標題所描述的可定制,如果頁面內重置絕大部分狀態,但需要保留其中一些狀態的時候我們可以通過我們傳遞過來的值來剔除相應的,使其不被更新。 在正式場景中我們經常遇到一個問題,就是登出頁面或其他操作的時候,我們需要重置所有的vuex,讓其變為初始狀態,那么,就涉及到了多種方法:1、頁面刷新: window.location.reload() 這個方法通過路由判斷...
摘要:這里為什么是一個數組呢因為這就是標題所描述的可定制,如果頁面內重置絕大部分狀態,但需要保留其中一些狀態的時候我們可以通過我們傳遞過來的值來剔除相應的,使其不被更新。 在正式場景中我們經常遇到一個問題,就是登出頁面或其他操作的時候,我們需要重置所有的vuex,讓其變為初始狀態,那么,就涉及到了多種方法:1、頁面刷新: window.location.reload() 這個方法通過路由判斷...
閱讀 1516·2021-08-09 13:47
閱讀 2776·2019-08-30 15:55
閱讀 3500·2019-08-29 15:42
閱讀 1122·2019-08-29 13:45
閱讀 3015·2019-08-29 12:33
閱讀 1748·2019-08-26 11:58
閱讀 991·2019-08-26 10:19
閱讀 2416·2019-08-23 18:00