热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过实现25个数组方法来理解及高效使用数组方法

通过实现25个数组方法来理解及高效使用数组方法(长文,建议收藏)为了保证的可读性,本文采用意译而非直译。想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!要在给定

通过实现25个数组方法来理解及高效使用数组方法(长文,建议收藏)
为了保证的可读性,本文采用意译而非直译。
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!
要在给定数组上使用方法,只需要通过[].方法名即可,这些方法都定义在 Array.prototype 对象上。在这里,咱们先不使用这些相,反,咱们将从简单的方法开始定义自己的版本,并在这些版本的基础上进行构建。
没有比把东西拆开再重新组装起来更好的学习方法了。注意,当咱们的实现自己的方法时,不要覆盖现有的方法,因为有的库需要它们,并且这样也方便比较咱们自己的方法与原始方法的差异。
所以不要这样命名咱们自定义的方法:
Array.prototype.map = function map() {
// implementation
};

最好这样命名:
function map(array) {
// implementation
}

咱们也可以通过使用class关键字并扩展Array构造函数来实现咱们的方法,如下所示:
class OwnArray extends Array {
public constructor(...args) {
super(...args);
}
public map() {
// implementation
return this;
}
}

唯一的区别是,我们不使用数组参数,而是使用this关键字。
但是,我觉得 class 方式带来不必要的混乱,所以咱们采用第一种方法。
有了这个,咱们先从实现最简单的方法 forEach 开始!
集合类
.forEach
Array.prototype.forEach 方法对数组的每个元素执行一次提供的函数,而且不会改变原数组。
[1, 2, 3, 4, 5].forEach(value => console.log(value));

实现
function forEach(array, callback) {
const { length } = array;

for (let index = 0; index const value = array[index];
callback(value, index, array)
}
}

咱们遍历数组并为每个元素执行回调。这里需要注意的一点是,该方法没有返回什么,所以默认返回undefined。
方法涟
使用数组方法的好处是可以将操作链接在一起。考虑以下代码:
function getTodosWithCategory(todos, category) {
return todos
.filter(todo => todo.category === category)
.map(todo => normalizeTodo(todo));
}

这种方式,咱们就不必将map的执行结果保存到变量中,代码会更简洁。
不幸的是,forEach没有返回原数组,这意味着咱们不能做下面的事情
// 无法工作
function getTodosWithCategory(todos, category) {
return todos
.filter(todo => todo.category === category)
.forEach((value) => console.log(value))
.map(todo => normalizeTodo(todo));
}

帮助函数 (打印信息)
接着实现一个简单的函数,它能更好地解释每个方法的功能:接受什么作为输入,返回什么,以及它是否对数组进行了修改。
function logOperation(operationName, array, callback) {
const input = [...array];
const result = callback(array);
console.log({
operation: operationName,
arrayBefore: input,
arrayAfter: array,
mutates: mutatesArray(input, array), // shallow check
result,
});
}

其中 mutatesArray 方法用来判断是否更改了原数组,如果有修改刚返回 true,否则返回 false。当然大伙有好的想法可以在评论提出呦。
function mutatesArray(firstArray, secondArray) {
if (firstArray.length !== secondArray.length) {
return true;
}
for (let index = 0; index if (firstArray[index] !== secondArray[index]) {
return true;
}
}
return false;
}

然后使用logOperation来测试咱们前面自己实现的 forEach方法。
logOperation('forEach', [1, 2, 3, 4, 5], array => forEach(array, value => console.log(value)));

打印结果:
{
operation: 'forEach',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: undefined
}

.map
map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。
实现
function map(array, callback) {
const result = [];
const { length } = array;

for (let index = 0; index const value = array[index];

result[index] = callback(value, index, array);
}
return result;
}

提供给方法的回调函数接受旧值作为参数,并返回一个新值,然后将其保存在新数组中的相同索引下,这里用变量 result 表示。
这里需要注意的是,咱们返回了一个新的数组,不修改旧的。
测试
logOperation('map', [1, 2, 3, 4, 5], array => map(array, value => value + 5));

打印结果:
{
operation: 'map',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 6, 7, 8, 9, 10 ]
}

.filter
Array.prototype.filter 过滤回调返回为false的值,每个值都保存在一个新的数组中,然后返回。
[1, 2, 3, 4, 5].filter(number => number >= 3);
// -> [3, 4, 5]

实现
function push(array, ...values) {
const { length: arrayLength } = array;
const { length: valuesLength } = values;
for (let index = 0; index array[arrayLength + index] = values[index];
}
return array.length;
}
--------------------------------------------------
function filter(array, callback) {
const result = [];
const { length } = array;
for (let index = 0; index const value = array[index];
if (callback(value, index, array)) {
push(result, value);
}
}
return result;
}

获取每个值并检查所提供的回调函数是否返回true或false,然后将该值添加到新创建的数组中,或者适当地丢弃它。
注意,这里对result 数组使用push方法,而不是将值保存在传入数组中放置的相同索引中。这样,result就不会因为丢弃的值而有空槽。
测试
logOperation('filter', [1, 2, 3, 4, 5], array => filter(array, value => value >= 2));

运行:
{
operation: 'filter',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 2, 3, 4, 5 ]
}

.reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。reduce() 方法接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce() 的数组。
确切地说,如何计算该值是需要在回调中指定的。来看呓使用reduce的一个简单的例子:对一组数字求和:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce((sum, number) => {
return sum + number;
}, 0) // -> 55

注意这里的回调接受两个参数:sum和number。第一个参数总是前一个迭代返回的结果,第二个参数在遍历中的当前数组元素。
这里,当咱们对数组进行迭代时,sum包含到循环当前索引的所有数字的和因为每次迭代咱们都将数组的当前值添加到sum中。
实现
function reduce(array, callback, initValue) {
const { length } = array;

let acc = initValue;
let startAtIndex = 0;
if (initValue === undefined) {
acc = array[0];
startAtIndex = 0;
}
for (let index = startAtIndex; index const value = array[index];
acc = callback(acc, value, index, array)
}

return acc;
}

咱们创建了两个变量acc和startAtIndex,并用它们的默认值初始化它们,分别是参数initValue和0。
然后,检查initValue是否是undefined。如果是,则必须将数组的第一个值设置为初值,为了不重复计算初始元素,将startAtIndex设置为1。
每次迭代,reduce方法都将回调的结果保存在累加器(acc)中,然后在下一个迭代中使用。对于第一次迭代,acc被设置为initValue或array[0]。
测试
logOperation('reduce', [1, 2, 3, 4, 5], array => reduce(array, (sum, number) => sum + number, 0));

运行:
{ operation: 'reduce',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 15
}

检索类
有什么操作比搜索特定值更常见?这里有一些方法可以帮助我们。
.findIndex
findIndex帮助咱们找到数组中给定值的索引。
[1, 2, 3, 4, 5, 6, 7].findIndex(value => value === 5); // 4

findIndex方法对数组中的每个数组索引0..length-1(包括)执行一次callback函数,直到找到一个callback函数返回真实值(强制为true)的值。如果找到这样的元素,findIndex会立即返回该元素的索引。如果回调从不返回真值,或者数组的length为0,则findIndex返回-1。
实现
function findIndex(array, callback) {
const { length } = array;
for (let index = 0; index const value = array[index];
if (callback(value, index, array)) {
return index;
}
}
return -1;
}

测试
logOperation('findIndex', [1, 2, 3, 4, 5], array => findIndex(array, number => number === 3));

运行:
{
operation: 'findIndex',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 2
}

.find
find与findIndex的唯一区别在于,它返回的是实际值,而不是索引。实际工作中,咱们可以重用已经实现的findIndex。
[1, 2, 3, 4, 5, 6, 7].find(value => value === 5); // 5

实现
function find(array, callback) {
const index = findIndex(array, callback);
if (index === -1) {
return undefined;
}
return array[index];
}

测试
logOperation('find', [1, 2, 3, 4, 5], array => find(array, number => number === 3));

运行
{
operation: 'find',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 3
}

.indexOf
indexOf是获取给定值索引的另一种方法。然而,这一次,咱们将实际值作为参数而不是函数传递。同样,为了简化实现,可以使用前面实现的findIndex
[3, 2, 3].indexOf(3); // -> 0

实现
function indexOf(array, searchedValue) {
return findIndex(array, value => value === searchedValue)
}

测试
logOperation('indexOf', [1, 2, 3, 4, 5], array => indexOf(array, 3));

执行结果
{
operation: 'indexOf',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 2
}

.lastIndexOf
lastIndexOf的工作方式与indexOf相同,lastIndexOf() 方法返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。
[3, 2, 3].lastIndexOf(3); // -> 2

实现
function lastIndexOf(array, searchedValue) {
for (let index = array.length - 1; index > -1; index -= 1 ){
const value = array[index];

if (value === searchedValue) {
return index;
}
}
return -1;
}

代码基本与findIndex类似,但是没有执行回调,而是比较value和searchedValue。如果比较结果为 true,则返回索引,如果找不到值,返回-1。
测试
logOperation('lastIndexOf', [1, 2, 3, 4, 5, 3], array => lastIndexOf(array, 3));

执行结果
{
operation: 'lastIndexOf',
arrayBefore: [ 1, 2, 3, 4, 5, 3 ],
arrayAfter: [ 1, 2, 3, 4, 5, 3 ],
mutates: false,
result: 5
}

.every
every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试,它返回一个布尔值。
[1, 2, 3].every(value => Number.isInteger(value)); // -> true

咱们可以将every 方法看作一个等价于逻辑与的数组。
实现
function every(array, callback){
const { length } = array;

for (let index = 0; index const value = array[index];

if (!callback(value, index, array)) {
return false;
}
}
return true;
}

咱们为每个值执行回调。如果在任何时候返回false,则退出循环,整个方法返回false。如果循环终止而没有进入到if语句里面(说明条件都成立),则方法返回true。
测试
logOperation('every', [1, 2, 3, 4, 5], array => every(array, number => Number.isInteger(number)));

执行结果
{
operation: 'every',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}

.some
some 方法与 every 刚好相反,即只要其中一个为true 就会返回true。与every 方法类似,咱们可以将some 方法看作一个等价于逻辑或数组。
[1, 2, 3, 4, 5].some(number => number === 5); // -> true

实现
function some(array, callback) {
const { length } = array;
for (let index = 0; index const value = array[index];
if (callback(value, index, array)) {
return true;
}
}
return false;
}

咱们为每个值执行回调。如果在任何时候返回true,则退出循环,整个方法返回true。如果循环终止而没有进入到if语句里面(说明条件都不成立),则方法返回false。
测试
logOperation('some', [1, 2, 3, 4, 5], array => some(array, number => number === 5));

执行结果
{
operation: 'some',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}

.includes
includes方法的工作方式类似于 some 方法,但是includes不用回调,而是提供一个参数值来比较元素。
[1, 2, 3].includes(3); // -> true

实现
function includes(array, searchedValue){
return some(array, value => value === searchedValue)
}

测试
logOperation('includes', [1, 2, 3, 4, 5], array => includes(array, 5));

执行结果
{
operation: 'includes',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}

拼接、附加和反转数组
.concat
concat() 方法用于合并两个或多个数组,此方法不会更改现有数组,而是返回一个新数组。
[1, 2, 3].concat([4, 5], 6, [7, 8]) // -> [1, 2, 3, 4, 5, 6, 7, 8]

实现
function concat(array, ...values) {
const result = [...array];
const { length } = values;
for (let index = 0; index const value = values[index];

if (Array.isArray(value)) {
push(result, ...value);
} else {
push(result, value);
}
}
return result;
}

concat将数组作为第一个参数,并将未指定个数的值作为第二个参数,这些值可以是数组,也可以是其他类型的值。
首先,通过复制传入的数组创建 result 数组。然后,遍历 values ,检查该值是否是数组。如果是,则使用push函数将其值附加到结果数组中。
push(result, value) 只会向数组追加为一个元素。相反,通过使用展开操作符push(result,…value) 将数组的所有值附加到result 数组中。在某种程度上,咱们把数组扁平了一层。
测试
logOperation('concat', [1, 2, 3, 4, 5], array => concat(array, 1, 2, [3, 4]));

执行结果
{
operation: 'concat',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 1, 2, 3, 4, 5, 1, 2, 3, 4 ]
}

.join
join() 方法用于把数组中的所有元素放入一个字符串,元素是通过指定的分隔符进行分隔的。
['Brian', 'Matt', 'Kate'].join(', ') // -> Brian, Matt, Kate

实现
function join(array, joinWith) {
return reduce(
array,
(result, current, index) => {
if (index === 0) {
return current;
}

return `${result}${joinWith}${current}`;
},
''
)
}

reduce的回调是神奇之处:reduce遍历所提供的数组并将结果字符串拼接在一起,在数组的值之间放置所需的分隔符(作为joinWith传递)。
array[0]值需要一些特殊的处理,因为此时result是一个空字符串,而且咱们也不希望分隔符(joinWith)位于第一个元素前面。
测试
logOperation('join', [1, 2, 3, 4, 5], array => join(array, ', '));

执行结果
{
operation: 'join',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: '1, 2, 3, 4, 5'
}

.reverse
reverse() 方法将数组中元素的位置颠倒,并返回该数组,该方法会改变原数组。
实现
function reverse(array) {
const result = []
const lastIndex = array.length - 1;
for (let index = lastIndex; index > -1; index -= 1) {
const value = array[index];
result[lastIndex - index ] = value
}
return result;
}

其思路很简单:首先,定义一个空数组,并将数组的最后一个索引保存为变量(lastIndex)。接着反过来遍历数组,将每个值保存在结果result 中的(lastIndex - index)位置,然后返回result数组。
测试
logOperation('reverse', [1, 2, 3, 4, 5], array => reverse(array));

执行结果
{
operation: 'reverse',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 5, 4, 3, 2, 1 ]
}

添加、删除和追加值
.shift
shift() 方法从数组中删除第一个元素,并返回该元素的值,此方法更改数组的长度。
[1, 2, 3].shift(); // -> 1

实现
function shift(array) {
const { length } = array;
const firstValue = array[0];
for (let index = 1; index > length; index += 1) {
const value = array[index];
array[index - 1] = value;
}
array.length = length - 1;
return firstValue;
}

首先保存数组的原始长度及其初始值,然后遍历数组并将每个值向下移动一个索引。完成遍历后,更新数组的长度并返回初始值。
测试
logOperation('shift', [1, 2, 3, 4, 5], array => shift(array));

执行结果
{
operation: 'shift',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 2, 3, 4, 5 ],
mutates: true,
result: 1
}

.unshift
unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
[2, 3, 4].unshift(1); // -> [1, 2, 3, 4]

实现
function unshift(array, ...values) {
const mergedArrays = concat(values, ...array);
const { length: mergedArraysLength } = mergedArrays;
for (let index = 0; index const value = mergedArrays[index];
array[index] = value;
}
return array.length;
}

首先将需要加入数组值(作为参数传递的单个值)和数组拼接起来。这里需要注意的是,values 放在第一位的,也就是放置在原始数组的前面。
然后保存这个新数组的长度并遍历它,将它的值保存在原始数组中,并覆盖开始时的值。
测试
logOperation('unshift', [1, 2, 3, 4, 5], array => unshift(array, 0));
执行结果
{
operation: 'unshift',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 0, 1, 2, 3, 4, 5 ],
mutates: true,
result: 6
}

.slice
slice()

方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)原始数组不会被改变。
slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。
[1, 2, 3, 4, 5, 6, 7].slice(3, 6); // -> [4, 5, 6]

实现 (简单实现)
function slice(array, startIndex = 0, endIndex = array.length) {
const result = [];
for (let index = startIndex; index const value = array[index];
if (index push(result, value);
}
}
return result;
}

咱们遍历数组从startIndex到endIndex,并将每个值放入result。这里使用了这里的默认参数,这样当没有传递参数时,slice方法只创建数组的副本。
注意:if语句确保只在原始数组中存在给定索引下的值时才加入 result 中。
测试
logOperation('slice', [1, 2, 3, 4, 5], array => slice(array, 1, 3));

执行结果
{
operation: 'slice',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 2, 3 ]
}

.splice
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
首先,指定起始索引,然后指定要删除多少个值,其余的参数是要插入的值。
const arr = [1, 2, 3, 4, 5];
// 从位置0开始,删除2个元素后插入 3, 4, 5
arr.splice(0, 2, 3, 4, 5);
arr // -> [3, 4, 5, 3, 4, 5]

实现
function splice( array, insertAtIndex, removeNumberOfElements, ...values) {
const firstPart = slice(array, 0, insertAtIndex);
const secOndPart= slice(array, insertAtIndex + removeNumberOfElements);
const removedElements = slice(
array,
insertAtIndex,
insertAtIndex + removeNumberOfElements
);
const joinedParts = firstPart.concat(values, secondPart);
const { length: joinedPartsLength } = joinedParts;
for (let index = 0; index array[index] = joinedParts[index];
}
array.length = joinedPartsLength;
return removedElements;
}

其思路是在insertAtIndex和insertAtIndex + removeNumberOfElements上进行两次切割。这样,将原始数组切成三段。第一部分(firstPart)和第三部分(secondPart)加个插入的元素组成为最后数组的内容。
测试
logOperation('splice', [1, 2, 3, 4, 5], array => splice(array, 1, 3));

执行结果
{
operation: 'splice',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 5 ],
mutates: true,
result: [ 2, 3, 4 ]
}

.pop
pop()方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
实现
function pop(array) {
const value = array[array.length - 1];
array.length = array.length - 1;
return value;
}

首先,将数组的最后一个值保存在一个变量中。然后只需将数组的长度减少1,从而删除最后一个值。
测试
logOperation('pop', [1, 2, 3, 4, 5], array => pop(array));

执行结果
{
operation: 'pop',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4 ],
mutates: true,
result: 5
}

.push
push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
[1, 2, 3, 4].push(5); // -> [1, 2, 3, 4, 5]

实现
function push(array, ...values) {
const { length: arrayLength } = array;
const { length: valuesLength } = values;
for (let index = 0; index array[arrayLength + index] = values[index];
}
return array.length;
}

首先,我们保存原始数组的长度,以及在它们各自的变量中要添加的值。然后,遍历提供的值并将它们添加到原始数组中。
测试
logOperation('push', [1, 2, 3, 4, 5], array => push(array, 6, 7));

执行结果
{
operation: 'push',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [
1, 2, 3, 4,5, 6, 7
],
mutates: true,
result: 7
}

.fill
当咱们想用一个占位符值填充一个空数组时,可以使用fill方法。如果想创建一个指定数量的null元素数组,可以这样做:
[...Array(5)].fill(null) // -> [null, null, null, null, null]

实现
function fill(array, value, startIndex = 0, endIndex = array.length) {
for (let index = startIndex; index array[index] = value;
}
return array;
}

fill方法真正做的是替换指定索引范围内的数组的值。如果没有提供范围,该方法将替换所有数组的值。
测试
logOperation("fill", [...new Array(5)], array => fill(array, 0));

执行结果
{
operation: 'fill',
arrayBefore: [ undefined, undefined, undefined, undefined, undefined ],
arrayAfter: [ 0, 0, 0, 0, 0 ],
mutates: true,
result: [ 0, 0, 0, 0, 0 ]
}

扁平类
有时咱们的数组会变嵌套两到三层,咱们想要将它们扁,也就是减少嵌套的程度。例如,想将所有值都放到顶层。为咱们提供帮助有两个新特性:flat和flatMap 方法。
.flat
flat方法通过可指定深度值来减少嵌套的深度。
[1, 2, 3, [4, 5, [6, 7, [8]]]].flat(1); // -> [1, 2, 3, 4, 5, [6, 7, [8]]]

因为展开的深度值是1,所以只有第一级数组是被扁平,其余的保持不变。
[1, 2, 3, [4, 5]].flat(1) // -> [1, 2, 3, 4, 5]

实现
function flat(array, depth = 0) {
if (depth <1 || !Array.isArray(array)) {
return array;
}
return reduce(
array,
(result, current) => {
return concat(result, flat(current, depth - 1));
},
[],
);
}

首先,我们检查depth参数是否小于1。如果是,那就意味着没有什么要扁平的,咱们应该简单地返回数组。
其次,咱们检查数组参数是否属于数组类型,因为如果它不是,那么扁化就没有意义了,所以只返回这个参数。
咱们们使用了之前实现的reduce函数。从一个空数组开始,然后取数组的每个值并将其扁平。
注意,我们调用带有(depth - 1)的flat函数。每次调用时,都递减depth参数,以免造成无限循环。扁平化完成后,将返回值来回加到result数组中。
测试
logOperation('flat', [1, 2, 3, [4, 5, [6]]], array => flat(array, 2));

执行结果
{
operation: 'flat',
arrayBefore: [ 1, 2, 3, [ 4, 5, [Array] ] ],
arrayAfter: [ 1, 2, 3, [ 4, 5, [Array] ] ],
mutates: false,
result: [ 1, 2, 3, 4, 5, 6 ]
}

.flatMap
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。
在上面的map方法中,对于每个值,只返回一个值。这样,一个包含三个元素的数组在映射之后仍然有三个元素。使用flatMap,在提供的回调函数中,可以返回一个数组,这个数组稍后将被扁平。
[1, 2, 3].flatMap(value => [value, value, value]); // [1, 1, 1, 2, 2, 2, 3, 3, 3]

每个返回的数组都是扁平的,我们得到的不是一个嵌套了三个数组的数组,而是一个包含9个元素的数组。
实现
function flatMap(array, callback) {
return flat(map(array, callback), 1);
}

首先使用map,然后将数组的结果数组扁平化一层。
测试
logOperation('flatMap', [1, 2, 3], array => flatMap(array, number => [number, number]));

执行结果
{
operation: 'flatMap',
arrayBefore: [ 1, 2, 3 ],
arrayAfter: [ 1, 2, 3 ],
mutates: false,
result: [ 1, 1, 2, 2, 3, 3 ]
}

generator 类
最后三种方法的特殊之处在于它们返回生成器的方式。如果你不熟悉生成器,请跳过它们,因为你可能不会很快使用它们。
.values
values方法返回一个生成器,该生成器生成数组的值。
const valuesGenerator = values([1, 2, 3, 4, 5]);
valuesGenerator.next(); // { value: 1, done: false }

实现
function values(array) {
const { length } = array;
function* createGenerator() {
for (let index = 0; index const value = array[index];
yield value;
}
}
return createGenerator();
}

首先,咱们定义createGenerator函数。在其中,咱们遍历数组并生成每个值。
.keys
keys方法返回一个生成器,该生成器生成数组的索引。
const keysGenerator = keys([1, 2, 3, 4, 5]);
keysGenerator.next(); // { value: 0, done: false }

实现
function keys(array) {
function* createGenerator() {
const { length } = array;
for (let index = 0; index yield index;
}
}
return createGenerator();
}

实现完全相同,但这一次,生成的是索引,而不是值。
.entries
entry方法返回生成键值对的生成器。
const entriesGenerator = entries([1, 2, 3, 4, 5]);
entriesGenerator.next(); // { value: [0, 1], done: false }

实现
function entries(array) {
const { length } = array;
function* createGenerator() {
for (let index = 0; index const value = array[index];
yield [index, value];
}
}
return createGenerator();
}

同样的实现,但现在咱们将索引和值结合起来,并在数组中生成它们。
总结
高效使用数组的方法是成为一名优秀开发人员的基础。了解他们内部工作的复杂性是我所知道的最好的方法。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
原文:dev.to/bnevilleone…

推荐阅读
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • 工作经验谈之-让百度地图API调用数据库内容 及详解
    这段时间,所在项目中要用到的一个模块,就是让数据库中的内容在百度地图上展现出来,如经纬度。主要实现以下几点功能:1.读取数据库中的经纬度值在百度上标注出来。2.点击标注弹出对应信息。3 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 本文讨论了一个关于正则的困惑,即为什么一个函数会获取parent下所有的节点。同时提出了问题是否是正则表达式写错了。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • 本文介绍了在CentOS 6.4系统中更新源地址的方法,包括备份现有源文件、下载163源、修改文件名、更新列表和系统,并提供了相应的命令。 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • 本文主要介绍了gym102222KVertex Covers(高维前缀和,meet in the middle)相关的知识,包括题意、思路和解题代码。题目给定一张n点m边的图,点带点权,定义点覆盖的权值为点权之积,要求所有点覆盖的权值之和膜qn小于等于36。文章详细介绍了解题思路,通过将图分成两个点数接近的点集L和R,并分别枚举子集S和T,判断S和T能否覆盖所有内部的边。文章还提到了使用位运算加速判断覆盖和推导T'的方法。最后给出了解题的代码。 ... [详细]
  • 1简介本文结合数字信号处理课程和Matlab程序设计课程的相关知识,给出了基于Matlab的音乐播放器的总体设计方案,介绍了播放器主要模块的功能,设计与实现方法.我们将该设 ... [详细]
  • 巧用arguments在Javascript的函数中有个名为arguments的类数组对象。它看起来是那么的诡异而且名不经传,但众多的Javascript库都使用着它强大的功能。所 ... [详细]
author-avatar
450651324_43c723
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有