@wy
2019-07-30T00:09:58.000000Z
字数 4600
阅读 558
博客
之前翻译过一篇文章,《我喜欢的5种编程技巧》,里面的一个技巧是借鉴一个网站的代码片段,好奇的小手点下链接后,发现是一个有 47000
多star的仓库,30-seconds-of-code。
仓库的名字就让我很惊讶,30秒就能理解一段代码,有点不可思议。看了每个方法实现的代码都不长,很简短,最复杂的也不过是4、5行代码,的确没有标题党的嫌疑,满满的干活。
处理的类型还很丰富,有Array、Browser、Date、Function、Math、Node、Object、String、Type、Utility。要了解更多,戳这里。
这就是一个工具函数库呀。
此时让我想到了 lodash.js
和 underscore.js
,用过这两个函数式编程的库,对提供的方法肯定不陌生。它们主要是以函数作为主要载体的编程方式,用函数去拆解、抽象的表达式,每个函数封装特定的功能,作用域局限在函数内部,形成闭包,不对外界产生副作用。
相信也有很多人阅读过它们的源码,每个函数很简短,考虑到兼容性,基本都用原生的方式实现,不会调用一些规范中最新推出的方法。如果能够精读它们,对自己的编程能力会有更高的提升,能够掌握很多的技巧。有时你可能只是想快速的了解一个方法大致的实现原理,但要去看源码的话,还是会有一些门槛。
而这个30秒就能理解的代码片段,摒弃了许多不必要的代码,只实现了最核心的部分,不像 lodash.js
和 underscore.js
那样,考虑参数边界值问题,例如,参数的类型是否符合预期等。默认情况下,都是按照传递符合预期的参数处理。
如果要把这个库用在自己的项目中,没有对参数的判断是非常糟糕的一件事。但不想引 lodash.js
或 underscore.js
这样大的库文件,想自己实现一个简洁的方法快速使用,那么这个库会对你实现自己的方法具有指导意义。不考虑兼容问题的话,你可以直接拷贝这个库的代码片段,加上对参数边界的处理就直接能用。
再有一点,这个库之所以简短,能够让你在30秒就理解,主要是能用规范提供的最新方法就用,不再很费劲的自己实现一套,全都调用了原生提供的方法,包括 ES6
的方法。每个方法都是独立的,可独立测试,独立运行,和其他的方法互不牵扯,极大的降低了阅读时找各种方法会被打断思路的烦恼。
当然,如果你想阅读 lodash.js
或 underscore.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
将数组分块成指定大小的较小数组。
使用 array.from()
创建一个新的数组,该数组的长度就是将要生成的块(chunk)的个数。
使用 array.prototype.slice()
将新数组的每个元素映射为一个长度为 size
的块(chunk)。
如果原始数组不能被平均分割,那么最后的块(chunk)将包含剩余的元素。
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
chunk([1, 2, 3, 4, 5], 2); // [[1,2],[3,4],[5]]
深度平铺一个数组。
使用递归。
使用 array. prototype.concat()
和空数组( []
),结合 spread 操作符('...')将数组平铺。
递归平铺数组中的每个元素。
const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]
将数组展平到指定的深度。
使用递归,为每个深度级别 depth
递减 1
。 使用 Array.prototype.reduce()
和 Array.prototype.concat()
来合并元素或数组。 基本情况下,depth
等于 1
停止递归。 省略第二个参数,depth
只能平铺到 1
层(单层平铺) 的深度。
const flatten = (arr, depth = 1) =>
arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);
flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]
初始化一个给定行数和列数,以及值的二维数组。
使用 array.prototype.map()
生成 h
行,其中每一行都是长度为 w
的新数组。如果没有提供值 val
,则默认为 null
。
const initialize2DArray = (w, h, val = null) =>
Array.from({ length: h }).map(() => Array.from({ length: w }).fill(val));
initialize2DArray(2, 2, 0); // [[0,0], [0,0]]
返回两个数组的并集,相同的元素只出现一次。
基于 a
和 b
创建一个 Set
对象,返回转换后的数组。
const union = (a, b) => Array.from(new Set([...a, ...b]));
union([1, 2, 3], [4, 3, 2]); // [1,2,3,4]
使用一个函数将数组的值映射到对象,在键值对中,原始值作为键,映射值作为值。
使用一个匿名的内部函数作用域来声明一个 undefined 的内存空间,使用闭包来存储返回值。 使用一个新的 Array
来存储带有函数映射的数组和一个逗号运算符来返回第二个步骤,而不需要从一个上下文移动到另一个上下文(由于闭包和操作顺序)。
const mapObject = (arr, fn) =>
(a => (
(a = [arr, arr.map(fn)]), a[0].reduce((acc, val, ind) => ((acc[val] = a[1][ind]), acc), {})
))();
const squareIt = arr => mapObject(arr, a => a * a);
squareIt([1, 2, 3]); // { 1: 1, 2: 4, 3: 9 }
将指定数量的元素移动到数组的末尾。
两次使用 Array.prototype.slice()
来获取指定索引之后的元素和指定索引之前的元素。
使用展开操作符(...
)将两个数组合成一个数组。
如果 offset
为负数,元素将从结束移动到开始位置。
const offset = (arr, offset) => [...arr.slice(offset), ...arr.slice(0, offset)];
offset([1, 2, 3, 4, 5], 2); // [3, 4, 5, 1, 2]
offset([1, 2, 3, 4, 5], -2); // [4, 5, 1, 2, 3]
改变原始数组,过滤掉指定的值。
使用 Array.prototype.filter()
和 array.prototype.include()
过滤指定的值。
使用 Array.prototype.length = 0
通过将数组的长度重置为0来清空数组,并使用 array.prototype.push()
把提取的值重新填充数组。
(对于不改变原始数组的代码片段,请参阅 without
)
const pull = (arr, ...args) => {
let argState = Array.isArray(args[0]) ? args[0] : args;
let pulled = arr.filter((v, i) => !argState.includes(v));
arr.length = 0;
pulled.forEach(v => arr.push(v));
};
let myArray = ['a', 'b', 'c', 'a', 'b', 'c'];
pull(myArray, 'a', 'c'); // myArray = [ 'b', 'b' ]
根据条件过滤一个对象数组,同时过滤掉未指定的键。
使用 array.prototype.filter()
根据断言函数 fn
对数组进行过滤,返回条件为真值(truthy)的对象。
在经过过滤后的数组上,使用 array.prototype.map()
和 array.prototype.reduce()
过滤掉在 keys
参数中未提供的键。
const reducedFilter = (data, keys, fn) =>
data.filter(fn).map(el =>
keys.reduce((acc, key) => {
acc[key] = el[key];
return acc;
}, {})
);
const data = [
{
id: 1,
name: 'john',
age: 24
},
{
id: 2,
name: 'mike',
age: 50
}
];
reducedFilter(data, ['id', 'name'], item => item.age > 24); // [{ id: 2, name: 'mike'}]
将两个数组的每个元素两两进行组合,组合出所有的可能对存在数组中,返回一个存在所有可能性对的数组。
使用 Array.prototype.reduce()
, Array.prototype.map()
和 Array.prototype.concat()
从两个数组的元素中生成所有可能的对,并将它们保存在一个数组中。
const xProd = (a, b) => a.reduce((acc, x) => acc.concat(b.map(y => [x, y])), []);
xProd([1, 2], ['a', 'b']); // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
看完后,是不是觉得实现特简洁简单?
看完后,给个 star 哦!仓库地址:https://github.com/WYseven/30-seconds-of-code。