首先我们来复习一下数组原型上这些会对数组进行遍历的操作:
方法 | 返回值 | 作用 |
---|---|---|
forEach | 无返回值 | 遍历数组,对每个元素执行回调函数 |
map | 新数组 | 遍历数组,对每个元素执行回调函数并返回新的元素,组成新的数组 |
filter | 新数组 | 遍历数组,筛选出满足条件的元素,组成新的数组 |
every | 布尔值 | 遍历数组,判断是否所有元素都满足条件 |
some | 布尔值 | 遍历数组,判断是否有至少一个元素满足条件 |
reduce | 累积值 | 遍历数组,对累积值和当前元素执行回调函数,返回最终的累积值 |
他们都接受一个回调函数和一个可选值作为参数:
forEach、map、filter、every、some这五种方法的回调函数都接受三个参数,分别是:
- 当前元素(element):数组中正在处理的当前元素。
- 当前索引(index):数组中正在处理的当前元素的索引。
- 数组(array):调用了该方法的数组。
可选值是 thisArg
,执行 callbackFn
时用作 this
的值,如果没有传入,则回调函数中的 this
值为 undefined
。
reduce方法的回调函数接受四个参数,分别是:
- 累积值(accumulator):累积器累积回调函数的返回值;它是上一次调用回调函数时返回的累积值,或者初始值(如果提供了的话)。
- 当前元素(currentValue):数组中正在处理的当前元素。
- 当前索引(currentIndex):数组中正在处理的当前元素的索引。
- 数组(array):调用了reduce方法的数组。
可选值是 initialValue
,第一次调用回调时初始化 accumulator
的值。如果指定了 initialValue
,则 callbackFn
从数组中的第一个值作为 currentValue
开始执行。如果没有指定 initialValue
,则 accumulator
初始化为数组中的第一个值,并且 callbackFn
从数组中的第二个值作为 currentValue
开始执行。在这种情况下,如果数组为空(没有第一个值可以作为 accumulator
返回),则会抛出错误。
好了,大致上的功能和使用方式就是这样,接下来让我们动手实现他们吧:
forEach
/**
* 使用ES6语法实现数组的forEach方法
* @param {Function} callback - 回调函数
* @param {Object} [thisArg] - 回调函数中的this值
*/
Array.prototype.myForEach = function(callback, thisArg) {
// 判断callback是否为函数
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
// 获取数组长度
let len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 判断数组中该元素是否存在
if (Object.prototype.hasOwnProperty.call(this, i)) {
// 调用回调函数,改变this指向为thisArg
callback.call(thisArg, this[i], i, this);
}
}
}
map
/**
* 使用ES6语法实现数组的map方法
* @param {Function} callback - 回调函数
* @param {Object} [thisArg] - 回调函数中的this值
* @return {Array} - 一个新数组,每个元素都是回调函数的返回值
*/
Array.prototype.myMap = function(callback, thisArg) {
// 判断callback是否为函数
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
// 获取数组长度
let len = this.length;
// 创建新数组
let result = new Array(len);
// 遍历数组
for (let i = 0; i < len; i++) {
// 判断数组中该元素是否存在
if (Object.prototype.hasOwnProperty.call(this, i)) {
// 调用回调函数,改变this指向为thisArg
result[i] = callback.call(thisArg, this[i], i, this);
}
}
// 返回新数组
return result;
}
filter
/**
* 使用ES6语法实现数组的filter方法
* @param {Function} callback - 回调函数
* @param {Object} [thisArg] - 回调函数中的this值
* @return {Array} - 一个新数组,包含所有满足条件的元素
*/
Array.prototype.myFilter = function(callback, thisArg) {
// 判断callback是否为函数
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
// 获取数组长度
let len = this.length;
// 创建新数组
let result = [];
// 遍历数组
for (let i = 0; i < len; i++) {
// 判断数组中该元素是否存在
if (Object.prototype.hasOwnProperty.call(this, i)) {
// 调用回调函数,改变this指向为thisArg
if (callback.call(thisArg, this[i], i, this)) {
result.push(this[i]);
}
}
}
// 返回新数组
return result;
}
every
/**
* 使用ES6语法实现数组的every方法
* @param {Function} callback - 回调函数
* @param {Object} [thisArg] - 回调函数中的this值
* @return {Boolean} - 如果数组中的每个元素都满足回调函数的测试,则返回true,否则返回false
*/
Array.prototype.myEvery = function(callback, thisArg) {
// 判断callback是否为函数
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
// 获取数组长度
let len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 判断数组中该元素是否存在
if (Object.prototype.hasOwnProperty.call(this, i)) {
// 调用回调函数,改变this指向为thisArg
let result = callback.call(thisArg, this[i], i, this);
// 如果回调函数返回false,则返回false
if (!result) {
return false;
}
}
}
// 如果遍历完整个数组都没有返回false,则返回true
return true;
}
some
/**
* 使用ES6语法实现数组的some方法
* @param {Function} callback - 回调函数
* @param {Object} [thisArg] - 回调函数中的this值
* @return {Boolean} - 如果数组中至少有一个元素满足回调函数的测试,则返回true,否则返回false
*/
Array.prototype.mySome = function(callback, thisArg) {
// 判断callback是否为函数
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
// 获取数组长度
let len = this.length;
// 遍历数组
for (let i = 0; i < len; i++) {
// 判断数组中该元素是否存在
if (Object.prototype.hasOwnProperty.call(this, i)) {
// 调用回调函数,改变this指向为thisArg
let result = callback.call(thisArg, this[i], i, this);
// 如果回调函数返回true,则返回true
if (result) {
return true;
}
}
}
// 如果遍历完整个数组都没有返回true,则返回false
return false;
}
reduce
/**
* 使用ES6语法实现数组的reduce方法
* @param {Function} callback - 回调函数
* @param {any} [initialValue] - 初始值
* @return {any} - 函数累计处理的结果
*/
Array.prototype.myReduce = function(callback, initialValue) {
// 判断callback是否为函数
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
// 获取数组长度
let len = this.length;
// 定义累积值和起始索引
let accumulator, startIndex;
// 如果提供了初始值,则累积值为初始值,起始索引为0
if (arguments.length >= 2) {
accumulator = initialValue;
startIndex = 0;
} else {
// 如果没有提供初始值,则累积值为数组中第一个存在的元素,起始索引为1
startIndex = -1;
for (let i = 0; i < len; i++) {
if (Object.prototype.hasOwnProperty.call(this, i)) {
accumulator = this[i];
startIndex = i + 1;
break;
}
}
// 如果数组为空且没有提供初始值,则抛出错误
if (startIndex === -1) {
throw new TypeError('Reduce of empty array with no initial value');
}
}
// 遍历数组
for (let i = startIndex; i < len; i++) {
// 判断数组中该元素是否存在
if (Object.prototype.hasOwnProperty.call(this, i)) {
// 调用回调函数,更新累积值
accumulator = callback(accumulator, this[i], i, this);
}
}
// 返回累积值
return accumulator;
}
思考
其实只要理解了 forEach
的实现,再往后推导实现其他的方法就不难了。值得注意的是,我们在判断数组中第i
位是否存在的时候使用了 Object.prototype.hasOwnProperty.call(this, i)
,这是为什么呢?这是为了避免数组中的空位带来影响:
let arr = [1, , undefined];
console.log(arr[1]); // undefined
console.log(arr[2]); // undefined
let arr = [1, , undefined];
console.log(arr.hasOwnProperty(1)); // false
console.log(arr.hasOwnProperty(2)); // true
如果数组中某一位的元素正好是 undefined
,那么如果直接通过 this[i]
读取他我们是无法判断这一位到底是空位,还是值为 undefined
的元素。举个例子:
let arr = [1, , 3];
let result = arr.map(x => x * 2);
console.log(result); // [2, 空, 6]
而如果我们在 myMap
中不做 if(Object.prototype.hasOwnProperty.call(this, i))
的判断:
let arr = [1, , 3];
let result = arr.myMap(x => x * 2);
console.log(result); // [2, NaN, 6]
可以发现二者对于空位的返回结果是不同的,因为后者没有跳过对 undefined
元素的处理,导致 undefined
值乘以2,得到 NaN
值。原生的方法中,对空位是会跳过处理的,而对值为 undefined
的元素不会,在我们手写实现的时候要注意到这一点。