实现原生方法
为了保证的可读性,本文采用意译而非直译。
要在给定数组上使用方法,只需要通过[].方法名
即可,这些方法都定义在 Array.prototype
对象上。在这里,咱们先不使用这些相,反,咱们将从简单的方法开始定义自己的版本,并在这些版本的基础上进行构建。
没有比把东西拆开再重新组装起来更好的学习方法了。注意,当咱们的实现自己的方法时,不要覆盖现有的方法,因为有的库需要它们,并且这样也方便比较咱们自己的方法与原始方法的差异。
所以不要这样命名咱们自定义的方法:
Array.prototype.map = function map() {
// implementation
};
复制代码
最好这样命名:
function map(array) {
// implementation
}
复制代码
集合类
.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 < length; index += 1) {
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 < firstArray.length; index += 1) {
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 < length; index +=1) {
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 < valuesLength; index += 1) { array[arrayLength + index] = values[index]; } return array.length; }
function filter(array, callback) { const result = []; const { length } = array; for (let index = 0; index < length; index += 1) { const value = array[index]; if (callback(value, index, array)) { push(result, value); } } return result; } 复制代码
获取每个值并检查所提供的回调函数是否返回`true`或`false`,然后将该值添加到新创建的数组中,或者适当地丢弃它。<br />注意,这里对`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 ] } 复制代码
<a name="7otlM"></a>
#### .reduce
`reduce()` 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为**一个值**。`reduce()` 方法接受四个参数:**初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce() 的数组**。<br />确切地说,如何计算该值是需要在回调中指定的。来看呓使用`reduce`的一个简单的例子:对一组数字求和:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce((sum, number) => { return sum + number; }, 0) // -> 55 复制代码
注意这里的回调接受两个参数:`sum`和`number`。第一个参数总是前一个迭代返回的结果,第二个参数在遍历中的当前数组元素。<br />这里,当咱们对数组进行迭代时,`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 < length; index += 1) { const value = array[index]; acc = callback(acc, value, index, array) }
return acc; } 复制代码
咱们创建了两个变量`acc`和`startAtIndex`,并用它们的默认值初始化它们,分别是参数`initValue`和`0`。<br />然后,检查`initValue`是否是`undefined`。如果是,则必须将数组的第一个值设置为初值,为了不重复计算初始元素,将`startAtIndex`设置为`1`。<br />每次迭代,`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 } 复制代码
<a name="sEkqP"></a>
### 检索类
有什么操作比搜索特定值更常见?这里有一些方法可以帮助我们。
<a name="kPbUz"></a>
#### .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 < length; index += 1) { const value = array[index]; if (callback(value, index, array)) { return index; } } return -1; } 复制代码
<a name="Z6sGA"></a>
#### 测试
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 } 复制代码
<a name="whjHJ"></a>
#### .find
`find`与`findIndex`的唯一区别在于,它返回的是实际值,而不是索引。实际工作中,咱们可以重用已经实现的`findIndex`。
[1, 2, 3, 4, 5, 6, 7].find(value => value === 5); // 5 复制代码
<a name="ZYd4L"></a>
#### 实现
function find(array, callback) { const index = findIndex(array, callback); if (index === -1) { return undefined; } return array[index]; } 复制代码
<a name="bA13v"></a>
#### 测试
logOperation(‘find’, [1, 2, 3, 4, 5], array => find(array, number => number === 3)); 复制代码
<a name="eJ70g"></a>
#### 运行
{ operation: ‘find’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: 3 } 复制代码
<a name="ppwSd"></a>
#### .indexOf
`indexOf`是获取给定值索引的另一种方法。然而,这一次,咱们将实际值作为参数而不是函数传递。同样,为了简化实现,可以使用前面实现的`findIndex`
[3, 2, 3].indexOf(3); // -> 0 复制代码
<a name="7y1xu"></a>
#### 实现
function indexOf(array, searchedValue) { return findIndex(array, value => value === searchedValue) } 复制代码
<a name="wtvRc"></a>
#### 测试
logOperation(‘indexOf’, [1, 2, 3, 4, 5], array => indexOf(array, 3)); 复制代码
<a name="dOMeY"></a>
#### 执行结果
{ operation: ‘indexOf’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: 2 } 复制代码
<a name="EyvAR"></a>
#### .lastIndexOf
lastIndexOf的工作方式与`indexOf`相同,`lastIndexOf()` 方法返回指定元素在数组中的最后一个的索引,如果不存在则返回 `-1`。
[3, 2, 3].lastIndexOf(3); // -> 2 复制代码
<a name="2JcX8"></a>
#### 实现
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`。
<a name="Uz8oq"></a>
#### 测试
logOperation(‘lastIndexOf’, [1, 2, 3, 4, 5, 3], array => lastIndexOf(array, 3)); 复制代码
<a name="cJGDp"></a>
#### 执行结果
{ operation: ‘lastIndexOf’, arrayBefore: [ 1, 2, 3, 4, 5, 3 ], arrayAfter: [ 1, 2, 3, 4, 5, 3 ], mutates: false, result: 5 } 复制代码
<a name="Y7cmp"></a>
#### .every
`every()` 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试,它返回一个布尔值。
[1, 2, 3].every(value => Number.isInteger(value)); // -> true 复制代码
咱们可以将`every` 方法看作一个等价于逻辑与的数组。
<a name="wLqfk"></a>
#### 实现
function every(array, callback){ const { length } = array;
for (let index = 0; index < length; index += 1) { const value = array[index];
if (!callback(value, index, array)) {
return false;
}
} return true; } 复制代码
咱们为每个值执行回调。如果在任何时候返回`false`,则退出循环,整个方法返回`false`。如果循环终止而没有进入到`if`语句里面(说明条件都成立),则方法返回`true`。
<a name="mPXKJ"></a>
#### 测试
logOperation(‘every’, [1, 2, 3, 4, 5], array => every(array, number => Number.isInteger(number))); 复制代码
<a name="3gBaa"></a>
#### 执行结果
{ operation: ‘every’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: true } 复制代码
<a name="VCue1"></a>
#### .some
`some` 方法与 `every` 刚好相反,即只要其中一个为`true` 就会返回`true`。与`every` 方法类似,咱们可以将`some` 方法看作一个等价于逻辑或数组。
[1, 2, 3, 4, 5].some(number => number === 5); // -> true 复制代码
<a name="5FCiU"></a>
#### 实现
function some(array, callback) { const { length } = array; for (let index = 0; index < length; index += 1) { const value = array[index]; if (callback(value, index, array)) { return true; } } return false; } 复制代码
咱们为每个值执行回调。如果在任何时候返回`true`,则退出循环,整个方法返回`true`。如果循环终止而没有进入到`if`语句里面(说明条件都不成立),则方法返回`false`。
<a name="jEQlX"></a>
#### 测试
logOperation(‘some’, [1, 2, 3, 4, 5], array => some(array, number => number === 5)); 复制代码
<a name="uMq7T"></a>
#### 执行结果
{ operation: ‘some’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: true } 复制代码
<a name="rNilI"></a>
#### .includes
`includes`方法的工作方式类似于 `some` 方法,但是`includes`不用回调,而是提供一个参数值来比较元素。
[1, 2, 3].includes(3); // -> true 复制代码
<a name="WxuYM"></a>
#### 实现
function includes(array, searchedValue){ return some(array, value => value === searchedValue) } 复制代码
<a name="MZuHz"></a>
#### 测试
logOperation(‘includes’, [1, 2, 3, 4, 5], array => includes(array, 5)); 复制代码
<a name="ZQuuz"></a>
#### 执行结果
{ operation: ‘includes’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: true } 复制代码
<a name="T1VNE"></a>
# 拼接、附加和反转数组
<a name="3ULs6"></a>
#### .concat
`concat()` 方法用于合并两个或多个数组,此方法不会更改现有数组,而是返回一个新数组。
[1, 2, 3].concat([4, 5], 6, [7, 8]) // -> [1, 2, 3, 4, 5, 6, 7, 8] 复制代码
<a name="E9SVd"></a>
#### 实现
function concat(array, …values) { const result = […array]; const { length } = values; for (let index = 0; index < length; index += 1) { const value = values[index];
if (Array.isArray(value)) {
push(result, ...value);
} else {
push(result, value);
}
} return result; } 复制代码
`concat`将数组作为第一个参数,并将未指定个数的值作为第二个参数,这些值可以是数组,也可以是其他类型的值。<br />首先,通过复制传入的数组创建 `result` 数组。然后,遍历 `values` ,检查该值是否是数组。如果是,则使用`push`函数将其值附加到结果数组中。<br />`push(result, value)` 只会向数组追加为一个元素。相反,通过使用展开操作符`push(result,…value)` 将数组的所有值附加到`result` 数组中。在某种程度上,咱们把数组扁平了一层。
<a name="yB5CF"></a>
#### 测试
logOperation(‘concat’, [1, 2, 3, 4, 5], array => concat(array, 1, 2, [3, 4])); 复制代码
<a name="qz59W"></a>
#### 执行结果
{ 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 ] } 复制代码
<a name="BG4zm"></a>
## .join
`join()` 方法用于把数组中的所有元素放入一个字符串,元素是通过指定的分隔符进行分隔的。
[‘Brian’, ‘Matt’, ‘Kate’].join(‘, ‘) // -> Brian, Matt, Kate 复制代码
<a name="tMaMF"></a>
#### 实现
function join(array, joinWith) { return reduce( array, (result, current, index) => { if (index === 0) { return current; }
return `${result}${joinWith}${current}`;
},
''
) } 复制代码
`reduce`的回调是神奇之处:`reduce`遍历所提供的数组并将结果字符串拼接在一起,在数组的值之间放置所需的分隔符(作为`joinWith`传递)。<br />`array[0]`值需要一些特殊的处理,因为此时`result`是一个空字符串,而且咱们也不希望分隔符(`joinWith`)位于第一个元素前面。
<a name="IpOas"></a>
#### 测试
logOperation(‘join’, [1, 2, 3, 4, 5], array => join(array, ‘, ‘)); 复制代码
<a name="fi9JH"></a>
#### 执行结果
{ operation: ‘join’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: ‘1, 2, 3, 4, 5’ } 复制代码
<a name="42mGq"></a>
## .reverse
`reverse()` 方法将数组中元素的位置颠倒,并返回该数组,该方法会改变原数组。
<a name="7tZVy"></a>
#### 实现
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`数组。
<a name="xCQM1"></a>
#### 测试
logOperation(‘reverse’, [1, 2, 3, 4, 5], array => reverse(array)); 复制代码
<a name="lnLww"></a>
#### 执行结果
{ operation: ‘reverse’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: [ 5, 4, 3, 2, 1 ] } 复制代码
<a name="Okjkh"></a>
# 添加、删除和追加值
<a name="7XdxI"></a>
## .shift
`shift()` 方法从数组中删除第一个元素,并返回该元素的值,此方法更改数组的长度。
[1, 2, 3].shift(); // -> 1 复制代码
<a name="x6x25"></a>
#### 实现
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; } 复制代码
首先保存数组的原始长度及其初始值,然后遍历数组并将每个值向下移动一个索引。完成遍历后,更新数组的长度并返回初始值。
<a name="Oa1Nk"></a>
#### 测试
logOperation(‘shift’, [1, 2, 3, 4, 5], array => shift(array)); 复制代码
<a name="gqKAj"></a>
#### 执行结果
{ operation: ‘shift’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 2, 3, 4, 5 ], mutates: true, result: 1 } 复制代码
<a name="x8ZiZ"></a>
## .unshift
`unshift()` 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
[2, 3, 4].unshift(1); // -> [1, 2, 3, 4] 复制代码
<a name="CYZDq"></a>
#### 实现
function unshift(array, …values) { const mergedArrays = concat(values, …array); const { length: mergedArraysLength } = mergedArrays; for (let index = 0; index < mergedArraysLength; index += 1) { const value = mergedArrays[index]; array[index] = value; } return array.length; } 复制代码
首先将需要加入数组**值**(作为参数传递的单个值)和**数组**拼接起来。**这里需要注意的是**,`values` 放在第一位的,也就是放置在原始数组的前面。<br />然后保存这个新数组的长度并遍历它,将它的值保存在原始数组中,并覆盖开始时的值。
<a name="e15gN"></a>
#### 测试
logOperation('unshift', [1, 2, 3, 4, 5], array => unshift(array, 0));
<a name="krgUw"></a>
#### 执行结果
{ operation: ‘unshift’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 0, 1, 2, 3, 4, 5 ], mutates: true, result: 6 } 复制代码
<a name="IZpvc"></a>
## .slice
slice() 复制代码
方法返回一个新的数组对象,这一对象是一个由 `begin` 和 `end` 决定的原数组的浅拷贝(包括 `begin`,不包括`end`)原始数组不会被改变。<br />`slice` 会提取原数组中索引从 `begin` 到 `end` 的所有元素(包含 `begin`,但不包含 `end`)。
[1, 2, 3, 4, 5, 6, 7].slice(3, 6); // -> [4, 5, 6] 复制代码
<a name="BdfkI"></a>
#### 实现 (简单实现)
function slice(array, startIndex = 0, endIndex = array.length) { const result = []; for (let index = startIndex; index < endIndex; index += 1) { const value = array[index]; if (index < array.length) { push(result, value); } } return result; } 复制代码
咱们遍历数组从`startIndex`到`endIndex`,并将每个值放入`result`。这里使用了这里的默认参数,这样当没有传递参数时,`slice`方法只创建数组的副本。<br />注意:`if`语句确保只在原始数组中存在给定索引下的值时才加入 `result` 中。
<a name="vUV0a"></a>
#### 测试
logOperation(‘slice’, [1, 2, 3, 4, 5], array => slice(array, 1, 3)); 复制代码
<a name="VSVtT"></a>
#### 执行结果
{ operation: ‘slice’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4, 5 ], mutates: false, result: [ 2, 3 ] } 复制代码
<a name="ZFQsP"></a>
## .splice
`splice()` 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。<br />首先,指定起始索引,然后指定要删除多少个值,其余的参数是要插入的值。
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] 复制代码
<a name="cTwOJ"></a>
#### 实现
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 < joinedPartsLength; index += 1) { array[index] = joinedParts[index]; } array.length = joinedPartsLength; return removedElements; } 复制代码
其思路是在`insertAtIndex`和`insertAtIndex + removeNumberOfElements`上进行两次切割。这样,将原始数组切成三段。第一部分(`firstPart`)和第三部分(`secondPart`)加个插入的元素组成为最后数组的内容。
<a name="LTAwr"></a>
#### 测试
logOperation(‘splice’, [1, 2, 3, 4, 5], array => splice(array, 1, 3)); 复制代码
<a name="GTIVE"></a>
#### 执行结果
{ operation: ‘splice’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 5 ], mutates: true, result: [ 2, 3, 4 ] } 复制代码
<a name="MVHk3"></a>
## .pop
`pop()`方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
<a name="BjyJn"></a>
#### 实现
function pop(array) { const value = array[array.length - 1]; array.length = array.length - 1; return value; } 复制代码
首先,将数组的最后一个值保存在一个变量中。然后只需将数组的长度减少`1`,从而删除最后一个值。
<a name="DA23I"></a>
#### 测试
logOperation(‘pop’, [1, 2, 3, 4, 5], array => pop(array)); 复制代码
<a name="rvGbe"></a>
#### 执行结果
{ operation: ‘pop’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4 ], mutates: true, result: 5 } 复制代码
<a name="BOOBG"></a>
## .push
`push()` 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
[1, 2, 3, 4].push(5); // -> [1, 2, 3, 4, 5] 复制代码
<a name="SDnha"></a>
#### 实现
function push(array, …values) { const { length: arrayLength } = array; const { length: valuesLength } = values; for (let index = 0; index < valuesLength; index += 1) { array[arrayLength + index] = values[index]; } return array.length; } 复制代码
首先,我们保存原始数组的长度,以及在它们各自的变量中要添加的值。然后,遍历提供的值并将它们添加到原始数组中。
<a name="GNPAX"></a>
#### 测试
logOperation(‘push’, [1, 2, 3, 4, 5], array => push(array, 6, 7)); 复制代码
<a name="zUXTr"></a>
#### 执行结果
{ operation: ‘push’, arrayBefore: [ 1, 2, 3, 4, 5 ], arrayAfter: [ 1, 2, 3, 4,5, 6, 7 ], mutates: true, result: 7 } 复制代码
<a name="e6WDC"></a>
## .fill
当咱们想用一个占位符值填充一个空数组时,可以使用`fill`方法。如果想创建一个指定数量的`null`元素数组,可以这样做:
[…Array(5)].fill(null) // -> [null, null, null, null, null] 复制代码
<a name="e9w0v"></a>
#### 实现
function fill(array, value, startIndex = 0, endIndex = array.length) { for (let index = startIndex; index < endIndex; index += 1) { array[index] = value; } return array; } 复制代码
`fill`方法真正做的是替换指定索引范围内的数组的值。如果没有提供范围,该方法将替换所有数组的值。
<a name="nAznt"></a>
#### 测试
logOperation(“fill”, […new Array(5)], array => fill(array, 0)); 复制代码
<a name="u9Oc5"></a>
#### 执行结果
{ operation: ‘fill’, arrayBefore: [ undefined, undefined, undefined, undefined, undefined ], arrayAfter: [ 0, 0, 0, 0, 0 ], mutates: true, result: [ 0, 0, 0, 0, 0 ] } 复制代码
<a name="JHDP7"></a>
# 扁平类
有时咱们的数组会变嵌套两到三层,咱们想要将它们扁,也就是减少嵌套的程度。例如,想将所有值都放到顶层。为咱们提供帮助有两个新特性:`flat`和`flatMap` 方法。
<a name="NOUGJ"></a>
## .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] 复制代码
<a name="7nQIR"></a>
#### 实现
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`。如果是,那就意味着没有什么要扁平的,咱们应该简单地返回数组。<br />其次,咱们检查数组参数是否属于数组类型,因为如果它不是,那么扁化就没有意义了,所以只返回这个参数。<br />咱们们使用了之前实现的`reduce`函数。从一个空数组开始,然后取数组的每个值并将其扁平。<br />注意,我们调用带有`(depth - 1)`的`flat`函数。每次调用时,都递减`depth`参数,以免造成无限循环。扁平化完成后,将返回值来回加到`result`数组中。
<a name="cxJfb"></a>
#### 测试
logOperation(‘flat’, [1, 2, 3, [4, 5, [6]]], array => flat(array, 2)); 复制代码
<a name="LfIOx"></a>
#### 执行结果
{ 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 ] } 复制代码
<a name="lcdTx"></a>
## .flatMap
`flatMap()` 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。<br />在上面的`map`方法中,对于每个值,只返回一个值。这样,一个包含三个元素的数组在映射之后仍然有三个元素。使用`flatMap`,在提供的回调函数中,可以返回一个数组,这个数组稍后将被扁平。
[1, 2, 3].flatMap(value => [value, value, value]); // [1, 1, 1, 2, 2, 2, 3, 3, 3] 复制代码
每个返回的数组都是扁平的,我们得到的不是一个嵌套了三个数组的数组,而是一个包含9个元素的数组。
<a name="rAlQ3"></a>
#### 实现
function flatMap(array, callback) { return flat(map(array, callback), 1); } 复制代码
首先使用`map`,然后将数组的结果数组扁平化一层。
<a name="5nE72"></a>
#### 测试
logOperation(‘flatMap’, [1, 2, 3], array => flatMap(array, number => [number, number])); 复制代码
<a name="KwUIb"></a>
#### 执行结果
{ operation: ‘flatMap’, arrayBefore: [ 1, 2, 3 ], arrayAfter: [ 1, 2, 3 ], mutates: false, result: [ 1, 1, 2, 2, 3, 3 ] } 复制代码
<a name="3Q8dz"></a>
# generator 类
最后三种方法的特殊之处在于它们返回生成器的方式。如果你不熟悉生成器,请跳过它们,因为你可能不会很快使用它们。
<a name="ydQDT"></a>
## .values
`values`方法返回一个生成器,该生成器生成数组的值。
const valuesGenerator = values([1, 2, 3, 4, 5]); valuesGenerator.next(); // { value: 1, done: false } 复制代码
<a name="DONlh"></a>
#### 实现
function values(array) { const { length } = array; function* createGenerator() { for (let index = 0; index < length; index += 1) { const value = array[index]; yield value; } } return createGenerator(); } 复制代码
首先,咱们定义`createGenerator`函数。在其中,咱们遍历数组并生成每个值。
<a name="tUTiA"></a>
## .keys
`keys`方法返回一个生成器,该生成器生成数组的索引。
const keysGenerator = keys([1, 2, 3, 4, 5]); keysGenerator.next(); // { value: 0, done: false } 复制代码
<a name="5uK3x"></a>
#### 实现
function keys(array) { function* createGenerator() { const { length } = array; for (let index = 0; index < length; index += 1) { yield index; } } return createGenerator(); } 复制代码
实现完全相同,但这一次,生成的是索引,而不是值。
<a name="yzyAn"></a>
## .entries
`entry`方法返回生成键值对的生成器。
const entriesGenerator = entries([1, 2, 3, 4, 5]); entriesGenerator.next(); // { value: [0, 1], done: false } 复制代码
<a name="kC5g2"></a>
#### 实现
function entries(array) { const { length } = array; function* createGenerator() { for (let index = 0; index < length; index += 1) { const value = array[index]; yield [index, value]; } } return createGenerator(); } 复制代码
同样的实现,但现在咱们将索引和值结合起来,并在数组中生成它们。
作者:前端小智<br />链接:https://juejin.im/post/5d82c12ff265da03a31d6f92<br />来源:掘金<br />著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
<a name="UFHiQ"></a>
### 创建一个数组:
// 字面量方式: // 这个方法也是我们最常用的,在初始化数组的时候 相当方便 var a = [3, 11, 8]; // [3,11,8]; // 构造器: // 实际上 new Array === Array,加不加new 一点影响都没有。 var a = Array(); // [] var a = Array(3); // [undefined,undefined,undefined] var a = Array(3,11,8); // [ 3,11,8 ]
<a name="2k0Ig"></a>
#### ES6 Array.of() 返回由所有参数值组成的数组
定义:返回由所有参数值组成的数组,如果没有参数,就返回一个空数组。<br />目的:Array.of() 出现的目的是为了解决上述构造器因参数个数不同,导致的行为有差异的问题。
let a = Array.of(3, 11, 8); // [3,11,8] let a = Array.of(3); // [3]
<a name="80Ei9"></a>
#### ES6 Arrar.from() 将两类对象转为真正的数组
定义:用于将两类对象转为真正的数组(不改变原对象,返回新的数组)。<br />参数:<br />第一个参数(必需):要转化为真正数组的对象。<br />第二个参数(可选): 类似数组的map方法,对每个元素进行处理,将处理后的值放入返回的数组。<br />第三个参数(可选): 用来绑定this。
// 1. 对象拥有length属性 let obj = {0: ‘a’, 1: ‘b’, 2:’c’, length: 3}; let arr = Array.from(obj); // [‘a’,’b’,’c’]; // 2. 部署了 Iterator接口的数据结构 比如:字符串、Set、NodeList对象 let arr = Array.from(‘hello’); // [‘h’,’e’,’l’,’l’] let arr = Array.from(new Set([‘a’,’b’])); // [‘a’,’b’]
---
<a name="paHNd"></a>
## 方法:
数组原型提供了非常多的方法,这里分为三类来讲,一类会改变原数组的值,一类是不会改变原数组,以及数组的遍历方法。
<a name="40OTg"></a>
### 改变原数组的方法(9个):
let a = [1,2,3]; ES5: a.pop()/ a.shift()/ a.push()/ a.unshift()/ a.reverse()/ a.splice()/ a.sort() ES6: a.copyWithin() / a.fill
对于这些能够改变原数组的方法,要注意避免在循环遍历中改变原数组的选项,比如: 改变数组的长度,导致遍历的长度出现问题。
<a name="PxLfT"></a>
#### pop() 删除一个数组中的最后的一个元素
定义: pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素。<br />参数: 无。
let a = [1,2,3]; let item = a.pop(); // 3 console.log(a); // [1,2]
<a name="TCsW2"></a>
#### shift() 删除数组的第一个元素
定义: shift()方法删除数组的第一个元素,并返回这个元素。<br />参数: 无。
let a = [1,2,3]; let item = a.shift(); // 1 console.log(a); // [2,3]
<a name="a7YdL"></a>
#### push() 向数组的末尾添加元素
定义:push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。<br />参数: item1, item2, …, itemX ,要添加到数组末尾的元素
let a = [1,2,3]; let item = a.push(‘末尾’); // 4 console.log(a); // [1,2,3,’末尾’]
<a name="dzjIx"></a>
#### unshift()
定义:unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。<br />参数: item1, item2, …, itemX ,要添加到数组开头的元素
let a = [1,2,3]; let item = a.unshift(‘开头’); // 4 console.log(a); // [‘开头’,1,2,3]
<a name="oThBB"></a>
#### reverse() 颠倒数组中元素的顺序
定义: reverse() 方法用于颠倒数组中元素的顺序。<br />参数: 无
let a = [1,2,3]; a.reverse(); console.log(a); // [3,2,1]
<a name="ElADL"></a>
#### splice() 添加/删除数组元素
定义: splice() 方法**向/从数组中添加/删除**项目,然后返回被删除的项目<br />语法: `array.splice(index,howmany,item1,.....,itemX)`<br />参数:
1. index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
1. howmany:必需。要删除的项目数量。如果设置为 0,则不会删除项目。
1. item1, …, itemX: 可选。向数组添加的新项目。
返回值: 如果有元素被删除,返回包含被删除项目的新数组。<br />eg1:删除元素
let a = [1, 2, 3, 4, 5, 6, 7]; let item = a.splice(0, 3); // [1,2,3] console.log(a); // [4,5,6,7] // 从数组下标0开始,删除3个元素 let item = a.splice(-1, 3); // [7] // 从最后一个元素开始删除3个元素,因为最后一个元素,所以只删除了7
eg2: 删除并添加
let a = [1, 2, 3, 4, 5, 6, 7]; let item = a.splice(0,3,’添加’); // [1,2,3] console.log(a); // [‘添加’,4,5,6,7] // 从数组下标0开始,删除3个元素,并添加元素’添加’ let b = [1, 2, 3, 4, 5, 6, 7]; let item = b.splice(-2,3,’添加1’,’添加2’); // [6,7] console.log(b); // [1,2,3,4,5,’添加1’,’添加2’] // 从数组最后第二个元素开始,删除3个元素,并添加两个元素’添加1’、’添加2’
eg3: 不删除只添加:
let a = [1, 2, 3, 4, 5, 6, 7]; let item = a.splice(0,0,’添加1’,’添加2’); // [] 没有删除元素,返回空数组 console.log(a); // [‘添加1’,’添加2’,1,2,3,4,5,6,7] let b = [1, 2, 3, 4, 5, 6, 7]; let item = b.splice(-1,0,’添加1’,’添加2’); // [] 没有删除元素,返回空数组 console.log(b); // [1,2,3,4,5,6,’添加1’,’添加2’,7] 在最后一个元素的前面添加两个元素
从上述三个栗子可以得出:
1. 数组如果元素不够,会删除到最后一个元素为止
1. 操作的元素,包括开始的那个元素
1. 可以添加很多个元素
1. 添加是在开始的元素前面添加的
<a name="3Bxb7"></a>
#### sort() 数组排序
定义: sort()方法对数组元素进行排序,并返回这个数组。<br />参数可选: 规定排序顺序的比较函数。<br />默认情况下sort()方法没有传比较函数的话,默认按字母升序,如果不是元素不是字符串的话,会调用`toString()`方法将元素转化为字符串的Unicode(万国码)位点,然后再比较字符。
// 字符串排列 看起来很正常 var a = [“Banana”, “Orange”, “Apple”, “Mango”]; a.sort(); // [“Apple”,”Banana”,”Mango”,”Orange”] // 数字排序的时候 因为转换成Unicode字符串之后,有些数字会比较大会排在后面 这显然不是我们想要的 var a = [10, 1, 3, 20,25,8]; console.log(a.sort()) // [1,10,20,25,3,8];
**比较函数的两个参数:**<br />sort的比较函数有两个默认参数,要在函数中接收这两个参数,这两个参数是数组中两个要比较的元素,通常我们用 a 和 b 接收两个将要比较的元素:
- 若比较函数返回值<0,那么a将排到b的前面;
- 若比较函数返回值=0,那么a 和 b 相对位置不变;
- 若比较函数返回值>0,那么b 排在a 将的前面;
对于sort()方法更深层级的内部实现以及处理机制可以看一下这篇文章[深入了解javascript的sort方法](https://juejin.im/entry/59f7f3346fb9a04514635552)<br />**sort排序常见用法**:
1. 数组元素为数字的升序、降序:
var array = [10, 1, 3, 4,20,4,25,8]; // 升序 a-b < 0 a将排到b的前面,按照a的大小来排序的 // 比如被减数a是10,减数是20 10-20 < 0 被减数a(10)在减数b(20)前面 array.sort(function(a,b){ return a-b; }); console.log(array); // [1,3,4,4,8,10,20,25]; // 降序 被减数和减数调换了 20-10>0 被减数b(20)在减数a(10)的前面 array.sort(function(a,b){ return b-a; }); console.log(array); // [25,20,10,8,4,4,3,1];
1. 数组多条件排序
var array = [{id:10,age:2},{id:5,age:4},{id:6,age:10},{id:9,age:6},{id:2,age:8},{id:10,age:9}]; array.sort(function(a,b){ if(a.id === b.id){// 如果id的值相等,按照age的值降序 return b.age - a.age }else{ // 如果id的值不相等,按照id的值升序 return a.id - b.id } }) // [{“id”:2,”age”:8},{“id”:5,”age”:4},{“id”:6,”age”:10},{“id”:9,”age”:6},{“id”:10,”age”:9},{“id”:10,”age”:2}]
1. 自定义比较函数,天空才是你的极限<br />
类似的:**运用好返回值,我们可以写出任意符合自己需求的比较函数**
var array = [{name:’Koro1’},{name:’Koro1’},{name:’OB’},{name:’Koro1’},{name:’OB’},{name:’OB’}]; array.sort(function(a,b){ if(a.name === ‘Koro1’){// 如果name是’Koro1’ 返回-1 ,-1<0 a排在b的前面 return -1 }else{ // 如果不是的话,a排在b的后面 return 1 } }) // [{“name”:”Koro1”},{“name”:”Koro1”},{“name”:”Koro1”},{“name”:”OB”},{“name”:”OB”},{“name”:”OB”}]
<a name="kW3zl"></a>
#### ES6: copyWithin() 指定位置的成员复制到其他位置
定义: 在当前数组内部,将指定位置的成员复制到其他位置,并返回这个数组。<br />语法:
array.copyWithin(target, start = 0, end = this.length)
参数:<br />三个参数都是数值,如果不是,会自动转为数值.
1. target(必需):从该位置开始替换数据。如果为负值,表示倒数。
1. start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
1. end(可选):到该位置前停止读取数据,默认等于数组长度。使用负数可从数组结尾处规定位置。
浏览器兼容(MDN): chrome 45,Edge 12,Firefox32,Opera 32,Safari 9, IE 不支持<br />eg:
// -2相当于3号位,-1相当于4号位 [1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5] var a=[‘OB1’,’Koro1’,’OB2’,’Koro2’,’OB3’,’Koro3’,’OB4’,’Koro4’,’OB5’,’Koro5’] // 2位置开始被替换,3位置开始读取要替换的 5位置前面停止替换 a.copyWithin(2,3,5) // [“OB1”,”Koro1”,”Koro2”,”OB3”,”OB3”,”Koro3”,”OB4”,”Koro4”,”OB5”,”Koro5”]
从上述栗子:
1. 第一个参数是开始被替换的元素位置
1. 要替换数据的位置范围:从第二个参数是开始读取的元素,在第三个参数前面一个元素停止读取
1. 数组的长度不会改变
1. **读了几个元素就从开始被替换的地方替换几个元素**
<a name="tFDFG"></a>
#### ES6: fill() 填充数组
定义: 使用给定值,填充一个数组。<br />参数:<br />第一个元素(必须): 要填充数组的值<br />第二个元素(可选): 填充的开始位置,默认值为0<br />第三个元素(可选):填充的结束位置,默认是为`this.length`<br />[MDN浏览器兼容](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/fill#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7)
[‘a’, ‘b’, ‘c’].fill(7) // [7, 7, 7] [‘a’, ‘b’, ‘c’].fill(7, 1, 2) // [‘a’, 7, ‘c’]
---
<a name="d1gSw"></a>
### 不改变原数组的方法(8个):
ES5: join、toLocateString、toStrigin、slice、cancat、indexOf、lastIndexOf、 ES7: includes
<a name="Xmhw1"></a>
#### join() 数组转字符串
定义: join() 方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串。<br />语法:
array.join(str)
参数:<br />str(可选): 指定要使用的分隔符,默认使用逗号作为分隔符。
let a= [‘hello’,’world’]; let str=a.join(); // ‘hello,world’ let str2=a.join(‘+’); // ‘hello+world’
使用join方法或者下文说到的toString方法时,当数组中的元素也是数组或者是对象时会出现什么情况?
let a= [[‘OBKoro1’,’23’],’test’]; let str1=a.join(); // OBKoro1,23,test let b= [{name:’OBKoro1’,age:’23’},’test’]; let str2 = b.join(); // [object Object],test // 对象转字符串推荐JSON.stringify(obj);
所以,`join()/toString()`方法在数组元素是数组的时候,会将里面的数组也调用`join()/toString()`,如果是对象的话,对象会被转为`[object Object]`字符串。
<a name="pzY5w"></a>
#### toLocaleString() 数组转字符串
定义: 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。<br />语法:
array.toLocaleString()
参数:无。
let a=[{name:’OBKoro1’},23,’abcd’,new Date()]; let str=a.toLocaleString(); // [object Object],23,abcd,2018/5/28 下午1:52:20
如上述栗子:调用数组的`toLocaleString`方法,数组中的每个元素都会调用自身的`toLocaleString`方法,对象调用对象的`toLocaleString`,Date调用Date的`toLocaleString`。
<a name="9bbYZ"></a>
#### toString() 数组转字符串 不推荐
定义: toString() 方法可把数组转换为由逗号链接起来的字符串。<br />语法:
array.toString()
参数: 无。<br />该方法的效果和join方法一样,都是用于数组转字符串的,但是与join方法相比没有优势,也不能自定义字符串的分隔符,因此不推荐使用。<br />**值得注意的是**:当数组和字符串操作的时候,js 会调用这个方法将数组自动转换成字符串
let b= [ ‘toString’,’演示’].toString(); // toString,演示 let a= [‘调用toString’,’连接在我后面’]+’啦啦啦’; // 调用toString,连接在我后面啦啦啦
<a name="RwymH"></a>
#### slice() 浅拷贝数组的元素
定义: 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。<br />**注意**:字符串也有一个slice() 方法是用来提取字符串的,不要弄混了。<br />语法:
array.slice(begin, end);
参数:<br />begin(可选): 索引数值,接受负值,从该索引处开始提取原数组中的元素,默认值为0。<br />end(可选):索引数值(不包括),接受负值,在该索引处前结束提取原数组元素,默认值为数组末尾(包括最后一个元素)。
let a= [‘hello’,’world’]; let b=a.slice(0,1); // [‘hello’] a[0]=’改变原数组’; console.log(a,b); // [‘改变原数组’,’world’] [‘hello’] b[0]=’改变拷贝的数组’; console.log(a,b); // [‘改变原数组’,’world’] [‘改变拷贝的数组’]
如上:新数组是浅拷贝的,**元素是简单数据类型,改变之后不会互相干扰**。<br />如果是**复杂数据类型(对象,数组)的话,改变其中一个,另外一个也会改变**。
let a= [{name:’OBKoro1’}]; let b=a.slice(); console.log(b,a); // [{“name”:”OBKoro1”}] [{“name”:”OBKoro1”}] // a[0].name=’改变原数组’; // console.log(b,a); // [{“name”:”改变原数组”}] [{“name”:”改变原数组”}] // b[0].name=’改变拷贝数组’,b[0].koro=’改变拷贝数组’; // [{“name”:”改变拷贝数组”,”koro”:”改变拷贝数组”}] [{“name”:”改变拷贝数组”,”koro”:”改变拷贝数组”}]
原因在定义上面说过了的:slice()是浅拷贝,对于复杂的数据类型浅拷贝,拷贝的只是指向原数组的指针,所以无论改变原数组,还是浅拷贝的数组,都是改变原数组的数据。
<a name="DSjYz"></a>
#### cancat
定义: 方法用于合并两个或多个数组,返回一个新数组。<br />语法:
var newArr =oldArray.concat(arrayX,arrayX,……,arrayX)
参数:<br />arrayX(必须):该参数可以是具体的值,也可以是数组对象。可以是任意多个。<br />eg1:
let a = [1, 2, 3]; let b = [4, 5, 6]; //连接两个数组 let newVal=a.concat(b); // [1,2,3,4,5,6] // 连接三个数组 let c = [7, 8, 9] let newVal2 = a.concat(b, c); // [1,2,3,4,5,6,7,8,9] // 添加元素 let newVal3 = a.concat(‘添加元素’,b, c,’再加一个’); // [1,2,3,”添加元素”,4,5,6,7,8,9,”再加一个”] // 合并嵌套数组 会浅拷贝嵌套数组 let d = [1,2 ]; let f = [3,[4]]; let newVal4 = d.concat(f); // [1,2,3,[4]]
**ES6扩展运算符`...`合并数组**:<br />因为ES6的语法更简洁易懂,所以现在合并数组我大部分采用`...`来处理,`...`运算符可以实现`cancat`的每个栗子,且更简洁和具有高度自定义数组元素位置的效果。
let a = [2, 3, 4, 5] let b = [ 4,…a, 4, 4] console.log(a,b); // [2, 3, 4, 5] [4,2,3,4,5,4,4]
更多关于扩展符的详细内容移步阮一峰大神的[ECMAScript 6 入门](http://es6.ruanyifeng.com/#docs/array%23%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6)
<a name="QK3vn"></a>
#### indexOf() 查找数组是否存在某个元素,返回下标
定义: 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。<br />语法:
array.indexOf(searchElement,fromIndex)
参数:<br />searchElement(必须):被查找的元素<br />fromIndex(可选):开始查找的位置(不能大于等于数组的长度,返回-1),接受负值,默认值为0。<br />严格相等的搜索:<br />数组的indexOf搜索跟字符串的indexOf不一样,数组的indexOf使用严格相等`===`搜索元素,即**数组元素要完全匹配**才能搜索成功。<br />**注意**:indexOf()不能识别`NaN`<br />eg:
let a=[‘啦啦’,2,4,24,NaN] console.log(a.indexOf(‘啦’)); // -1 console.log(a.indexOf(‘NaN’)); // -1 console.log(a.indexOf(‘啦啦’)); // 0
使用场景:
1. [数组去重](https://juejin.im/post/5aad40e4f265da237f1e12ed#heading-10)
1. 根据获取的数组下标执行操作,改变数组中的值等。
1. 判断是否存在,执行操作。
<a name="ylOWF"></a>
#### lastIndexOf() 查找指定元素在数组中的最后一个位置
定义: 方法返回指定元素,在数组中的最后一个的索引,如果不存在则返回 -1。(从数组后面往前查找)<br />语法:
arr.lastIndexOf(searchElement,fromIndex)
参数:<br />searchElement(必须): 被查找的元素<br />fromIndex(可选): 逆向查找开始位置,默认值数组的长度-1,即查找整个数组。<br />关于fromIndex有三个规则:
1. 正值。如果该值大于或等于数组的长度,则整个数组会被查找。
1. 负值。将其视为从数组末尾向前的偏移。(比如-2,从数组最后第二个元素开始往前查找)
1. 负值。其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
let a=[‘OB’,4,’Koro1’,1,2,’Koro1’,3,4,5,’Koro1’]; // 数组长度为10 // let b=a.lastIndexOf(‘Koro1’,4); // 从下标4开始往前找 返回下标2 // let b=a.lastIndexOf(‘Koro1’,100); // 大于或数组的长度 查找整个数组 返回9 // let b=a.lastIndexOf(‘Koro1’,-11); // -1 数组不会被查找 let b=a.lastIndexOf(‘Koro1’,-9); // 从第二个元素4往前查找,没有找到 返回-1
<a name="kjs5l"></a>
#### ES7 includes() 查找数组是否包含某个元素 返回布尔
定义: 返回一个布尔值,表示某个数组是否包含给定的值<br />语法:
array.includes(searchElement,fromIndex=0)
参数:<br />searchElement(必须):被查找的元素<br />fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。<br />**includes方法是为了弥补indexOf方法的缺陷而出现的:**
1. indexOf方法不能识别`NaN`
1. indexOf方法检查是否包含某个值不够语义化,需要判断是否不等于`-1`,表达不够直观
eg:
let a=[‘OB’,’Koro1’,1,NaN]; // let b=a.includes(NaN); // true 识别NaN // let b=a.includes(‘Koro1’,100); // false 超过数组长度 不搜索 // let b=a.includes(‘Koro1’,-3); // true 从倒数第三个元素开始搜索 // let b=a.includes(‘Koro1’,-100); // true 负值绝对值超过数组长度,搜索整个数组
兼容性(MDN): chrome47, Firefox 43,Edge 14,Opera 34, Safari 9,IE 未实现。
---
<a name="6H1fU"></a>
### 遍历方法(12个):
js中遍历数组并不会改变原始数组的方法总共有12个:
ES5: forEach、every 、some、 fliter、map、reduce、reduceRight、 ES6: find、findIndex、keys、values、entries
<a name="hxqDJ"></a>
#### 关于遍历:
- 关于遍历的效率,可以看一下这篇[详解JS遍历](http://louiszhai.github.io/2015/12/18/traverse/#%E6%B5%8B%E8%AF%95%E5%90%84%E6%96%B9%E6%B3%95%E6%95%88%E7%8E%87)
- 尽量不要在遍历的时候,修改后面要遍历的值
- 尽量不要在遍历的时候修改数组的长度(删除/添加)
<a name="B6Q0f"></a>
#### forEach
定义: 按升序为数组中含有效值的每一项执行一次回调函数。<br />语法:
array.forEach(function(currentValue, index, arr), thisValue)
参数:<br />function(必须): 数组中每个元素需要调用的函数。
// 回调函数的参数
- currentValue(必须),数组当前元素的值
- index(可选), 当前元素的索引值
- arr(可选),数组对象本身
`` thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为
undefined`
关于forEach()你要知道:
- 对于空数组是不会执行回调函数的
- 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
- 遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历。
- 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。
let a = [1, 2, ,3]; // 最后第二个元素是空的,不会遍历(undefined、null会遍历) let obj = { name: ‘OBKoro1’ }; let result = a.forEach(function (value, index, array) { a[3] = ‘改变元素’; a.push(‘添加到尾端,不会被遍历’) console.log(value, ‘forEach传递的第一个参数’); // 分别打印 1 ,2 ,改变元素 console.log(this.name); // OBKoro1 打印三次 this绑定在obj对象上 // break; // break会报错 return value; // return只能结束本次回调 会执行下次回调 console.log(‘不会执行,因为return 会执行下一次循环回调’) }, obj); console.log(result); // 即使return了一个值,也还是返回undefined // 回调函数也接受接头函数写法eg:
array.every(function(currentValue, index, arr), thisValue)<a name="TSpLw"></a>
#### every 检测数组所有元素是否都符合判断条件
定义: 方法用于检测数组所有元素是否都符合函数定义的条件<br />语法:
// 回调函数的参数参数:(这几个方法的参数,语法都类似)<br />function(必须): 数组中每个元素需要调用的函数。
- currentValue(必须),数组当前元素的值
- index(可选), 当前元素的索引值
arr(可选),数组对象本身
`` thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为
undefined`
方法返回值规则:如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
- 如果所有元素都满足条件,则返回 true。=
eg:
function isBigEnough(element, index, array) {
return element >= 10; // 判断数组中的所有元素是否都大于10
}
let result = [12, 5, 8, 130, 44].every(isBigEnough); // false
let result = [12, 54, 18, 130, 44].every(isBigEnough); // true
// 接受箭头函数写法
[12, 5, 8, 130, 44].every(x => x >= 10); // false
[12, 54, 18, 130, 44].every(x => x >= 10); // true
some 数组中的是否有满足判断条件的元素
定义:数组中的是否有满足判断条件的元素
语法:
array.some(function(currentValue, index, arr), thisValue)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数。
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
方法返回值规则:
- 如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回false。
function isBigEnough(element, index, array) {
return (element >= 10); //数组中是否有一个元素大于 10
}
let result = [2, 5, 8, 1, 4].some(isBigEnough); // false
let result = [12, 5, 8, 1, 4].some(isBigEnough); // true
filter 过滤原始数组,返回新数组
定义: 返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。
语法:
参数:(这几个方法的参数,语法都类似)let new_array = arr.filter(function(currentValue, index, arr), thisArg)
function(必须): 数组中每个元素需要调用的函数。 ``` // 回调函数的参数 - currentValue(必须),数组当前元素的值
- index(可选), 当前元素的索引值
- arr(可选),数组对象本身
let a = [32, 33, 16, 40]; let result = a.filter(function (value, index, array) { return value >= 18; // 返回a数组中所有大于18的元素 }); console.log(result,a);// [32,33,40] [32,33,16,40]thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为`undefined`<br />eg:
let new_array = arr.map(function(currentValue, index, arr), thisArg)<a name="1Fo46"></a>
#### map 对数组中的每个元素进行处理,返回新的数组
定义:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。<br />语法:
// 回调函数的参数参数:(这几个方法的参数,语法都类似)<br />function(必须): 数组中每个元素需要调用的函数。
- currentValue(必须),数组当前元素的值
- index(可选), 当前元素的索引值
- arr(可选),数组对象本身
let a = [‘1’,’2’,’3’,’4’]; let result = a.map(function (value, index, array) { return value + ‘新数组的新元素’ }); console.log(result, a); // [“1新数组的新元素”,”2新数组的新元素”,”3新数组的新元素”,”4新数组的新元素”] [“1”,”2”,”3”,”4”]thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为`undefined`<br />eg:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)<a name="aRMJz"></a>
#### reduce 为数组提供累加器,合并为一个值
定义:reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。<br />语法:
// 回调函数的参数参数:<br />function(必须): 数组中每个元素需要调用的函数。
- total(必须),初始值, 或者上一次调用回调返回的值
- currentValue(必须),数组当前元素的值
- index(可选), 当前元素的索引值
- arr(可选),数组对象本身
```
initialValue(可选): 指定第一次回调 的第一个参数。
回调第一次执行时:
- 如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
- 如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
- 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。
eg:
// 数组求和
let sum = [0, 1, 2, 3].reduce(function (a, b) {
return a + b;
}, 0);
// 6
// 将二维数组转化为一维 将数组元素展开
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
(a, b) => a.concat(b),
[]
);
// [0, 1, 2, 3, 4, 5]
reduceRight 从右至左累加
这个方法除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。
ES6:find()& findIndex() 根据条件找到数组成员
find()定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。
findIndex()定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
这两个方法
语法:
let new_array = arr.find(function(currentValue, index, arr), thisArg)
let new_array = arr.findIndex(function(currentValue, index, arr), thisArg)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数。
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
这两个方法都可以识别NaN
,弥补了indexOf
的不足.
eg:
// find
let a = [1, 4, -5, 10].find((n) => n < 0); // 返回元素-5
let b = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n)); // 返回元素NaN
// findIndex
let a = [1, 4, -5, 10].findIndex((n) => n < 0); // 返回索引2
let b = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n)); // 返回索引4
浏览器兼容(MDN):Chrome 45,Firefox 25,Opera 32, Safari 8, Edge yes,
ES6 keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值
定义:三个方法都返回一个新的 Array Iterator 对象,对象根据方法不同包含不同的值。
语法:
array.keys()
array.values()
array.entries()
参数:无。
遍历栗子(摘自ECMAScript 6 入门):
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
在for..of
中如果遍历中途要退出,可以使用break
退出循环。
如果不使用for...of
循环,可以手动调用遍历器对象的next方法,进行遍历:
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
entries()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 7.1
keys()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 8,
注意:目前只有Safari 9支持,,其他浏览器未实现,babel转码器
参考资料:
ECMAScript6 入门 数组的扩展
JavaScript Array数组相关汇总
深入了解javascript的sort方法
【深度长文】JavaScript数组所有API全解密
详解JS遍历
判断一个变量是否为数组
在 JavaScript 中,如何求出两个数组的交集和差集?