我编写了 14 个函数来转储 lodash 并减少捆绑包大小......
Lodash 和 underscore 永远改变了我编写 Javascript 的方式,但今天对于最常见的功能可能有更好的选择。
我最近检查了我们的主要应用程序,希望减少捆绑包的大小,并很快发现尽管我们尽了最大努力进行特定的功能导入,但我们仍然导入了大部分 lodash。
我们转向了 lodash-es,这有点帮助,但我仍然在关注一些占用大约 30% 软件包空间的实用函数。
问题是,作为一个节点模块,库已经做出了许多关于填充旧功能的选择,因此根据你的目标浏览器,你可能会有很多你不需要的代码。
我确定了 lodash 中使用的 14 个核心函数,并用现代 JavaScript 重写了它们,以便打包过程能够根据目标环境决定需要提供哪些 polyfill。导入文件大小显著减少。
我的代码:4.1kb(未压缩/未最小化,但在旧版浏览器上需要 polyfill)
核心功能
以下是我对该功能列表所做的事情:
匹配的功能
- 筛选
- forEach(数组和对象)
- groupBy
- 键值
- 映射(数组和对象)
- 合并
- 忽略
- 排序方式
- 独特
- uniqBy
实施“足够”
- 挑选
- 获取(不支持数组语法)
- 设置(不支持数组语法)
- 防抖动(使用 maxWait、flush、cancel)
功能
以下是这些功能、它们的作用以及我如何实现它们:
pick(函数(项目)=>值|属性名称)
我们将从它开始,pick
因为它对于其他一切非常有用。pick
将返回一个函数来从对象中提取属性 - 我的实现将把字符串转换为这个,但保留其他值。
你可以pick
像这样使用自己:
const array = [{ name: "mike", a: 1 }, { name: "bob", a: 2 }]
console.log(array.map(pick('name')) //=> ["mike", "bob"]
执行
import {get} from './get'
export function pick(fn) {
return typeof fn === "string" ? (v) => get(v,fn) : fn
}
过滤器(数组,函数(项目)=>布尔值 | 字符串)
我们filter
经常使用名称属性,因此过滤器基本上只是选择和现有的过滤函数:
const array = [{ name: "mike", a: 1 }, { name: "bob", a: 2 }, { a: 4 }]
console.log(filter(array, 'name')) //=> [{ name: "mike", a: 1 }, { name: "bob", a: 2 }]
执行
import { pick } from "./pick"
export function filter(target, fn) {
return target.filter(pick(fn))
}
forEach(数组|对象,函数(值,键))
在 lodash 中,forEach 可以使用对象或数组,因此我们需要一个可以实现这一点的实现。回调函数会获取参数value
和key
。它的工作原理如下:
const data = { a: 1, b: 2, d: "hello" }
forEach(data, (value, key)=>console.log(`${key}=${value}`)
//=> a=1
//=> b=2
//=> d=hello
执行
import { pick } from "./pick"
export function applyArrayFn(target, fnName, fn) {
fn = pick(fn)
if (Array.isArray(target)) return target[fnName](fn)
if (target && typeof target === "object")
return Object.entries(target)[fnName](([key, value], index) =>
fn(value, key, target, index)
)
throw new Error(`Cannot iterate ${typeof target}`)
}
export function forEach(target, fn) {
return applyArrayFn(target, "forEach", fn)
}
获取(对象、属性路径、默认值)
get
允许您从对象读取属性,如果未找到任何中介或最终值,它将返回默认值
const data = { a: { b: {d: 1 } } }
get(data, "a.b.d") //=> 1
get(data, "a.c.d", "hmmm") //=> hmmm
执行
export function get(object, path, defaultValue) {
const parts = path.split(".")
for (let part of parts) {
if(!object) return defaultValue
object = object[part]
}
return object ?? defaultValue
}
groupBy(数组,函数(项目)=>键|属性名称)
创建一个以函数结果(或选定的属性名称)为键的对象,其中每个值都是具有相同键的项目的数组。
const array = [{ name: "mike", type: "user" }, { name: "bob", type: "user" }, { name: "beth", type: "admin"} ]
console.log(groupBy(array, 'type'))
/*=>
{
admin: [{name: "beth", type: "admin" }],
user: [{name: "mike", type: "user" }, {name: "bob", type: "user"}]
}
*/
执行
import { pick } from "./pick"
export function groupBy(target, fn) {
fn = pick(fn)
return target
.map((value) => ({ value, key: fn(value) }))
.reduce((c, a) => {
c[a.key] = c[a.key] || []
c[a.key].push(a.value)
return c
}, {})
}
keyBy(数组,函数(项)=>键|属性名称)
类似,groupBy
但结果是与键匹配的最后一个项目 - 通常这会给出一些键是唯一的(如 id)来创建查找
const array = [{ id: "a7", name: "mike", type: "user" }, { id: "z1", name: "bob", type: "user" }, { id: "a3", name: "beth", type: "admin"} ]
console.log(keyBy(array, 'id'))
/*=>
{
"a3": {name: "beth", type: "admin", id: "a3" },
"a7": {name: "mike", type: "user", id: "a7" },
"z1": {name: "bob", type: "user", id: "z1"}
}
*/
执行
import { pick } from "./pick"
export function keyBy(target, fn) {
fn = pick(fn)
return target
.map((value) => ({ value, key: fn(value) }))
.reduce((c, a) => {
c[a.key] = a.value
return c
}, {})
}
映射(数组|对象,函数(值,键)=>值|属性名称)
映射对象和数组(如forEach
)
const records = {
"a3": {name: "beth", type: "admin" },
"a7": {name: "mike", type: "user" },
"z1": {name: "bob", type: "user"}
}
console.log(map(records, 'name')) /=> ["beth", "mike", "bob"]
执行
import { pick } from "./pick"
export function applyArrayFn(target, fnName, fn) {
fn = pick(fn)
if (Array.isArray(target)) return target[fnName](fn)
if (target && typeof target === "object")
return Object.entries(target)[fnName](([key, value], index) =>
fn(value, key, target, index)
)
throw new Error(`Cannot iterate ${typeof target}`)
}
export function forEach(target, fn) {
return applyArrayFn(target, "map", fn)
}
合并(目标,...源)
工作原理类似Object.assign
,但会深入底层结构来更新更深层的对象,而不是替换它们。
const record = { id: "2", name: "Beth", value: 3, ar: ["test", { a: 3, d: { e: 4 } }] }
console.log(merge(record, { ar: [{ b: 1 }, { c: 3, d: { f: 5 } }]))
/*=>
{
id: "2",
name: "Beth",
value: 3,
ar: [{ b: 1 }, { c: 3, d: { f: 5, e: 4 } }]
}
*/
执行
export function merge(target, ...sources) {
for (let source of sources) {
mergeValue(target, source)
}
return target
function innerMerge(target, source) {
for (let [key, value] of Object.entries(source)) {
target[key] = mergeValue(target[key], value)
}
}
function mergeValue(targetValue, value) {
if (Array.isArray(value)) {
if (!Array.isArray(targetValue)) {
return [...value]
} else {
for (let i = 0, l = value.length; i < l; i++) {
targetValue[i] = mergeValue(targetValue[i], value[i])
}
return targetValue
}
} else if (typeof value === "object") {
if (targetValue && typeof targetValue === "object") {
innerMerge(targetValue, value)
return targetValue
} else {
return value ? { ...value } : value
}
} else {
return value ?? targetValue ?? undefined
}
}
}
省略(对象,arrayOfProps)
返回已删除列出的道具的对象
const record = { a: 1, b: 2, c: 3}
console.log(omit(record, ['b', 'c'])) //=> {a: 1}
执行
export function omit(target, props) {
return Object.fromEntries(
Object.entries(target).filter(([key]) => !props.includes(key))
)
}
设置(对象,属性路径,值)
设置对象上的值,{}
如有必要则创建空对象。
const record = { a: 1, d: { e: 1 } }
set(record, "a.d.e", 2) //=> { a: 1, d: { e: 2 } }
set(record, "a.b.c", 4) //=> { a: 1, b: { c: 4 }, d: { e: 2 } }
执行
export function set(object, path, value) {
const parts = path.split(".")
for (let i = 0, l = parts.length - 1; i < l; i++) {
const part = parts[i]
object = object[part] = object[part] || {}
}
object[parts[parts.length - 1]] = value
}
sortBy(数组,函数(项目)=>值|属性名称)
按子元素对数组进行排序。
const array = [{ id: "a7", name: "mike", type: "user" }, { id: "z1", name: "bob", type: "user" }, { id: "a3", name: "beth", type: "admin"} ]
console.log(sortBy(array, 'name'))
/*=>
[
{ id: "a3", name: "beth", type: "admin"}
{ id: "z1", name: "bob", type: "user" },
{ id: "a7", name: "mike", type: "user" },
]
*/
执行
import { pick } from "./pick"
export function sortBy(array, fn) {
fn = pick(fn)
return array.sort((a, b) => {
const va = fn(a)
const vb = fn(b)
if (va < vb) return -1
if (va > vb) return 1
return 0
})
}
uniq(数组)
从现有数组创建一个唯一数组
const array = ['a', 'b', 'c', 'b', 'b', 'a']
console.log(uniq(array)) //=> ['a', 'b', 'c']
执行
export function uniq(target) {
return Array.from(new Set(target))
}
uniqBy(数组,函数(项目)=>值|属性名称)
使用数组中对象的属性创建一个唯一数组。
const array = [{a: 1, b: 2}, {a: 4, b: 2}, {a: 5, b: 3}]
console.log(uniqBy(array, 'b')) //=> [{a: 1, b: 2}, {a: 5, b: 3}]
执行
import { pick } from "./pick"
export function uniqBy(target, fn) {
fn = pick(fn)
const dedupe = new Set()
return target.filter((v) => {
const k = fn(v)
if (dedupe.has(k)) return false
dedupe.add(k)
return true
})
}
部分实施debounce
lodashdebounce
非常强大——对我来说太强大了,也太庞大了。我只需要一个可以去抖动的函数,一个最大等待时间,以及刷新或取消任何待处理调用的功能。(所以缺少的是尾随和前沿等等,以及其他我不使用的选项)。
const debounced = debounce(()=>save(), 1000, {maxWait: 10000})
...
debounced() // Call the debounced function after 1s (max 10s)
debounced.flush() // call any pending
debounced.cancel() // cancel any pending calls
执行
export function debounce(fn, wait = 0, { maxWait = Infinity } = {}) {
let timer = 0
let startTime = 0
let running = false
let pendingParams
let result = function (...params) {
pendingParams = params
if (running && Date.now() - startTime > maxWait) {
execute()
} else {
if (!running) {
startTime = Date.now()
}
running = true
}
clearTimeout(timer)
timer = setTimeout(execute, Math.min(maxWait - startTime, wait))
function execute() {
running = false
fn(...params)
}
}
result.flush = function () {
if (running) {
running = false
clearTimeout(timer)
fn(...pendingParams)
}
}
result.cancel = function () {
running = false
clearTimeout(timer)
}
return result
}
结论
如果只使用这些函数,可以省去对 lodash 的依赖。在我们的应用中,我们确实使用了其他 lodash 函数,但它们都使用了惰性导入(例如template
)——因此,我们的应用加载速度显著提升。
您可以随意在自己的项目中使用任何代码。
文章来源:https://dev.to/miketalbot/14-functions-so-you-can-dump-lodash-and-reduce-your-bundle-size-3gg9