Lodash的介绍
Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库,Lodash 通过降低 array、number、objects、string 等等的使用难度从而让 JavaScript 变得更简单。
简单来说,Lodash 是一套工具库,内部封装了很多字符串、数组、对象等常见数据类型的处理函数,大大提高工作效率。
你可能会说 Lodash 里面的好多方法都能自己实现,没必要再引入额外的工具库来增加项目的体积,但是并不能保证实现得比 Lodash 好。我们自己在项目中封装的工具函数,很多时候功能并不是完善的,尤其是一些边界值的处理,而且需要考虑到各种使用场景,Lodash 这些方面就做得很好。
而且 Lodash 支持多种模块化方案,配合 tree-shaking 技术或者使用单独的函数模块,几乎不会导致冗余代码。
js
// 方式1:引入整个lodash对象
import _ from "lodash";
// 方式2:按名称引入特定的函数
import { cloneDeep } from "lodash";
// 上述2种方式都会引入整个lodash库,体积大,而下面2种方式都能实现按需引入,减小体积
// 1.只引入cloneDeep函数
import cloneDeep from "lodash/cloneDeep";
// 2.使用lodash-es
import { cloneDeep } from "lodash-es";
基于上面的这些优点,Lodash 已经变得非常流行,目前被很多项目应用,越来越受欢迎,Github 上目前已经有 59.1k 的 Star
既然 Lodash 这么优秀,那么我们很应该去学习一下它内部函数的实现,从而提升自己的编码水平,因此这篇文章与大家一同手写实现 get 函数,手写 get 函数也是前端一道高频的面试题。
get函数的使用
首先我们得知道 get 函数是如何使用的?有哪些参数?每个参数的具体作用?
通过文档可以知道,Lodash.get 的使用:_.get(object, path, [defaultValue])
,接收三个参数
object
(Object) : 要检索的对象path
(Array|string) : 要获取属性的路径[defaultValue]
: 如果解析值是undefined
,这值会被返回
来看一个例子:
js
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
_.get(object, ['a', '0', 'b', 'c']);
// => 3
_.get(object, 'a.b.c', 'default');
// => 'default'
通过这个例子 get 函数的作用已经很明显了:根据 path 去依次取对象 object 中的成员的值,比如 _.get(object, 'a[0].b.c')
,首先取 object 中的 a 属性,然后取 0 属性,接着取 b 属性,最后取 c 属性,如果对象中没有对应要取的属性,返回 undefined,这时若指定第三个参数 default,那么当前面取值解析为 undefined 时会返回 default。
手写实现简易版
下面开始手写实现一个简易版的 get
函数
1. 基本功能的实现
先考虑 path
为数组的情况,并且先不管第三个参数;path 为数组类型比较好处理,直接遍历数组拿到所有属性即可:
js
function _get(object, path) {
let obj = object;
for (const key of path) {
obj = obj[key];
}
return obj;
};
测试代码:
js
const object = { a: [{ b: { c: 3 } }] };
console.log('-------', _get(object, ['a', '0', 'b', 'c']));
console.log('-------', _get(object, ['a', '0']));
console.log('-------', _get(object, ['a', '0', 'b']));
console.log('-------', _get(object, ['a']));
结果如下,已经基本符合 get 函数的功能:
2. 路径类型的处理
接着考虑 path
为字符串的情况,我这里的思路是,只需加一个类型转化的逻辑,将字符串转为对应的数组即可,然后其他的逻辑与上面保持一致。
利用正则表达式,匹配字符串中的非[ ] .
字符,使用 match 方法得到一个数组,举个例子:a[0].b.c => ['a', '0', 'b', 'c']
Lodash 源码里对字符串的匹配更加复杂,需要考虑到各种字符,我这里的正则表达式比较简单,怎么简单怎么来~
js
function _get(object, path) {
let obj = object;
if (typeof path === 'string') {
const reg = /[^\[\].]+/g;
path = path.match(reg);
}
for (const key of path) {
obj = obj[key];
}
return obj;
}
测试代码:
js
const object = { a: [{ b: { c: 3 } }] };
console.log('-------', _get(object, 'a[0].b.c'));
console.log('-------', _get(object, 'a[0]'));
console.log('-------', _get(object, 'a[0].b'));
console.log('-------', _get(object, 'a'));
结果如下,与上面 path 为数组的结果是一样的,说明代码没问题:
3. 处理访问不到的属性
现在的 get 方法还存在一个问题,那就是如果我访问对象 object 中不存在的属性:
js
console.log(_get(object, ['w', '0', 'b', 'c']));
是会报错的,但是 Lodash 的 get 函数不会报错,而是会返回 undefined
,所以需要做些处理:
结果打印 undefined,不会报错。
4. 第三个参数
最后考虑下第三个参数,就是当我前面取值解析为 undefined 时会返回 default,这里很简单,在刚刚的判断语句里返回默认值就好了:
这里还有个小问题,就是如果最后一个属性取不到,目前会返回 undefined
:
js
console.log(_get(object, ['a', '0', 'b', 'w'], '我是默认值')); // undefined
但是正常情况下应该返回第三个参数才对的,因此需要对 get 函数的返回值做处理:
5. 最终版代码
js
function _get(object, path, defaultValue) {
let obj = object;
if (typeof path === 'string') {
const reg = /[^\[\].]+/g;
path = path.match(reg);
}
for (const key of path) {
if (!obj) {
return defaultValue;
}
obj = obj[key];
}
return obj === undefined ? defaultValue : obj;
}
最终版的代码如上,代码不多,看起来也很好理解,关键在于一些细节以及边界值的处理,希望能帮到对大家有所帮助!
写在最后
这就是一个 get 函数简易版本的实现了,Lodash 的源码里其实比这还要复杂,我们实现成上面的样子已经足够了,至少原理方面已经理解透彻,面试时手写也没啥问题,已经达到了手写 get 函数的目的!
如果有讲得不好的地方,大家可以批评指正,一起交流进步🥳🥳
期待大家的点赞 + 关注,多多支持,你的支持是我前进的动力!🤗🤗