📕 重学JavaScript:如何手写一个`filter`高阶函数?

简介: `filter` 高阶函数是一个非常常用的数组方法,可以让你用一种简单的方法来筛选数组里的元素。

📕 重学JavaScript:如何手写一个filter高阶函数?

嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️

filter 高阶函数是一个非常常用的数组方法,可以让你用一种简单的方法来筛选数组里的元素。

filter可以让你把数组里符合你想要的条件的元素挑出来,放到一个新的数组里。

比如,你可以用filter来找出数组里所有偶数的元素,或者所有大于10的元素,或者所有开头是A的元素等等。😊

在ECMA中,也就是一群很聪明的人制定的一些规则中,他们已经给我们解释了filter方法是怎么工作的。

他们用了一段伪代码,也就是一种类似于真正代码但是不完全一样的东西,来描述filter方法的每一个步骤:

1. Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. If IsCallable(callbackfn) is false, throw a TypeError exception.
4. Let A be ? ArraySpeciesCreate(O, 0).
5. Let k be 0.
6. Let to be 0.
7. Repeat, while k < len,
    a. Let Pk be ! ToString(𝔽(k)).
    b. Let kPresent be ? HasProperty(O, Pk).
    c. If kPresent is true, then
        i. Let kValue be ? Get(O, Pk).
        ii. Let selected be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
        iii. If selected is true, then
            1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue).
            2. Set to to to + 1.
    d. Set k to k + 1.
8. Return A.

好的,根据这个原理,我们可以用JavaScript来实现我们的目标。😎

⭕ 解答一部分实现的关键逻辑

把调用filter函数的值转换成一个对象(ToObject)。

保证 filter 函数可以接受任何类型的值作为参数,比如字符串、数字、布尔等,不会因为用户传入的差异导致报错。

// 假设我们有一个数组arr
let arr = [1, 2, 3, 4];
// 把它转换成一个对象O
let O = Object(arr);

获取对象的长度(LengthOfArrayLike)

确定我们需要遍历对象中的多少个元素

// 获取对象O的所有属性名
let keys = Object.keys(O);
// 获取它们的个数,也就是对象O的长度len
let len = keys.length;

判断传入的回调函数是否是一个函数(IsCallable)

保证我们传入的回调函数是一个有效的函数,而不是其他类型的值

我们使用 typeof 运算符来判断回调函数的类型是否是 "function"

如果不是,我们就抛出一个类型错误

  if (typeof callbackfn !== "function") {
   
    throw new TypeError(callbackfn + " is not a function");
  }

检查对象里有没有Pk这个属性

// 检查一下对象里有没有Pk这个属性
if (O.hasOwnProperty(Pk)) {
   

}

调用回调函数,把kValue、k和对象作为参数传进去

callbackfn.call(thisArg, kValue, k, O)

其他的都是口水话,很简单,这里我就不过多解释了,接下来直接上源码。

💌 完整代码解析如下:

function filter(callbackfn, thisArg) {
   
  // 把数组变成一个对象
  let O = Object(this);
  // 把对象的长度记下来
  let len = Object.keys(O).length;
  // 判断传入的回调函数是否是一个函数,如果不是,就报错
  if (typeof callbackfn !== "function") {
   
    throw new TypeError(`${
     callbackfn} is not a function`);
  }
  // 创建一个新的空数组A,用来存放筛选出来的元素
  let A = [];
  // 把一个变量叫做k设为0
  let k = 0;
  // 把一个变量叫做to设为0
  let to = 0;
  // 重复以下步骤,直到处理完了所有元素
  while (k < len) {
   
    // 把k转换成字符串,并叫做Pk
    let Pk = String(k);
    // 检查一下对象里有没有Pk这个属性,如果有,就表示这个位置有要处理的元素
    if (O.hasOwnProperty(Pk)) {
   
      // 如果有要处理的元素,就做以下事情
      // 把对象里Pk这个属性对应的值赋给一个变量叫做kValue
      let kValue = O[Pk];
      // 调用回调函数,把kValue、k和对象作为参数传进去,并把返回值转换成布尔值,叫做selected
      let selected = Boolean(callbackfn.call(thisArg, kValue, k, O));
      // 如果selected是true,就表示这个元素符合我们想要的条件,就做以下事情
      if (selected) {
   
        // 把kValue放到新数组A里,并给它一个属性名叫做to
        A[to] = kValue;
        // 把to加1
        to++;
      }
    }
    // 把k加1
    k++;
  }
  // 返回新数组A
  return A;
}

这个方法其实没什么难点。

💨 我们试一下是否能实现filter函数的效果

let arr = [1, 2, 3, 4];
let callbackfn = function(a) {
   return a % 2 === 0};
let result = filter.call(arr, callbackfn);
// [2, 4]
console.log(result);

✔ V8引擎中的map源码是怎么实现的呢?

我在这里直接粘贴给大家看看,大家自行对比一下:

// The following functions cannot be made efficient on sparse arrays while
// preserving the semantics, since the calls to the receiver function can add
// or delete elements from the array.
function InnerArrayFilter(f, receiver, array, length, result) {
   
  var result_length = 0;
  for (var i = 0; i < length; i++) {
   
    if (i in array) {
   
      var element = array[i];
      if (%_Call(f, receiver, element, i, array)) {
   
        %CreateDataProperty(result, result_length, element);
        result_length++;
      }
    }
  }
  return result;
}



function ArrayFilter(f, receiver) {
   
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter");

  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = TO_OBJECT(this);
  var length = TO_LENGTH(array.length);
  if (!IS_CALLABLE(f)) throw %make_type_error(kCalledNonCallable, f);
  var result = ArraySpeciesCreate(array, 0);
  return InnerArrayFilter(f, receiver, array, length, result);
}

📕 参考资料

V8数组部分源码第1025行

ecma262草案


🎉 你觉得怎么样?这篇文章可以给你带来帮助吗?如果你有任何疑问或者想进一步讨论相关话题,请随时发表评论分享您的想法,让其他人从中受益。🚀✨

目录
相关文章
|
6天前
|
JavaScript 前端开发
解释 JavaScript 中的`map()`、`filter()`和`reduce()`方法的用途。
解释 JavaScript 中的`map()`、`filter()`和`reduce()`方法的用途。
22 1
|
7月前
|
前端开发 JavaScript
前端基础 - JavaScript高级应用(高阶函数)
前端基础 - JavaScript高级应用(高阶函数)
34 0
|
6天前
|
分布式计算 JavaScript 前端开发
JS中数组22种常用API总结,slice、splice、map、reduce、shift、filter、indexOf......
JS中数组22种常用API总结,slice、splice、map、reduce、shift、filter、indexOf......
|
3天前
|
前端开发 JavaScript 程序员
Javascript:forEach、map、filter、reduce、reduceRight
Javascript:forEach、map、filter、reduce、reduceRight
|
4天前
|
前端开发 JavaScript 容器
早期 JavaScript 中的高阶函数与模块化实现
早期 JavaScript 中的高阶函数与模块化实现
|
6天前
|
JavaScript 前端开发
JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式
【5月更文挑战第11天】JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式。map() 用于创建新数组,其中元素是原数组元素经过指定函数转换后的结果;filter() 则筛选出通过特定条件的元素生成新数组;reduce() 将数组元素累计为单一值。这三个方法使代码更简洁易读,例如:map() 可用于数组元素乘以 2,filter() 用于选取偶数,reduce() 计算数组元素之和。
13 2
|
6天前
|
JavaScript 前端开发
js的filter函数
js的filter函数
12 1
|
6天前
|
JavaScript 前端开发
JavaScript中高阶函数与闭包的实际应用
【4月更文挑战第22天】JavaScript中的高阶函数和闭包是强大的工具,常用于抽象、复用和构建组合逻辑。高阶函数如回调、数组方法和函数组合能提高代码灵活性。闭包则用于封装私有变量、创建函数工厂和在异步编程中保留上下文。两者结合使用,如`createAccumulator`函数,能创建更灵活的代码结构。
|
6天前
|
JavaScript 前端开发 测试技术
JavaScript中的函数式编程:纯函数与高阶函数的概念解析
【4月更文挑战第22天】了解JavaScript中的函数式编程,关键在于纯函数和高阶函数。纯函数有确定输出和无副作用,利于预测、测试和维护。例如,`add(a, b)`函数即为纯函数。高阶函数接受或返回函数,用于抽象、复用和组合,如`map`、`filter`。函数式编程能提升代码可读性、可维护性和测试性,帮助构建高效应用。
|
6天前
|
JavaScript 安全 前端开发
高阶函数(js的问题)
高阶函数(js的问题)
10 0