如何實現相似 lodash 的 get 與 merge 函數

lodash 基本上成爲了寫 javascript 工具庫的標配,它普遍應用在各類服務端以及前端應用中,可是它的包體積略大了一些。對於服務端來講,包的體積並非十分的重要,或者換句話說,不像前端那樣對包的體積特別敏感,一分一毫都會影響頁面打開的性能,從而影響用戶體驗。javascript

正由於前端包體積對於用戶體驗的重要性,所以有各類各樣減少包體積的方法。針對 lodash 來講,你徹底沒必要要引入 lodash 的全部工具函數,你只須要按需引入或者直接使用單函數包。關於按需引入你能夠參考如下文章前端

Lessons on tree-shaking Lodash with Webpack and Babeljava

在針對個人我的站點中的 lodash 進行優化時,若是沒記錯的話,lodash 從之前 gzip 後的 80KB 變爲了 20KB,相對來講仍是比較大。而當我全局搜索了 lodash 的引用以後,發現 90% 的場景都是在使用 _.getgit

另外,隨着 ES6+ 的發展,以及瀏覽器與 Node 對它的支持,不少 lodash 的函數都很容易本身來實現或者說已被實現,如 _.assign_.trim_.startsWith 等等已被 ES6+ 實現,而 _.uniq 又很容易經過 new Set() 來解決。有人就在 github 上總結了 you-dont-need/You-Dont-Need-Lodash-Underscore,其中囊括了不少工具函數很簡易的實現。github

鑑於本站點就是我做爲試驗田用來實踐各類技術,因而我決定本身來實現 lodash 的一些工具函數。getmerge 兩個函數在我使用時比較多,且相對來講比較複雜一些,這裏貼一下個人實現代碼。數組

本文地址: shanyue.tech/post/lodash…瀏覽器

get

在 js 中常常會出現嵌套調用這種狀況,如 a.b.c.d.e,可是這麼寫很容易拋出異常。你須要這麼寫 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e,可是顯得有些囉嗦與冗長了。特別是在 graphql 中,這種嵌套調用更是難以免。性能優化

這時就須要一個 get 函數,使用 get(a, 'b.c.d.e') 簡單清晰,而且容錯性提升了不少。如下是須要經過的幾個測試用例服務器

get({ a: null }, 'a.b.c', 3)
// output: 3

get({ a: undefined }, 'a', 3)
// output: 3

get({ a: null }, 'a', 3)
// output: 3

get({ a: [{ b: 1 }]}, 'a[0].b', 3)
// output: 1
複製代碼

path 中也多是數組的路徑,所有轉化成 . 運算符並組成數組less

// a[3].b -> a.3.b
const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.')
複製代碼

而後層層迭代屬性便可,另外注意 nullundefined 取屬性會報錯,因此使用 Object 包裝一下。

function get (source, path, defaultValue = undefined) {
  // a[3].b -> a.3.b
  const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.')
  let result = source
  for (const p of paths) {
    result = Object(result)[p]
    if (result === undefined) {
      return defaultValue
    }
  }
  return result
}
複製代碼

merge

merge 用來遞歸合併對象,至關於深層的 Object.assign。在 graphql 中會普遍用到 merge,如會常用 merge 來合併全部的 resolver,特別是 Mutation 以下示例

const rootResolver = {
  Query: {
  
  },
  Mutation: {
    login () {}
  }
}

const userResolver = {
  User: {
    createUser() {}
  }
}

const resolver = merge(rootResolver, userResolver)
// output
// {
// Query: {},
// Mutation: {
// login () {},
// createUser () {}
// }
// }
複製代碼

另外,在前端進行 graphql 的查詢時也常常須要使用到 merge。如在進行頁面的性能優化時,爲了不一個 Query 耗時太久,頁面渲染過於耗時,會拆成兩個 Query,先渲染響應快的數據,在慢慢等待個別響應慢的數據。

如下是一個關於我的主頁信息的 Query,可是其中有一個字段 dataNeedDelay3s 會在服務器耗時許久,會由於此字段加大了用戶的等待時間,形成不友好的用戶體驗。此時會把此字段單獨拆掉,優先渲染其它我的信息。

query PROFILE {
  me {
    id
    age
    name
    # 須要耗時3s的字段
    dataNeedDelay3s
  }
}

# 拆爲如下兩個茶軒
query PROFILE_ONE {
  me {
    id
    age
    name
  }
}

query PROFILE_TWO {
  me {
    dataNeedDelay3s
  }
}
複製代碼

此時就有 merge 的需求,查詢完成後把兩次查詢結果給拼到一塊兒。

關於拆 graphql 的 Query 的需求無處不在,如在服務端渲染時,須要把權限資源與非權限資源分開。

這裏講述下如何實現 merge

function isObject (value) {
  const type = typeof value
  return value !== null && (type === 'object' || type === 'function')
}

// { a: [{ b: 2 }] } { a: [{ c: 2 }]} -> { a: [{b:2}, {c:2}]}
// merge({o: {a: 3}}, {o: {b:4}}) => {o: {a:3, b:4}}
function merge (source, other) {
  if (!isObject(source) || !isObject(other)) {
    return other === undefined ? source : other
  }
  // 合併兩個對象的 key,另外要區分數組的初始值爲 []
  return Object.keys({
    ...source,
    ...other
  }).reduce((acc, key) => {
    // 遞歸合併 value
    acc[key] = merge(source[key], other[key])
    return acc
  }, Array.isArray(source) ? [] : {})
}
複製代碼
相關文章
相關標籤/搜索