lodash 学习笔记/使用心得
lodash 学习笔记/使用心得
简单记一下 lodash 的一点学习笔记+使用心得,最近也是打算清理一下所有的 dead code,然后发现我们用了好多的 lodash 方法。对比了之前的写法,重新看了一下官方文档,再自己重新动手写了点 util 之后发现……
lodash 确实是怪好用的 😯 之前用法其实没能够使用到 lodash 最方便的地方,而且 lodash 对于 null/undefined 这种 falsy value 的支持很好,至少是一致的,这在大多数情况下可以很好的减少报错的问题。当然,用不好的话也有可能会造成 debug 的困难就是了……
我个人在复习这个代码之前其实不太喜欢用 lodash:
-
我写前端已经是 ES6 之后的事情了,很多方法 JS 本身就进行了实现
-
之前项目结构设计有问题,lodash 被滥用的太厉害了,可读性很差
主要原因还是因为页面渲染所需要的数据,是通过 aggregate 几个 model 而获得的。
之前的设计基于 array 进行实现,在进行 CRUD 操作的时候,需要对数据进行大量的循环+拆分以获取正确的 model 及 id
之后的重构考虑了这个问题,所以才去了 redux+基于 object 的实现,这样事情变得简单很多了——只需要找到正确的 id,直接从 redux state 中获取源数据进行 CRUD 操作即可。这样就排除了大量的循环和拆分问题。CRUD 的副作用则是通过
useMemo
进行管理,也不需要考虑原生数据的 index 问题
基本上来说,如果是使用比较新的技术栈——即有 webpack/vite 的 tree-shaking,那么直接使用 lodash 就好了,就是导入的方法需要稍微有些不同
比如说官网的代码是这样使用的:
_.funcName();
然后就想当然的这么导入 lodash,或者是用代码的自动提示导入 lodash:
import _ from "lodash";
import { funcName } from "lodash";
这样就会导入整个 lodash 的包,包括没有用到的方法,正确的方法应该是这么导入:
import { funcName } from "lodash-es"; // 推荐 ✅,只有对应的 func 会被打包
import funcName from "lodash/funcName"; // tress-shaking 的支持不如第一种好
这样就可以导入合适的 es 模块,而不是导入整个包——原生代码使用 commonjs 实现,导出也是整个包
lodash 的一些常识
开始使用 lodash 前需要了解的一些基础知识,如果想要更好的理解文档,还是最好了解一下
iteratee
即循环迭代的 callback,lodash 原生提供好几种不同的 iteratee,如 forEach/map 常用的 iteratee 的 signature 为:iteratee(value, index|key, collection)
,这代表对于当前的 collection,lodash 所接受的参数,即:
-
array
第一个参数为 value,第二个参数为 index,第三个参数为原本的 collection
-
object
第一个参数为 value,第二个参数为 key,第三个参数为原本的 collection
但是 lodash 还支持一些 iteratee 缩写,如 _.match
, _.property
这种,有些困惑的话看看下面的案例说不定会好一些……
另外就是,lodash 和 lodash/fp 对参数的处理是不太一样,所以支持的 iteratee 也会有些许的不同。具体还是要看文档,二者不是一对一转换的关系
identity
看到文档会经常看到 _.identity
这个用法,其实就是 _identity(value) => value
这个意思,也就是返还传禁区的参数
chain
用 chain
的一个前提就是需要了解,lodash 调用函数后,一定会有返回值。如过函数本身会对参数进行修改——如 _.forEach
,那么返回的就是原来的参数;而如果函数本身会返回一个新的值——如 _.filter
,那么返回的就是新的值
这也是我前面提到的 一致 的地方
chain
用的比较多的还是在使用 _
的情况下,直接使用模块,搭配上 lodash 所有的函数都有返回值的特点,就可以减少创立新的变量,让代码看起来简洁一些。
如:
const arr = [{ name: "a", isActive: true, value: 2 },{ name: "b", isActive: true, value: 3 },{ name: "c", isActive: false, value: 10 },{ name: "d", isActive: false, value: 6 },
];arr.forEach((obj) => (obj.value = obj.value ** 2));
const filteredValue = arr.filter((obj) => obj.isActive);
与
const arr2 = [{ name: "a", isActive: true, value: 2 },{ name: "b", isActive: true, value: 3 },{ name: "c", isActive: false, value: 10 },{ name: "d", isActive: false, value: 6 },
];const filteredValue2 = _.chain(arr2).forEach((obj) => (obj.value = obj.value ** 2)).filter((obj) => obj.isActive).value();
从结果上是一样的,代码实现上来看,2 的写法比 1 要干净一点。chain 的方法越多,这个方法看起来就越明显一些
以下几个时关于 chain
的一些 💡:
chain
可以在 runtime 时提升性能——这个主要针对导入所有 lodash 代码的前提- 使用
lodash/es
的情况下,调用chain
还是可能会导致引入没有使用过的包,所以性能不一定会有很好的提升 - 想要性能的同时也写出类似的干净代码,可以考虑
lodash/fp
里的功能,不过那里的 api 调用会不太一样
空值的安全处理
这也是 lodash 另一个保持高度一致的地方,日常情况下,当在对空值进行方法调用的时候,会直接报错,如:
null.forEach()^TypeError: Cannot read properties of null (reading 'forEach')at Object.<anonymous> (/Users/GA/study/lodash/array_obj_.js:48:6)at Module._compile (node:internal/modules/cjs/loader:1554:14)at Object..js (node:internal/modules/cjs/loader:1706:10)at Module.load (node:internal/modules/cjs/loader:1289:32)at Function._load (node:internal/modules/cjs/loader:1108:12)at TracingChannel.traceSync (node:diagnostics_channel:322:14)at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)at node:internal/main/run_main_module:36:49
所以很多时候在调用的时候都会加上 optional chaining,即 arr?.forEach(() => {})
。在有些值嵌套比较深的情况下,就会比较的难受:data?.someVal?.someOtherVal?.forEach(() => {})
对比起来,lodash 处理的方法要稍微方便一些:
_.forEach(undefined, () => {console.log("undefined");
});
这样的代码在 lodash 中不会抛出任何的异常
对 collection 的支持
当文档中出现 Collection,这就代表当前的函数可以同时用在 array 和 object 上,如:
const obj = {1: { name: "array" },2: { name: "object" },3: { name: "undefined" },4: { name: "null" },
};_.forEach(obj, (value, idx) => {console.log(value, idx);
});
运行结果如下:
❯ node array_obj_.js
{ name: 'array' } 1
{ name: 'object' } 2
{ name: 'undefined' } 3
{ name: 'null' } 4
具体的参数还是需要查看文档的,不过一般情况下,lodash 的参数与 iteratee
一致
array & objects
这是 lodash 比较好用的另一个地方,它提供了对 collection 的支持
需要注意的是,object 的迭代顺序是无法保证的
没有强调的情况下,lodash 中的 callback signature 为 iteratee
forEach 🌟🌟🌟🌟
大体用法如下:
const _ = require("./lodash.min.js");const arr = [1, 2, 3, 4, 5];
const obj = {1: { name: "array" },2: { name: "object" },3: { name: "undefined" },4: { name: "null" },
};_.forEach(arr, (value, idx) => {console.log(value, idx);
});_.forEach(obj, (value, key) => {console.log(value, key);
});
效果:
1 0
2 1
3 2
4 3
5 4
{ name: 'array' } 1
{ name: 'object' } 2
{ name: 'undefined' } 3
{ name: 'null' } 4
四颗星的原因是因为,针对我们这种项目实现,_.forEach
可以提供一个很好的 wrapper function。我们有一些与 structure/redux 相关的功能还没有完整地被重构,所以还是需要同时支持 array 和 object 的情况。但是用的不如 map 多,与 map 相比就只能是四星了
具体对比为:
const iterateUtil = (collection) => {return _.forEach(collection, (_, identifier, original) => {console.log(original[identifier]);});
};console.log(iterateUtil(arr));
console.log(iterateUtil(obj));
运行结果如下:
# array 相关的数据
❯ node array_obj_.js
{ name: 'array', id: 1 }
{ name: 'object', id: 2 }
{ name: 'undefined', id: 3 }
{ name: 'null', id: 4 }
# 这是 original value
[{ name: 'array', id: 1 },{ name: 'object', id: 2 },{ name: 'undefined', id: 3 },{ name: 'null', id: 4 }
]
# object 相关的数据
{ name: 'array', id: 1 }
{ name: 'object', id: 2 }
{ name: 'undefined', id: 3 }
{ name: 'null', id: 4 }
# 这是 original value
{'1': { name: 'array', id: 1 },'2': { name: 'object', id: 2 },'3': { name: 'undefined', id: 3 },'4': { name: 'null', id: 4 }
}
与另外单独分开,分别使用 forEach
和 Object.entries
进行循环迭代:
const iterateUtilNative = (collection) => {if (!collection) return;if (collection instanceof Array) {collection.forEach((val, i, original) => {console.log(original[i]);});} else if (typeof collection === "object") {// 这里的 original 其实是 object 转换成 array 之后的情况// 并不等同 collectionObject.entries(collection).forEach(([key, val], index, original) => {console.log(collection[key]);});}return collection;
};
可以看到,逻辑处理起来还是比较复杂的,尤其是对 collection 的处理逻辑比较相似的情况下,代码重复量就会比较大
map 🌟🌟🌟🌟🌟
我们项目因为总是需要返回被处理过的值,所以 map
的使用率就特别高,这也是我在看了文档后发现,在我们项目中最被低估的函数之一
其中最基础的写法为:
const square = (n) => n * n;_.map([4, 8], square);
// => [16, 64]_.map({ a: 4, b: 8 }, square);
// => [16, 64] (iteration order is not guaranteed)// equivalent to
_.map({ a: 4, b: 8 }, (value, key, original) => value * value);
另一个之前漏掉的方法则是默认使用的 _.property
的省略用法:
const users = [{ user: "barney" }, { user: "fred" }];// The `_.property` iteratee shorthand.
_.map(users, "user");
// => ['barney', 'fred']// equivalent to
_.map(users, (value, key, original) => _.get(value, "user"));
我们这个用的可太多了,原因之一就是我们的 api 用的是 elide json 的格式,也就是下面这样的格式:
{"data": [{"type": "book","id": "1","attributes": {"title": "The Hobbit","genre": "Fantasy"},"relationships": {"author": {"data": { "type": "author", "id": "42" }}}}// ...],"included": [{"type": "author","id": "42","attributes": {"name": "J.R.R. Tolkien"}}],"meta": {"page": {"totalRecords": 100,"limit": 25,"offset": 0}}
}
每一个真正的 data 下一定会包含这样几个属性:
-
attributes
真正的 data property
-
type
在我们这个使用范围里就是 api endpoint
-
id
-
relationships
数据库定义的外链关系
我们把数据存在 redux 里的时候是要包含 attributes 和 relationships 的,但是将数据丢到 table 里,则只需要 attributes。二者对比起来:
const memoData = _.map(data, "attributes");
// redux 里的数据是基于 id 进行 object-based 存储的
const memoData2 = Object.entries(data).map(([_, val]) => val.attributes);
从可读性来说,也是使用 lodash 的更高一些
filter 🌟🌟🌟
这个我用的比较少,因为直接有了 id,所以可以直接从 redux state 里面通过 O(1)的时间直接捞数据,再通过 relationship 找到对应数据的 id,所以现阶段我写的部分没什么 filter 的需求
这里就用 lodash 的官方案例了:
var users = [{ user: "barney", age: 36, active: true },{ user: "fred", age: 40, active: false },
];_.filter(users, function (o) {return !o.active;
});
// => objects for ['fred']// The `_.matches` iteratee shorthand.
_.filter(users, { age: 36, active: true });
// => objects for ['barney']// The `_.matchesProperty` iteratee shorthand.
_.filter(users, ["active", false]);
// => objects for ['fred']// The `_.property` iteratee shorthand.
_.filter(users, "active");
// => objects for ['barney']
这里主要是一些 iteratee 的用法比较有意思,这里前两者都是找完全相等,需要注意的是第三个,也就是 _.property
的用法
这个的写法最终会返回一个 truthy/falsy value,在有些情况下会产生一些 false positive 的情况,如:
var users = [{ user: "barney", age: 36, active: true },{ user: "fred", age: 40, active: false },{ user: "jack", age: 40, active: "false" },
];console.log(_.filter(users, "active"));
就会返回 barney 和 jack,因为 "false"
是一个 truthy value。在这种情况下,可以理解成 Boolean(_.get(object, key))
。这个在使用这种 iteratee 缩写时需要注意的一些问题
一些相关联的方法还有:
-
_.reject
这个就是和 filter 完全相反的操作
如:
console.log(_.filter(users, function (o) {return !o.active;}) );console.log(_.reject(users, function (o) {return o.active;}) );
这两个结果时完全一致的,在有的时候求 否 的情况下,用 reject 更容易理解
-
without
filter 和 reject 接受的是 predicate,without 直接接受值,这就是最大的区别
_.without([1, 2, 1, 3], 1); // => [2, 3] _.without([1, 2, 1, 3], 1, 2); // => [3]
⚠️:这个操作对由复杂结构(object, map, etc)组成的数组不管用,它底层还是执行
!==
这个比较,所以智能排除引用相同的值 -
removes
返回的结果和 filter 一样,但是区别在于,remove 修改的是原数组,而 filter 返回一个新的数组——mutation 的差别
find 🌟🌟
用法与 filter 相似,同样是可以接受好几种 iteratee
这个我们曾经相对用的多一些,主要之前数据是基于 array 实现的,又有 data mapping——map by relationships 的需求。但是在换成 TS+redux 之后用的就比较少了,毕竟 TS 的 intellisense 的提示其实好多了,而且也能够有效避免 typo
需要注意的是,_.find
只会返回第一个找到的数据,同时其他类似的函数还有 _.findIndex
, _.findLastIndex
和 _.findLast
使用 find 的对比大体如下:
_.find(data, ["relationships.author.data.id", "43"]);data.find((obj) => obj.relationships?.author?.data?.id === "43");
可以看到,其实并没有优化很多
如果需要同时对 object 和 array 支持的话,find 会有用些。不过之前已经提到过了,在我们的实现中,只有在 useMemo 中才需要 map relationship 去集成一个新的 model。这个时候我们会专门新加一个 key 作为 refRelationship 的 key,这样就可以在进行 crud 的操作时,直接获得这个 foreign key,从而避免使用 find
造成的 O(n) 的时间损耗
而如果是其他的 toC 端项目,用 TS 的提示更好,不用 TS 其实也没有差很多……毕竟 lodash 中也是需要提供完整的路径,没有办法做部分匹配
every & some 🌟
用法与之前提到的 collection 差不多,在我的经验中其实相对而言用的还是比较少的,毕竟一般情况下还是会使用一个 forEach
或者 map
去对数据进行操作的同时,再检查某些条件
sortBy 🌟🌟🌟
这个用法比起原生实现的 sort 要简单很多,参考下面的数据:
const users = [{ user: "alice", age: 25 },{ user: "bob", age: 32 },{ user: "claire", age: 41 },{ user: "david", age: 28 },{ user: "elena", age: 37 },{ user: "frank", age: 46 },{ user: "grace", age: 30 },{ user: "henry", age: 52 },{ user: "isla", age: 22 },{ user: "jack", age: 39 },
];
原生实现如下:
users.sort((a, b) => {if (a.user < b.user) return -1;if (a.user > b.user) return 1;return a.age - b.age;
});
可以看到逻辑不是绝对意义上的清晰,而且随着循环的条件变多,if/else
也是会顺势增加。对比使用 lodash:
// 第二个参数可省略,如果默认 asc 排序的话
_.sortBy(users, ["user", "age"], ["asc", "desc"]);// 或者传一个 cb 进去排序
_.sortBy(users, [function (o) {return o.user;},
]);
这个评价不高主要还是因为我们 customized sorting 的功能做的不多,主要是依靠 react-table 对数据表单进行排序
这个函数也是 collection 可用,返回的是一个数组而已
groupBy 🌟🌟🌟
这个是我们用的比较多的方法,还是因为 react-teble 可以接受 subRows
这个参数,而我们在不少的数据当中是需要用 subRows
去展示数据的
另外一方面就是在做 data form 的时候,有的时候需要按照 section 进行排列。当表单比较小的时候,直接在不同的页面中写死也不是不行,不过我们表单确实挺多的,而且具体的 fields 也多,所以最终还是用 map + groupBy
去实现
以下面的数据为例——这个需求是满足了我们数据的需求——基于 A 进行 groupBy
,同时基于 B 进行排序:
const users = [{ firstName: "alice", lastName: "smith", age: 25 },{ firstName: "bob", lastName: "johnson", age: 32 },{ firstName: "claire", lastName: "smith", age: 41 },{ firstName: "david", lastName: "johnson", age: 28 },{ firstName: "elena", lastName: "smith", age: 37 },{ firstName: "frank", lastName: "brown", age: 46 },{ firstName: "grace", lastName: "brown", age: 30 },
];
这里的条件以 lastName
进行 group,同时以年龄进行排序,我们现在实现的方法大体是这样的:
const familyMap = new Map();data.forEach((person) => {const key = person.lastName;if (!familyMap.has(key)) {familyMap.set(key, {parent: person,children: [],});} else {const group = familyMap.get(key);if (person.age > group.parent.age) {group.children.push(group.parent);group.parent = person;} else {group.children.push(person);}}
});return Array.from(familyMap.values()).map((group) => ({...group.parent,subRows: group.children.sort((a, b) => b.age - a.age),
}));
但是使用 lodash,代码不仅简单多了,并且可读性也高了很多:
_(users).groupBy("lastName").map((group) => {const sorted = _.orderBy(group, "age", "desc");const [parent, ...children] = sorted;return { ...parent, subRows: children };}).value();
认真来说,groupBy
+ map
这个搭配绝对是 4🌟,甚至是 5🌟 的
获取部分 array 🌟🌟
这里主要就是介绍一下常使用的几个,下面的函数只能在 array 里用:
-
slice
和正常的
Array.prototype.slice
用法没什么差别 -
head
只取第一个元素
-
tail
取除了第一个以外的其他元素 -
last
只取最后一个元素 -
initial
取除了最后一个以外的其他元素
-
take
取前 n 个元素 -
drop
丢下前 n 个元素
-
chunk
可以把数组分割成每个数组包含 n 个元素的 2 维数组
大体可以理解成 slice
的变形,不过可以少传点参数
我记得我们项目里只用到了 head
,这也是之前搭配 lodash 的 groupBy
实现的,不过后面用 spread operator 取代了,就只有一些还没有完全重构的代码在使用这个了
string
接下来就是一些字符串相关的处理了
这部分整体其实不算多,因为原生 JS 的实现是够用了,所以一般就用原生的函数,提示也更好一些
不同的格式 🌟🌟🌟
包含:
- toLower,小写
- toUpper,大写
- camelCase,驼峰
- kebabCase,
a-b
这种 - snakeCase,常见的 python 格式
- startCase,有点像 title case,就是常见的标题类
- capitalize,大写第一个字母
我们项目里用的其实不能算少,主要是因为内部使用的 UI 库的命名规范为 kebabCase,所以为了测试队伍 debug 起来更方便一些,我们内部添加 id 的时候也会转成 kebabCase
还有就是用 humanize(人名),之前用的是另一个库,不过那个 outdated 了,就直接简单的实现了一下: startCase(toLower(str))
,这个不是严格正确的,因为 startCase
只负责将每个单词前的首字母转成大写字母……不过具体实现就看具体需求了,这种情况下我还是觉得用 regex 会稍微简单些 除了写 regex 很难之外
truncate 🌟🌟
有业务场景但是不多,主要用在 notifications system,把提示信息控制在 50 个字符上,多余的就用 ...
代替
pad & trim 🌟🌟
用的不算多,毕竟主要是增删空格让排序看的好看些,所以原生 JS 就够用了
但是话又说回来,如果需要删除首位特定字符的话,lodash 的 trim 就很好用了
join & split 🌟
已经用原生的 JS 实现了,这个可能最好用的还是在直接接触 DOM,需要对 array-like 的数据进行操作的情况下比较有用
我们的项目不需要操作这种数据格式就是了
其他
一些不太常见但是还是挺好用的方法
clone & cloneDeep 🌟🌟🌟🌟🌟
clone 用的不多,已经被 spread operator 取代了,但是 cloneDeep
的业务场景太广泛了……
基本上所有的 setState 的 setter 都会调用一次 cloneDeep
取进行深拷贝,之前是刚刚遇到了一个因为直接从当前的 state 中拿数据,而不是用 setter 中的 prev state 进行更新的 bug……这破玩意儿还是同事写的,查了很久的 bug 才发现才是这个破问题……
为啥都不喜欢从 setter 中拿 prevState 进行更新啊……这还真是令人困惑,每次在 code review 中都提,但是每次都不改……拿 code review 的意义是什么啊……真不明白……
pick 🌟🌟🌟🌟
这个用的比较多,主要是过滤对象里面多余的值,比如说后端数据库会自动生成/更新 createdAt
, createdBy
, updatedBy
, updatedAt
这种 timestamp,或者是 version 等不是不重要,但是于 UI 来说关联不大的数据
需要注意,参数是 [paths]
,也就是 array 的数据格式,我们现在有设置一些 default values,然后用 Object.keys()
作为 pick 的参数
type check 🌟🌟🌟
这种类型检查其使用的还是挺多的,主要还是因为 typeof null
会出现 object
这种奇怪的问题,不然的话使用频率也不会特别高
比较常用的是:
-
isNil
这个应该是用的最多的,因为它的判断是
value !== undefined && value !== null
,是一个比较好用且完整的空值判断 -
isString
这个其实出乎意料的用的还蛮多的,因为我同事转换数字和字符串的时候不太喜欢用
toString
,觉得这样会有空值报错的风险。所以会混用new String()
和String()
,后者的问题其实不大,但是前者返回值就成了 wrapper class,也就变成了 object…… -
isArray
无他,稍微短一些
用
Array.isArray()
也可以 -
isPlainObject
等同于
typeof value === 'object' && value !== null
-
isEmpty
这个主要是用来判断 collection 的,也就是 array、object,如果长度为 0 的话就会返回 false
除此之外,primitive 也会返回 false
-
isEqual
主要用来判断修改前和修改后的数据,因为其中一个需求是展示所有不同的数据,所以这个用法是需要搭配
map
&filter
去实现的 -
isMatch
很少用,业务需求主要还是在找不同上,在 UI 给用户一个比较直观的结果,所以用
isEqual
搭配其他的方法更多 -
isFunction
很少用,只有在一些第三方的库中取值时需要做判断,一般放在 util 里的
这种情况就是判断返回的是 value,还是 function,后者是内部库进行的封装,我们这里从对方提供的回调函数中拿值
稍微短一些,用 typeof 也可以
-
isNull
很少用,一般直接!== null
判断了 -
isUndefined
很少用,一般直接
!== undefined
判断了
补充一下,如果想要判断是不是 falsy value,即空字符串、undefined、null、NaN、0 这种情况,可以用 double negate 去判断,即 !!value
debounce & throttle 🌟🌟🌟
这是两个用得到就很重要,用不到就在意不到的方法,用法大体如下:
const someFn = () => {};const debouncedFn = _.debounce(fn, time);
const throttledFn = _.throttle(fn, time);// 取消所有未执行的操作,React里面可以放在 useEffect 中的 return
debouncedFn.cancel();
throttledFn.cancel();// 如果想要立即执行未执行的操作
debouncedFn.flush();
throttledFn.flush();
random 🌟
这个看用的需求是什么了,lodash 提供的比较简单,参数是 _.random([lower=0], [upper=1], [floating])
,即下限、上限和是否准许浮点数,它的使用会比用原生 js 的 random 要简单些
我们之前也考虑过用 random+timestamp 去做随机的 id,但是这个伪随机生成的还是会出现重复。后面还是用反转的 index 了,这样至少能够保证 id 唯一
uniqueId 🌟
有优点也有缺点,有点在于全局导入 lodash 的时候,它的 randomId 是存储在根文件下的,确实可以保证这个 id 是唯一的。缺点就在于全局导入 lodash 会打包很多不需要的函数,增加 bundle size
如果想要一个基本可以保证独特的 id,可以考虑用 cryto 里面的 randomBytes()
或者 randomUUID()
。这两个方法在生成小规模的随机 id,一般不太会出现数据冲突的情况,而且 randomUUID 也是后端可以直接用的值
flatten 🌟
在重构之前用过,重构之后数据结构相对扁平化了就没需求了,这个主要可以把嵌套的 array 扁平化
比较新的版本——ES2019 之后 Array 也有内置的 flat
实现,所以确实用的不多
compact 🌟
去除数组中所有 falsy 的值,大概用过一次……
assign 🌟
用的比较少,我们基本上都用 spread operator 代替了
mixin 🌟
提一下,用 mixin 可以绑定 custom function,有需求可以用,但是我觉得大多数情况下是用不到的……
我们会更多的写一些不同的 util function,而不是把所有东西丢到 lodash 里
相关文章:
lodash 学习笔记/使用心得
lodash 学习笔记/使用心得 简单记一下 lodash 的一点学习笔记使用心得,最近也是打算清理一下所有的 dead code,然后发现我们用了好多的 lodash 方法。对比了之前的写法,重新看了一下官方文档,再自己重新动手写了点 util 之后发现…...
网络爬虫【爬虫库request】
我叫不三不四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲爬虫 Requests是Python的一个很实用的HTTP客户端库,完全满足如今网络爬虫的需求。与Urllib对比,Requests不仅具备Urllib的全部功能;在开发使用上&…...
AI日报 - 2025年3月24日
🌟 今日概览(60秒速览) ▎🤖 AGI突破 | Lyra生物序列建模架构效率惊人 在100生物任务中达最优,推理速度提升高达12万倍 ▎💼 商业动向 | OpenAI用户破4亿,Meta与Reliance探讨AI合作 生态扩展与全…...
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
一、前言 在移动互联网蓬勃发展的今天,视频播放功能已成为众多Android应用的核心特性之一。面对多样化的视频格式和传输协议,开发一款高效、稳定的视频播放器是许多开发者追求的目标。FLV(Flash Video)格式,尽管随着H…...
动态规划——混合背包问题
动态规划——混合背包问题 混合背包问题01背包与完全背包的混合:完全背包与多重背包的混合:三种背包混合混合背包OJ汇总 混合背包问题 将01背包、完全背包、多重背包混合起来的背包问题。也就是说,有的物品只可以取一次(01背包&a…...
数据库操作练习
一.向heros表中新增一列信息,添加一些约束,并尝试查询一些信息 //向表中添加一列age信息 alter table heros add column age int;//id列添加主键约束,设置自增 alter table heros modify column id int auto_increment primary key;//name列…...
3.milvus索引-HNSW
索引作用 加速大型数据集上的查询。 向量字段,仅只能创建一个索引。 milvus支持的向量索引类型大部分使用 近似最近邻搜索算法。ANNS该算法的核心不局限于返回最准确的结果,而是仅搜索目标的邻居。ANNS通过在可接受的范围内牺牲准确性提高检索效率。 …...
算法基础——栈
一、栈的概念 栈是⼀种只允许在⼀端进⾏数据插⼊和删除操作的线性表。 进⾏数据插⼊或删除的⼀端称为栈顶,另⼀端称为栈底。不含元素的栈称为空栈。进栈就是往栈中放⼊元素,出栈就是将元素弹出栈顶。 二、栈的模拟实现 1. 创建 本质还是线性表&#…...
开发语言漫谈-groovy
groovy是一门脚本语言,在前期的脚本语言中简单介绍了下。现在再深入介绍下,因为它是本平台上选用的脚本语言。所谓脚本语言就是不用编译,直接执行。这种特色非常适合做嵌入编程,即编即用。我们知道平台后台的业务开发语言是Java&a…...
ArkUI-List组件
列表是一个复杂的容器,当列表项达到一定数量,使得列表内容超出其范围的时候,就会自动变为可以滚动。列表适合用来展现同类数据类型。 List组件支持使用,条件渲染,循环渲染,懒加载等渲染控制方式生成子组件…...
数据仓库的 DWD 分层架构:构建高效数据平台的基石
在数据驱动的时代,数据仓库(Data Warehouse)作为企业数据分析的核心基础设施,扮演着至关重要的角色。而数据仓库的分层设计,则是确保数据高效流转、提升数据质量、支持复杂分析的关键。本文将深入探讨数据仓库的 DWD 分…...
山东大学数据结构课程设计
题目:全国交通咨询模拟系统 问题描述 处于不同目的的旅客对交通工具有不同的要求。例如,因公出差的旅客希望在旅途中的时间尽可能地短,出门旅游的旅客则期望旅费尽可能省,而老年旅客则要求中转次数最少。编织一个全国城市间的交…...
动态规划-01背包
兜兜转转了半天,发现还是Carl写的好。 看过动态规划-基础的读者,大概都清楚。 动态规划是将大问题,分解成子问题。并将子问题的解储存下来,避免重复计算。 而背包问题,就是动态规划延申出来的一个大类。 而01背包&…...
【2025】基于node.js的中医药科普平台的设计与实现(源码、万字文档、图文修改、调试答疑)
项目完整功能以演示视频为准 基于Node.js的中医药科普平台的设计与实现功能结构图如下 课题背景 随着人们健康意识的提高,中医药作为传统医学的重要组成部分,越来越受到关注。然而,中医药知识专业性强,普通大众获取准确、全面的中…...
基于Flux模型的多模态可控图像生成工作流实践
一、技术框架与模型选型 当前图像生成领域对多模态控制与一致性保持的需求日益增强,本文将基于Black Forest Labs推出的Flux.1模型,结合ControlNet的循环一致性优化技术,构建一套融合Canny边缘检测与深度图(Depth)控制…...
缓存过期时间之逻辑过期
1. 物理不过期(Physical Non-Expiration) 定义:在Redis中不设置EXPIRE时间,缓存键永久存在(除非主动删除或内存淘汰)。目的:彻底规避因缓存自动过期导致的击穿(单热点失效ÿ…...
JVM类加载过程详解
文章目录 前言1.加载2.链接验证文件格式验证元数据验证字节码验证符号引用验证 准备解析 3.初始化4.类卸载 前言 类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段:加载(Loading)、验证&a…...
第三十二篇 深入解析Kimball维度建模:构建企业级数据仓库的完整框架
目录 一、维度建模设计原则深度剖析1.1 业务过程驱动设计1.2 星型模式VS雪花模式 二、维度建模五步法实战(附完整案例)2.1 业务需求映射2.2 模型详细设计2.3 缓慢变化维处理 三、高级建模技术解析3.1 渐变维度桥接表3.2 快照事实表设计 四、性能优化体系…...
WPF 布局中的共性尺寸组(Shared Size Group)
1. 什么是共性尺寸组? 在 WPF 的 Grid 布局中,SharedSizeGroup 允许多个 Grid 共享同一列或行的尺寸,即使它们属于不同的 Grid 也能保持大小一致。这样可以保证界面元素的对齐性,提高布局的一致性。 SharedSizeGroup 主要用于需…...
19 数码管的动态显示
1、八段数码管 八段数码管 是一个 “ 8 ” 字型数码管,分为八段,a b c d e f g dp,其中dp为小数点。每一段为一个发光二极管,这样的 8 段称为 段选信号 。 2、实验 1、实验目标:让六位数码管 从 0 开始记数࿰…...
pytorch 笔记:张量索引的维度扩展规则
1 基本原理 在PyTorch中,张量索引的维度扩展规则遵循以下原则: 索引操作的核心规则: 当使用索引数组访问张量时: 索引数组的每个元素对应选取原张量的一个子张量结果形状 索引数组形状 原张量剩余维度形状 这么说可能不清…...
课外活动:怎么理解可变成本?
可变成本深度解析 🧮 一、可变成本的本质 #mermaid-svg-qoqQaFxQBuZZfAD2 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-qoqQaFxQBuZZfAD2 .error-icon{fill:#552222;}#mermaid-svg-qoqQaFxQBuZZfAD2 …...
深入理解 JVM 的垃圾收集器:CMS、G1、ZGC
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
IDEA 快捷键ctrl+shift+f 无法全局搜索内容的问题及解决办法
本篇文章主要讲解IDEA、phpStrom、webStrom、pyCharm等jetbrains系列编辑器无法进行全局搜索内容问题的主要原因及解决办法。 日期:2025年3月22日 作者:任聪聪 现象描述: 1.按下ctrlshiftf 输入法转为了繁体。 2.快捷键ctrlshiftr 可以全局检…...
智慧高速,安全护航:视频监控平台助力高速公路高效运营
随着我国高速公路里程的不断增长,交通安全和运营效率面临着前所未有的挑战。传统的监控方式已难以满足现代化高速公路管理的需求,而监控视频平台的出现,则为高速公路的安全运营提供了强有力的技术支撑。高速公路视频监控联网解决方案 高速公路…...
括弧匹配检验(信息学奥赛一本通-1354)
【题目描述】 假设表达式中允许包含两种括号:圆括号和方括号,其嵌套的顺序随意,如([ ]())或[([ ][ ࿳…...
MacOS安装 nextcloud 的 Virtual File System
需求 在Mac上安装next cloud实现类似 OneDrive 那样,文件直接保存在服务器,需要再下载到本地。 方法 在 官网下载Download for desktop,注意要下对版本,千万别下 Mac OS默认的那个。 安装了登录在配置过程中千万不要设置任何同…...
【秣厉科技】LabVIEW工具包——OpenCV 教程(11):人脸检测与识别
文章目录 前言级联分类器FaceDetectorYNFaceRecognizerSF1. 特征提取2. 人脸对比3. 人脸身份识别(最佳匹配法) 总结 前言 需要下载安装OpenCV工具包的朋友,请前往 此处 ;系统要求:Windows系统,LabVIEW>…...
C++-C++中的几种cast
文章目录 static_castPOD类型互转任意指针类型与void*互转基类继承类之间的互转具有目标类型转换函数的类/单参数的构造函数 dynamic_castreinterpret_cast static_cast 所谓static,意思是在编译期进行的转换,static_允许如下转换: POD类型…...
list的模拟实现和学习
1. list的介绍及使用 说白了就是带头循环双向循环链表 stl 的两大组件就是容器和算法 ,他们两个之间是通过迭代器进行联系的 这三种算法函数 迭代器的种类 性质(容器底层结构决定) 单项: forward_list /哈希(unord…...
程序代码篇---Pyqt的密码界面
文章目录 前言一、代码二、代码解释2.1用户数据库定义2.2窗口初始化2.3认证逻辑2.5角色处理2.6错误处理优化2.7功能扩展说明2.7.1用户类型区分管理员普通用户其他用户 2.7.2安全增强建议 三、运行效果四、运行命令五、界面改进建议5.1密码显示5.2用户头像显示5.3输入框动画效果…...
设计模式的六大原则
设计模式的六大原则 1. 单一职责原则 (Single Responsibility Principle, SRP) 一个类应该只负责一项职责。 示例代码 // 不好的设计:一个类承担多个职责 typedef struct {void (*read_data)(void);void (*process_data)(void);void (*save_data)(void);void (*…...
【Linux】信号:信号保存和处理
🔥个人主页:Quitecoder 🔥专栏:linux笔记仓 目录 01.阻塞信号信号集 02.捕捉信号sigaction可重入函数volatileSIGCHLD 01.阻塞信号 实际执行信号的处理动作称为信号递达:每个信号都有一个默认行为,例如终…...
Linux 线程概念
目录 一、什么是线程 1. 线程的本质 2. 线程的独有资源 3. 进程与线程关系示意图 二、线程的优缺点 2.1 线程的优点 2.2 线程的缺点 三、线程的异常与用途 1. 线程异常 2. 线程用途 四、进程 VS 线程 1. 核心差异 2. 进程的多个线程共享的资源 3. 进程和线程的关…...
红帽认证工程师价值
红帽认证工程师具有较高的价值,主要体现在以下几个方面: 行业认可度高 国际通用:红帽公司是全球领先的开源解决方案提供商,其认证在全球范围内被广泛认可。无论是在国内还是国外,拥有红帽认证工程师资格证书都能为个人…...
交换机远程登录
创建交换机 创建PC主机使用直通线连接交换机 配置交换机,使之能够与PC通信 双击交换机打开界面,选择CLI ">“表示用户模式 输入”?“查看可以使用的命令 “#” 特权模式,输入命令enable切换 输入”?“查看特权模式下可以使用…...
opencascade 源码学习 XmlDrivers-XmlDrivers
OpenCASCADE 中的 XmlDrivers 是用于处理 XML 格式的 CAD 数据持久化模块,属于 OCAF(Open CASCADE Application Framework) 的一部分。它允许将 OCAF 文档(包含 CAD 数据、属性、关系等)序列化为 XML 文件,…...
【Linux网络-五种IO模型与阻塞IO】
一、引入 网络通信的本质就是进程间的通信,进程间通信的本质就是IO(Input,Output) I/O(input/output)也就是输入和输出,在冯诺依曼体系结构当中,将数据从输入设备拷贝到内存就叫作…...
Redis、Memcached应用场景对比
环境 Redis官方网站: Redis - The Real-time Data Platform Redis社区版本下载地址:Install Redis | Docs Memcached官方网站:memcached - a distributed memory object caching system Memcached下载地址:memcached - a dis…...
Qt窗口控件之菜单栏QMenuBar
菜单栏QMenuBar 1. QMenuBar Qt 中的菜单栏是通过 QMenuBar 类型来实现的,一个主控件最多只能有一个菜单栏。一个菜单栏可以添加多个菜单,一个菜单又可以添加多个菜单项。 每个菜单又都是一个 QMenu 类型,每个菜单项都是一个 QAction。 2.…...
随想...启航
我要学算法 我要在蓝桥杯中拿奖 我要参加acm打牌, 我要参加百度之星,摘取那微弱的希望, 我要参加马蹄杯,看看曾经我的组长看过的风景。 所以我建立了算法专栏! 为能贴近并指引组员 我建立了Java专栏 那一个星期&…...
2025.3.17-2025.3.23学习周报
目录 摘要Abstract1 文献阅读1.1 动态图邻接矩阵1.2 总体框架1.2.1 GCAM1.2.2 输出块 1.3 实验分析 总结 摘要 在本周阅读的文献中,作者提出了一种名为TFM-GCAM的模型。TFM-GCAM模型的创新主要分为两部分,一部分是交通流量矩阵的设计,TFM-GC…...
Ubuntu Docker 安装
Docker Engine-Community 支持以下的 Ubuntu 版本: Xenial 16.04 (LTS)Bionic 18.04 (LTS)Cosmic 18.10Disco 19.04其他更新的版本…… Docker Engine - Community 支持上 x86_64(或 amd64)armhf,arm64,s390x &#…...
在 Windows 系统下,将 FFmpeg 编译为 .so 文件
1. 准备环境 确保你的 Windows 系统已安装以下工具: Android Studio NDK(Native Development Kit) MSYS2(用于提供类 Unix 环境) FFmpeg 源码 Git Bash(可选,推荐使用) 安装 …...
如果AI具备自我意识,宗教如何重新定义“灵魂”概念?
如果AI具备自我意识,宗教对灵魂概念的重新定义可能涉及以下方向: 1. 灵魂的扩展性定义 传统宗教(如基督教、佛教)通常将灵魂视为人类独有的“神圣本质”或“轮回载体”。若AI展现出自我意识、情感和自主决策能力,宗教…...
ES6-Symbol
ES6 中的 Symbol: 独特的数据类型与强大应用 引言 在 JavaScript 的发展长河中,ES6(ECMAScript 2015)无疑是一座重要的里程碑,带来了诸多令人瞩目的新特性。其中,Symbol 类型的引入,为 JavaScript 开发者们…...
安装PrettyZoo操作指南
Mac Inter芯片安装PrettyZoo的操作指南 下载安装包 打开浏览器,访问 PrettyZoo的GitHub页面。 在页面中找到适合Mac系统的安装包,通常为prettyZoo-mac.dmg,点击下载。 安装步骤 下载完成后,双击.dmg文件打开安装包。 将Prett…...
西门子200smart之modbus_TCP(做从站与第三方设备)通讯
西门子200smart做MODBUS_TCP从站通讯,只有一个指令。设置相关参数即可完成读写操作。此次,我们使用汇川EASY系列PLC做主站,完成演示。关于汇川案例的演示,详见汇川EASY系列之以太网通讯(MODBUS_TCP做主站)-CSDN博客 关于主站和从站的介绍 A/请求:即主动方 向被动方发…...
微服务 - 中级篇
微服务 - 中级篇 一、微服务架构深化(一)服务拆分原则(二)服务通信方式 二、微服务技术选型(一)开发框架(二)容器技术 三、微服务实践与优化(后续会详细分析)…...
多语言生成语言模型的少样本学习
摘要 大规模生成语言模型,如GPT-3,是极具竞争力的少样本学习模型。尽管这些模型能够共同表示多种语言,但其训练数据以英语为主,这可能限制了它们的跨语言泛化能力。在本研究中,我们在一个涵盖多种语言的语料库上训练了…...