成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

摸索 JS 內(nèi)深拷貝的最佳實踐

Jonathan Shieber / 3034人閱讀

摘要:想要簡單點難道我深拷貝一個變量還要引入這么麻煩嗎沒有簡單點的辦法嗎嗯,可能有點不是那么酷炫,但是他確實可以滿足要求,而且也無須引入其他的庫。

問題

由于 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 } }

我只是想繼承參數(shù)的部分數(shù)據(jù),并在此基礎(chǔ)添加一些東西,但是參數(shù) data 的源數(shù)據(jù)也被我改動了,如果之后有其他人想要從data獲取數(shù)據(jù),他可能還需要注意是否有像 setTime 這樣的函數(shù)調(diào)用它。

一點修改
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方法內(nèi),拿到要拷貝的對象 value 后,先檢查其類型,然后由對應的 handler 來處理,比如value是數(shù)組類型,則使 result 為同樣長度的數(shù)據(jù),然后對每一項都遞歸調(diào)用 baseClone,直到 value 是非引用類型,返回 value的值;如果是普通對象類型,則使 result 為空數(shù)組,然后拿取valuekey,對每個key的賦值也是遞歸調(diào)用baseClone。

想要簡單點

難道我深拷貝一個變量還要引入 lodash 這么麻煩嗎 ?沒有簡單點的辦法嗎?

JSON.parse(JSON.stringify(param))

嗯,可能有點不是那么酷炫,但是他確實可以滿足要求,而且也無須引入其他的庫。但如果它真的這么完美,為什么 lodash 不這么寫呢?
的確,它的缺點還挺多的,這里取幾個我覺得比較重要的:

Set 類型、Map 類型以及 Buffer 類型會被轉(zhuǎn)換成 {}

undefined、任意的函數(shù)以及 symbol 值,在序列化過程中會被忽略(出現(xiàn)在非數(shù)組對象的屬性值中時)或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時)

對包含循環(huán)引用的對象(對象之間相互引用,形成無限循環(huán))執(zhí)行此方法,會拋出錯誤

所有以 symbol 為屬性鍵的屬性都會被完全忽略掉,即便 replacer 參數(shù)中強制指定包含了它們

是啊,畢竟JSON的兩個方法本身就只是用來轉(zhuǎn)換 js 內(nèi)的對象為 JSON 格式的,上述幾點甚至都不是缺點,是我們想借用其他方法做深拷貝時遇到的問題。

既然是問題那應該可以解決吧,比如第一條和第二條,在 stringify 時判斷類型,轉(zhuǎn)化成 帶類型標識符的對象字符串如:Set [1,2,3,4,5],然后在parse的時候?qū)ψ址M行解析,特別的類型調(diào)用對應的構(gòu)造函數(shù)... 聽起來變得更麻煩了,沒關(guān)系,忍忍把各個類型的處理都寫了;針對第三條,拋錯了?沒關(guān)系,我 try catch 包起來...,什么?循環(huán)引用?

循環(huán)引用?
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

如上代碼, 變量ab 互相引用對方,此時如果借用 JSON 的方法來進行深拷貝的話,會報循環(huán)結(jié)構(gòu)轉(zhuǎn)換轉(zhuǎn)換 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);

這里的 valueresult 分別是是一次遍歷中 要拷貝的值 和 拷貝的結(jié)果。stack 是一個用來儲存每次對應的 valueresult 的對象, stack下有一塊用于儲存的數(shù)組結(jié)構(gòu),該數(shù)組的每一項記錄了單次遍歷中的 valueresult,后二者再次以數(shù)組的形式存儲,以 value 做為下標 0 的項,result 為下標 1 的項(這里不用對象的 key-value 形式可能是因為循環(huán)引用的變量無法使用 JSON.stringify 轉(zhuǎn)換成字符串,只能 toString 轉(zhuǎn)成 object Object);stack 是做為參數(shù)貫穿整個遍歷過程的,每次遍歷時都會以當前的 value 值進行查找(這里的查找直接是判斷內(nèi)存地址相等),如果能在 stack 中查到到對應的結(jié)果,則直接返回記錄中的result,不再繼續(xù)遞歸。
好了,循環(huán)引用的問題我們解決了,鼓掌!但是我也放棄使用 JSON 方法了...還有沒有其他直接點的方法呢?

其他方法

結(jié)構(gòu)化克隆算法是由HTML5規(guī)范定義的用于復制復雜JavaScript對象的算法,它通過遞歸輸入對象來構(gòu)建克隆,同時保持先前訪問過的引用的映射,以避免無限遍歷循環(huán)。

怎么用?
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 原有的狀態(tài),所以用完就要恢復原狀,當無事發(fā)生過。
至少,這是個同步的方法...,如果是同步的場景可以考慮一下...

性能展示

這里的測試代碼是使用的 [Deep-copying in JavaScript] (https://dassur.ma/things/deep... 一文中的,并再次基礎(chǔ)做了一些修改。

結(jié)果! (很懶就不畫圖表了)

單位 μs (繆斯),計算時間的用的接口是 performance.now()結(jié)果精確到5微秒。

chrome

safari

...em...Safari瀏覽器在調(diào)用完 postMessage 方法后就...沒有然后了...表格都沒刷出來...等了 40 s 終于刷出第一欄...
注釋完 postMessage 又發(fā)現(xiàn)不能頻繁的調(diào)用 history 。

firefox

...em.. 調(diào)用 history 相關(guān) api 對 firefox 好像壓力很大,以至于循環(huán)都有些錯亂...于是注釋了相關(guān)代碼

就結(jié)果而言好像看不出什么區(qū)別,可能是我的數(shù)據(jù)不好,大家可以去看看原文,有展示閱讀性更好的圖表,盡管沒有 lodash 就是了。

結(jié)果

回到我們最初的問題,我們只是想深拷貝一個 js 對象,如果只是一個比較"普通"的對象,用JSON的方法簡單又快捷,但是如果這個對象有些“復雜”,似乎使用 lodash 的方法是比較好的選擇,而且 lodash 連 Structured Clone 算法忽視的 symbol 類型 和 Function 也考慮其中,兼容性也沒問題,也不會在不同的瀏覽器發(fā)生意外的狀況...
lodash 萬歲!lol?。?/p>

參考閱讀:
Deep-copying in JavaScript

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96722.html

相關(guān)文章

  • 我用到ES6

    let和const webpack構(gòu)建的項目,直接廢棄var,直接使用let代替var for循環(huán)中使用let而不是var 變量聲明之后就不會改變,請使用const 解構(gòu)賦值 概念: 先解構(gòu)再賦值,先從一堆數(shù)據(jù)中找出自己需要的數(shù)據(jù),然后將找到的數(shù)據(jù)賦值給事先定義好的變量 // 對象的解構(gòu)賦值 // 使用場景 // 1,等號右邊是大json,等號左邊是變量,這樣可快速獲取大json中數(shù)據(jù),后續(xù)可...

    libin19890520 評論0 收藏0
  • 平時積累前端資源,持續(xù)更新中。。。

    本文收集學習過程中使用到的資源。 持續(xù)更新中…… 項目地址 https://github.com/abc-club/f... 目錄 vue react react-native Weex typescript Taro nodejs 常用庫 css js es6 移動端 微信公眾號 小程序 webpack GraphQL 性能與監(jiān)控 高質(zhì)文章 趨勢 動效 數(shù)據(jù)結(jié)構(gòu)與算法 js core 代碼規(guī)范...

    acrazing 評論0 收藏0
  • vuex重置所有state(可定制)

    摘要:這里為什么是一個數(shù)組呢因為這就是標題所描述的可定制,如果頁面內(nèi)重置絕大部分狀態(tài),但需要保留其中一些狀態(tài)的時候我們可以通過我們傳遞過來的值來剔除相應的,使其不被更新。 在正式場景中我們經(jīng)常遇到一個問題,就是登出頁面或其他操作的時候,我們需要重置所有的vuex,讓其變?yōu)槌跏紶顟B(tài),那么,就涉及到了多種方法:1、頁面刷新: window.location.reload() 這個方法通過路由判斷...

    singerye 評論0 收藏0
  • vuex重置所有state(可定制)

    摘要:這里為什么是一個數(shù)組呢因為這就是標題所描述的可定制,如果頁面內(nèi)重置絕大部分狀態(tài),但需要保留其中一些狀態(tài)的時候我們可以通過我們傳遞過來的值來剔除相應的,使其不被更新。 在正式場景中我們經(jīng)常遇到一個問題,就是登出頁面或其他操作的時候,我們需要重置所有的vuex,讓其變?yōu)槌跏紶顟B(tài),那么,就涉及到了多種方法:1、頁面刷新: window.location.reload() 這個方法通過路由判斷...

    LeviDing 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<