[关闭]
@wy 2019-07-30T00:09:58.000000Z 字数 4600 阅读 558

30秒就能理解的 Javascript 代码片段 --- Array

博客


之前翻译过一篇文章,《我喜欢的5种编程技巧》,里面的一个技巧是借鉴一个网站的代码片段,好奇的小手点下链接后,发现是一个有 47000 多star的仓库,30-seconds-of-code

仓库的名字就让我很惊讶,30秒就能理解一段代码,有点不可思议。看了每个方法实现的代码都不长,很简短,最复杂的也不过是4、5行代码,的确没有标题党的嫌疑,满满的干活。

处理的类型还很丰富,有Array、Browser、Date、Function、Math、Node、Object、String、Type、Utility。要了解更多,戳这里

这就是一个工具函数库呀。

此时让我想到了 lodash.jsunderscore.js,用过这两个函数式编程的库,对提供的方法肯定不陌生。它们主要是以函数作为主要载体的编程方式,用函数去拆解、抽象的表达式,每个函数封装特定的功能,作用域局限在函数内部,形成闭包,不对外界产生副作用。

相信也有很多人阅读过它们的源码,每个函数很简短,考虑到兼容性,基本都用原生的方式实现,不会调用一些规范中最新推出的方法。如果能够精读它们,对自己的编程能力会有更高的提升,能够掌握很多的技巧。有时你可能只是想快速的了解一个方法大致的实现原理,但要去看源码的话,还是会有一些门槛。

而这个30秒就能理解的代码片段,摒弃了许多不必要的代码,只实现了最核心的部分,不像 lodash.jsunderscore.js 那样,考虑参数边界值问题,例如,参数的类型是否符合预期等。默认情况下,都是按照传递符合预期的参数处理。

如果要把这个库用在自己的项目中,没有对参数的判断是非常糟糕的一件事。但不想引 lodash.jsunderscore.js 这样大的库文件,想自己实现一个简洁的方法快速使用,那么这个库会对你实现自己的方法具有指导意义。不考虑兼容问题的话,你可以直接拷贝这个库的代码片段,加上对参数边界的处理就直接能用。

再有一点,这个库之所以简短,能够让你在30秒就理解,主要是能用规范提供的最新方法就用,不再很费劲的自己实现一套,全都调用了原生提供的方法,包括 ES6 的方法。每个方法都是独立的,可独立测试,独立运行,和其他的方法互不牵扯,极大的降低了阅读时找各种方法会被打断思路的烦恼。

当然,如果你想阅读 lodash.jsunderscore.js 的源码,先阅读这个库会很有帮助,它排除了许多不必要的干扰让你很清晰很明确的get到核心的实现方式。

之前也有人翻译过,但都很早,大约2年前了,作者最新最近更新的方法都没有。而且仓库中不止提供 javascript 的方法,还有 css react 的简短代码,还有其他语言的。基于自己学习的目的,同时也让更多人掌握这些方法的实现方式,决定翻译成中文。

仓库地址:https://github.com/WYseven/30-seconds-of-code。感兴趣的,可以给个 star 哦!

目前已完成数组方法的翻译,点击查看 https://wyseven.github.io/30-seconds-of-code/。其他方法也在持续的更新中。。。

我不建议你闷着头一口气读完,然后头昏眼花的不知道自己看了什么。而是建议你在闲暇之余,工作空隙,断断续续,一天看几个就够了,权当做工作累了休憩时当做消遣来看。

因为篇幅的原因,以下随机选择了10个方法,你看简单不简单。

chunk
deepFlatten
flatten
initialize2DArray
union
mapObject
pull
reducedFilter
xProd

chunk

将数组分块成指定大小的较小数组。

使用 array.from() 创建一个新的数组,该数组的长度就是将要生成的块(chunk)的个数。
使用 array.prototype.slice() 将新数组的每个元素映射为一个长度为 size 的块(chunk)。
如果原始数组不能被平均分割,那么最后的块(chunk)将包含剩余的元素。

  1. const chunk = (arr, size) =>
  2. Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
  3. arr.slice(i * size, i * size + size)
  4. );
  1. chunk([1, 2, 3, 4, 5], 2); // [[1,2],[3,4],[5]]

deepFlatten

深度平铺一个数组。

使用递归。
使用 array. prototype.concat() 和空数组( [] ),结合 spread 操作符('...')将数组平铺。
递归平铺数组中的每个元素。

  1. const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
  1. deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]

flatten

将数组展平到指定的深度。

使用递归,为每个深度级别 depth 递减 1。 使用 Array.prototype.reduce()Array.prototype.concat() 来合并元素或数组。 基本情况下,depth 等于 1 停止递归。 省略第二个参数,depth 只能平铺到 1 层(单层平铺) 的深度。

  1. const flatten = (arr, depth = 1) =>
  2. arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);
  1. flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
  2. flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]

initialize2DArray

初始化一个给定行数和列数,以及值的二维数组。

使用 array.prototype.map() 生成 h 行,其中每一行都是长度为 w 的新数组。如果没有提供值 val,则默认为 null

  1. const initialize2DArray = (w, h, val = null) =>
  2. Array.from({ length: h }).map(() => Array.from({ length: w }).fill(val));
  1. initialize2DArray(2, 2, 0); // [[0,0], [0,0]]

union

返回两个数组的并集,相同的元素只出现一次。

基于 ab 创建一个 Set 对象,返回转换后的数组。

  1. const union = (a, b) => Array.from(new Set([...a, ...b]));
  1. union([1, 2, 3], [4, 3, 2]); // [1,2,3,4]

mapObject

使用一个函数将数组的值映射到对象,在键值对中,原始值作为键,映射值作为值。

使用一个匿名的内部函数作用域来声明一个 undefined 的内存空间,使用闭包来存储返回值。 使用一个新的 Array 来存储带有函数映射的数组和一个逗号运算符来返回第二个步骤,而不需要从一个上下文移动到另一个上下文(由于闭包和操作顺序)。

  1. const mapObject = (arr, fn) =>
  2. (a => (
  3. (a = [arr, arr.map(fn)]), a[0].reduce((acc, val, ind) => ((acc[val] = a[1][ind]), acc), {})
  4. ))();
  1. const squareIt = arr => mapObject(arr, a => a * a);
  2. squareIt([1, 2, 3]); // { 1: 1, 2: 4, 3: 9 }

offset

将指定数量的元素移动到数组的末尾。

两次使用 Array.prototype.slice() 来获取指定索引之后的元素和指定索引之前的元素。
使用展开操作符(...)将两个数组合成一个数组。
如果 offset 为负数,元素将从结束移动到开始位置。

  1. const offset = (arr, offset) => [...arr.slice(offset), ...arr.slice(0, offset)];
  1. offset([1, 2, 3, 4, 5], 2); // [3, 4, 5, 1, 2]
  2. offset([1, 2, 3, 4, 5], -2); // [4, 5, 1, 2, 3]

pull

改变原始数组,过滤掉指定的值。

使用 Array.prototype.filter()array.prototype.include() 过滤指定的值。
使用 Array.prototype.length = 0 通过将数组的长度重置为0来清空数组,并使用 array.prototype.push() 把提取的值重新填充数组。

(对于不改变原始数组的代码片段,请参阅 without)

  1. const pull = (arr, ...args) => {
  2. let argState = Array.isArray(args[0]) ? args[0] : args;
  3. let pulled = arr.filter((v, i) => !argState.includes(v));
  4. arr.length = 0;
  5. pulled.forEach(v => arr.push(v));
  6. };
  1. let myArray = ['a', 'b', 'c', 'a', 'b', 'c'];
  2. pull(myArray, 'a', 'c'); // myArray = [ 'b', 'b' ]

reducedFilter

根据条件过滤一个对象数组,同时过滤掉未指定的键。

使用 array.prototype.filter() 根据断言函数 fn 对数组进行过滤,返回条件为真值(truthy)的对象。
在经过过滤后的数组上,使用 array.prototype.map()array.prototype.reduce() 过滤掉在 keys 参数中未提供的键。

  1. const reducedFilter = (data, keys, fn) =>
  2. data.filter(fn).map(el =>
  3. keys.reduce((acc, key) => {
  4. acc[key] = el[key];
  5. return acc;
  6. }, {})
  7. );
  1. const data = [
  2. {
  3. id: 1,
  4. name: 'john',
  5. age: 24
  6. },
  7. {
  8. id: 2,
  9. name: 'mike',
  10. age: 50
  11. }
  12. ];
  13. reducedFilter(data, ['id', 'name'], item => item.age > 24); // [{ id: 2, name: 'mike'}]

xProd

将两个数组的每个元素两两进行组合,组合出所有的可能对存在数组中,返回一个存在所有可能性对的数组。

使用 Array.prototype.reduce(), Array.prototype.map()Array.prototype.concat() 从两个数组的元素中生成所有可能的对,并将它们保存在一个数组中。

  1. const xProd = (a, b) => a.reduce((acc, x) => acc.concat(b.map(y => [x, y])), []);
  1. xProd([1, 2], ['a', 'b']); // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]

看完后,是不是觉得实现特简洁简单?

看完后,给个 star 哦!仓库地址:https://github.com/WYseven/30-seconds-of-code

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注