lodash
基本上成為了寫 javascript 工具庫的標配,它廣泛應用在各種服務端以及前端應用中,但是它的包體積略大了一些。對於服務端來說,包的體積並不是十分的重要,或者換句話說,不像前端那樣對包的體積特別敏感,一分一毫都會影響頁面開啟的效能,從而影響使用者體驗。
正因為前端包體積對於使用者體驗的重要性,因此有各種各樣減小包體積的方法。針對 lodash
來說,你完全不必要引入 lodash
的所有工具函式,你只需要按需引入或者直接使用單函式包。關於按需引入你可以參考以下文章
在針對我的個人站點中的 lodash
進行優化時,如果沒記錯的話,lodash
從以前 gzip 後的 80KB
變為了 20KB
,相對來說還是比較大。而當我全域性搜尋了 lodash 的引用之後,發現 90% 的場景都是在使用 _.get
。
另外,隨著 ES6+
的發展,以及瀏覽器與 Node 對它的支援,很多 lodash
的函式都很容易自己來實現或者說已被實現,如 _.assign
,_.trim
,_.startsWith
等等已被 ES6+ 實現,而 _.uniq
又很容易通過 new Set()
來解決。有人就在 github 上總結了 you-dont-need/You-Dont-Need-Lodash-Underscore,其中囊括了很多工具函式很簡易的實現。
鑑於本站點就是我作為試驗田用來實踐各種技術,於是我決定自己來實現 lodash
的一些工具函式。get
與 merge
兩個函式在我使用時比較多,且相對來說比較複雜一些,這裡貼一下我的實現程式碼。
本文地址: 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
中也可能是陣列的路徑,全部轉化成 .
運算子並組成陣列
// a[3].b -> a.3.b
const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.')
複製程式碼
然後層層迭代屬性即可,另外注意 null
與 undefined
取屬性會報錯,所以使用 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) ? [] : {})
}
複製程式碼