如果觉得没有面试题,那么lodash每一个方法就可以当作一个题目,可以看着效果反过来实现,以不同的方法实现、多种方法实现,巩固基础。除了某些一瞬间就可以实现的函数,下面抽取部分函数作为试炼。时代在进步,下文所有的解法都采用es2015+

本文实现方法都是看效果倒推实现方法,并进行一些拓展和思考,和源码无关。lodash这个库的文档更像一个题库,给出了题目让我们刷题的

能收获什么

1、修炼代码基本功,了解常见的套路

2、了解到一些操作的英文命名和规范

3、积累经验,面对复杂逻辑问题可以迅速解决

4、也许可以查到自己的js基础知识的漏洞

⚠️注意:

  • 三星难度以上的会具体拓展和讲解

  • 文中使用的基本都是数组原生api以及es6+函数式编程,代码简洁且过程清晰

  • 如果说性能当然是命令式好,实现起来稍微麻烦一些而且比较枯燥无味

  • 时代在进步,人生苦短,我选择语法糖和api。面临大数据的性能瓶颈,才是考虑命令式编程的时候

chunk

  • 描述: _.chunk(array, [size=0]),将数组拆分成多个 size 长度的块,并组成一个新数组。如果数组无法被分割成全部等长的块,那么最后剩余的元素将组成一个块。

  • 难度系数:★★

  • 建议最长用时:6min

// example
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]

参考代码:

// 常规方法function chunk(arr, size = 0) {let current = 0;let temp = [];return arr.reduce((acc, cur, i) => {if (current++ < size) {temp.push(cur);}if (temp.length === size || i === arr.length - 1) {acc.push(temp);current = 0;temp = [];}return acc;}, []);}// 抽象派function chunk(arr, size = 0) {let current = 0;let temp = [];return arr.reduce((acc, cur, i) =>temp.push(...(current++ < size ? [cur] : [])) === size ||i === arr.length - 1? [...acc, temp, ...((current = 0), (temp = []), [])]: acc,[]);}

zip

zip&unzip

  • _.zip([arrays])创建一个打包所有元素后的数组。第一个元素包含所有提供数组的第一个元素,第二个包含所有提供数组的第二个元素,以此类推。

  • 参数[arrays] (...Array),表示要处理的数组队列

  • 返回值 (Array)是一个打包后的数组
    _.unzip(array)类似 _.zip,接收一个打包后的数组并且还原为打包前的状态。

  • 难度系数:★

  • 建议最长用时:2min * 2 = 4min

zip&unzip的例子

var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
// => [['fred', 30, true], ['barney', 40, false]]_.unzip(zipped);
// => [['fred', 'barney'], [30, 40], [true, false]]

参考代码

function zip(target, ...arrs) {return target.map((item, index) => [item, ...arrs.map(arr => arr[index])])
}function unzip(arrs) {return arrs.reduce((acc, arr) => {arr.forEach((item, index) => {acc[index] = acc[index] || [];acc[index].push(item)})return acc}, [])
}

zipWith & unzipWith

  • _.zipWith类似 _.zip, 它另外接受一个 iteratee 决定如何重组值。iteratee 会调用每一组元素,最后返回一个打包后的数组

  • _.unzipWith(array, [iteratee=_.identity])另外接受一个 iteratee 来决定如何重组解包后的数组。iteratee 会传入4个参数:(accumulator, value, index, group)。每组的第一个元素作为初始化的值,返回一个解包后的数组

  • 难度系数:★★

  • 建议最长用时:6min

// example
_.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {return a + b + c;
});
// => [111, 222]// unzipWith
var zipped = _.zip([1, 2], [10, 20], [100, 200]);
// => [[1, 10, 100], [2, 20, 200]]_.unzipWith(zipped, _.add);
// => [3, 30, 300]

参考代码:

// zipWith稍微修改一下就可以实现
function zipWith(target, ...arrs) {const iteratee = arrs.pop()return target.map((item, index) => [item, ...arrs.map(arr => arr[index])]).map(group => iteratee(...group))
}function unzipWith(arrs, iteratee) {
// 使用唯一标记,避免`!result`的假值误判const FIRST_FLAG = Symbol()return arrs.reduce((acc, arr) => {arr.forEach((item, index) => {acc[index] = acc[index] || []acc[index].push(item)})return acc}, []).map(group => group.reduce((result, cur, index, all) =>result === FIRST_FLAG ? cur : iteratee(result, cur, index, all)), FIRST_FLAG)
}

zipObject & zipObjectDeep

  • _.zipObject([props=[]], [values=[]]),接受属性key的数组和values的数组,返回每一对k-v形成的对象

  • 难度系数:★

  • 建议最长用时:2min

//example
_.zipObject(['a', 'b'], [1, 2]);
// => { 'a': 1, 'b': 2 }
function zipObject(keys, values) {return keys.reduce((obj, key, index) => {obj[key] = values[index]return obj}, {})
}
  • _.zipObjectDeep([props=[]], [values=[]]),类似 _.zipObject,它还支持属性路径。

  • 难度系数:★★★★

  • 建议最长用时:12min

// example
_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }

参考代码:

// 从 'a'或者'[1]'这种抠出真正的key:a、1
function getRealKey(currentKey) {return /\[(\d+)\]/.test(currentKey) ? RegExp.$1 : currentKey
}function zipObjectDeep(keys, values) {return keys.reduce((obj, key, index) => {const path = key.split(/\.|\B(?=\[\d+\])|\b(?=\[\d+\])/)// 辅助变量temp,利用引用类型的特性遍历和赋值整个对象let temp = obj// 预留一个空位,最后一下赋值while(path.length > 1) {const currentKey = path.shift()const realKey = getRealKey(currentKey)// 如果下一个是[1]这种,那么就是数组,不然就是一个对象// 如果你想给下一层的属性赋值,那么就要提前准备好它上一层的结构temp[realKey] = temp[realKey] || (/\[(\d+)\]/.test(path[0]) ? [] : {})temp = temp[realKey]}// 最后一下就是赋值了const lastKey = getRealKey(path.shift())temp[lastKey] = values[index]return obj}, {})
}

关于正则key.split(/\.|\B(?=\[\d+\])|\b(?=\[\d+\])/)的分析:

  • split是可以传入正则的哦,对匹配到的内容进行分割。除了普通的.,还要考虑类似[0]这种,这种需要匹配到边界才可以完美分割

  • 分成3部分,.、单词边界+[数字]、非单词边界+[数字]

  • .匹配到的split一下就是 'a.b.c' => ['a', 'b', 'c']

  • 单词边界+[数字] 'a[1]' => ['a', '[1]']

  • 非单词边界+[数字] '[0][1]' => ['[0]', '[1]']

  • ?=是正向0宽断言,也就是说/a(?=xx)/匹配前面是xx的字符a,且xx不纳入捕获组中

groupBy

  • _.groupBy(collection, [iteratee=_.identity])key 是经 iteratee 处理的结果, value 是产生 key 的元素数组。iteratee 会传入1个参数:(value)。

  • 参数: collection (Array|Object)是需要遍历的集合。[iteratee=_.identity] (Function|Object|string)是一个函数,这个函数会处理每一个元素(和其他By系列的方法都是一样的,传入函数和With一样的效果,传入字符串或者数组会先调用_.property)

  • 返回一个组成汇总的对象

  • 难度系数:★★(如果不知道property方法实现,再加多两星难度)

  • 建议最长用时:6min

// example
_.groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }// 使用了 `_.property` 的回调结果
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }

参考代码:

function property(path) {return function (o) {const temp = Array.isArray(path) ? path : path.split(".");let res = o;while (temp.length && res) {res = res[temp.shift()];}return res;};
}function groupBy(...arrs) {let iteratee = arrs.pop();iteratee = typeof iteratee === 'function' ? iteratee : property(iteratee);return arrs.reduce((acc, arr) => {arr.forEach(item => {const key = iteratee(item);(acc[key] || (acc[key] = [])).push(item)})return acc}, {})
}

invokeMap

  • _.invokeMap(collection, path, [args])调用 path 的方法处理集合中的每一个元素,返回处理的数组。如果方法名是个函数,集合中的每个元素都会被调用到。

  • 参数: collection (Array|Object)是需要遍历的集合,path (Array|Function|string)是要调用的方法名 或者 这个函数会处理每一个元素。[args] (...*)给方法传入的参数

  • 返回数组结果

  • 难度系数:★

  • 建议最长用时:3min

// example
_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
// => [[1, 5, 7], [1, 2, 3]]_.invokeMap([123, 456], String.prototype.split, '');
// => [['1', '2', '3'], ['4', '5', '6']]

参考代码:

function invokeMap(arr, fn, ...args) {return arr.map(item => {
// 面对这种传入函数手动调用的,都记得call/apply一下return (typeof fn === 'function' ? fn : arr[fn]).apply(item, args)})
}

lodash的数组和collection的方法就此告一段落了,其他方法基本是一瞬间就可以写出来或者没有什么坑点。后面是function系列,to be continue

往期回顾:

内功修炼之lodash——By、With系列

内功修炼之lodash——chunk、zip、groupBy、invokeMap方法相关推荐

  1. 《C语言点滴》一1.5 内功修炼

    本节书摘来自异步社区<C语言点滴>一书中的第1章,第1.5节,作者 赵岩,更多章节内容可以访问云栖社区"异步社区"公众号查看 1.5 内功修炼 C语言点滴 1.5.1 ...

  2. 专访刘伟:软件开发人员的内功修炼之道

    摘要:数学修养对软件开发之路起着什么作用?码农如何修炼自己的内功并成长为优秀的软件开发员?带着相关思考,社区之星第10期采访了中南大学副教授--刘伟.他对数学修养.设计模式.软件架构和重构方面的独特见 ...

  3. linux 物理内存用完了_12张图解Linux内存管理,程序员内功修炼,看过都说懂了!...

    本文已收录Github:imcoderlemon/CodeClass从小白到架构师,关于编程所有你需要掌握的内容都在这里 今天来带大家研究一下Linux内存管理.对于精通 CURD 的业务同学,内存管 ...

  4. Python可以这样学(第一季:Python内功修炼)-董付国-专题视频课程

    Python可以这样学(第一季:Python内功修炼)-18287人已学习 课程介绍         董付国系列教材<Python程序设计基础>.<Python程序设计(第2版)&g ...

  5. 软件开发人员的内功修炼之道

    摘要:数学修养对软件开发之路起着什么作用?码农如何修炼自己的内功并成长为优秀的软件开发员?带着相关思考,社区之星第10期采访了中南大学副教授--刘伟.他对数学修养.设计模式.软件架构和重构方面的独特见 ...

  6. 设计模式的艺术——软件开发人员内功修炼之道 重磅来袭

    今天(2012年12月17日),拿到了清华大学出版社给我寄的<设计模式的艺术--软件开发人员内功修炼之道>样书,这本近400页的书凝聚了过去多年我对设计模式的实战经验和教学精华,感谢清华大 ...

  7. RTOS内功修炼记(七)—— 内存管理

    内容导读: 第一篇文章讲述了任务的三大元素:任务控制块.任务栈.任务入口函数,并讲述了编写RTOS任务入口函数时三个重要的注意点. RTOS内功修炼记(一)-- 任务到底应该怎么写? 第二篇文章从任务 ...

  8. 视频教程-内功修炼之数据结构与算法-Java

    内功修炼之数据结构与算法 2018年以超过十倍的年业绩增长速度,从中高端IT技术在线教育行业中脱颖而出,成为在线教育领域一匹令人瞩目的黑马.咕泡学院以教学培养.职业规划为核心,旨在帮助学员提升技术技能 ...

  9. 开发内功修炼网络篇电子书出炉!!!

    点击上方蓝字"开发内功修炼",关注并设为星标 了解你的每一比特,用好你的每一纳秒 飞哥的开发内功修炼技术号更新文章有一年多了,以前的文章都是单独介绍一个技术点,没给大家一个整体的视 ...

  10. RTOS内功修炼记(二)—— 优先级抢占调度到底是怎么回事?

    内容导读: 本文从任务如何切换开始讲起,引出RTOS内核中的就绪列表.优先级表,一层一层为你揭开RTOS内核优先级抢占式调度方法的神秘面纱,只有对内核的深入了解,才能创造出更好的应用. 1.知识点回顾 ...

最新文章

  1. windows phone发布时其他注意事项
  2. oracle11g 多了个 client,Oracle10g Client Oracle11g DB同时安装发生OCI Error
  3. MICROSOFT VISUAL STUDIO COMMUNITY 2015软件许可条款
  4. Spring Boot 实战 —— MyBatis(注解版)使用方法
  5. nginx搭建文件服务器脚本,nginx搭建web服务器,配置端口复用
  6. 微擎支付返回商户单号_一步一步教你在SpringBoot中集成微信扫码支付
  7. .NET Core很酷,你不得不知
  8. eclipse 如何关联git_作为一名初学Java者 如何做简单的Java项目
  9. Apache Spark源码剖析
  10. WIFI远程控制实例分享,喜欢你就来!
  11. 毕业论文格式系列1 Word 图片交叉引用其题注
  12. 26岁,2020 - 观《人生七年》
  13. TypeScript 从零实现 axios 0x0
  14. SPSS(基础篇09)--拆分数据文件
  15. 配置hMailServer成功,完成邮件异步群发
  16. 常见的树以及树的应用场景
  17. 安全之路 —— 利用端口复用技术隐藏后门端口
  18. vite项目(vue-ts)搭建常用插件引入方式
  19. Java培训机构哪家好,不靠谱的有哪些
  20. clock constrain

热门文章

  1. java名片_JavaWeb练习-网上名片管理系统
  2. pythonmacd指标编写_利用python编写macd、kdj、rsi、ma等指标
  3. Maven基础概念和安装配置教程
  4. GAN——生成对抗网络详解
  5. php解决商品超卖,商品超卖问题,你用PHP如何解决?
  6. 2022杭州云栖大会定档11月3日至5日:技术产品发布+超4万平科技展
  7. 视频教程-太空大战游戏实战课程-其他
  8. 糖尿病11年的隔壁老王
  9. uk码对照表_【鞋子尺码对照表】 全面轻松了解鞋子尺码对照表 让你购物无困扰...
  10. mysqlclient和pymysql如何选择?_gevent_waiter的使用