【源码阅读 | xe-utils源码 | 11】find 函数 - 寻找一个符合条件的值

1. 背景

  在 ES6 规范中,数组类型扩展了 find 函数,先温习一下 find 的使用

  有这么一个需求:在一组表格数据中,找到名字为 "Lisa" 那一行的数据,代码如下:

const tableData = [
  {
    name: 'Jokerls',
    age: 18
  },
  {
    name: 'Lisa',
	age: 20
  }
]

// 返回找到的第一个数据
tableData.find(item => item.name === 'Jokerls') //{ name: 'Jokerls', age: 18 }

  但是一旦提到 ES6,作为一个通用的工具类,必须想到什么?兼容性

  如果你有看过前面几期的源码,不难发现,原本一个简单或者内置已有的功能,为了兼容性问题往往会多写非常多的判断代码。

  本期的 find 就属于这一类,因此我会先用简单的方式实现一下 find 的功能,让你先了解实现思路,再去研究 xe-utils 中对兼容性和封装的处理。

2. 简单实现

1)思路

  • 传入的参数有两个,第一个参数为目标对象(如第一部分代码的 tableData),第二个参数为自定义的过滤函数(如第一部分的 item => item.name === 'xxx'
  • 遍历目标对象,对目标对象执行 过滤函数,符合条件则直接返回当前数据

2)实现

  代码非常简单,有两个点可以注意一下

  • !! 双感叹号:将当前值强转为布尔值,相当于 Boolean(xxx)。一个感叹号代表转为 Boolean 并取反,两个感叹号则能把取反的值再转回来,达到了 Boolean(xxx) 的效果。
  • fn.call(this):因为需要执行一个回调函数,因此需要把 当前作用域指向到 fn 中,避免内部读取的 this 错乱。
function find(arr, fn) {
  let res = null
  for (let i = 0, len = arr.length; i < len; i++) {
    if (!!fn.call(this, arr[i])) {
      res = arr[i]
      break
    }
  }
  return res
}

3. 源码解析

1)find 源码

var helperCreateIterateHandle = require("./helperCreateIterateHandle");

/**
 * 从左至右遍历,匹配最近的一条数据
 *
 * @param {Object} obj 对象/数组
 * @param {Function} iterate(item, index, obj) 回调
 * @param {Object} context 上下文
 * @return {Object}
 */
var find = helperCreateIterateHandle("find", 1, 3, true);
module.exports = find;

2)解析

  可以看到 find 是由一个 helperCreateIterateHandle 函数创建出来的,该函数内部做了什么,而这其中传的参数是什么意思?接着往下看。

  helperCreateIterateHandle 可以用于构造类似如 findevery 等函数,主要做了这三件事,以 find 为例

  • 判断目标对象上,是否存在 find 方法,若有则直接调用;若没有 find 方法,说明当前环境不支持 ES6,需要自行编写 find 逻辑
  • 判断当前方法若支持目标对象为数组,且目标对象为数组的情况下,使用 数组 的方式遍历,并判断是否符合过滤函数的条件(如同第二大点代码中第四行的作用)
  • 其他类型的则通过 for...in 的方式遍历,并逐个判断属性是否符合过滤函数的条件

  带着这三个点,来看下源码的实现,具体内容见注释

// helperCreateIterateHandle.js
var hasOwnProp = require('./hasOwnProp')
var isArray = require('./isArray')

/**
 * @param prop 需要构造的方法,如 'find'
 * @param useArray 是否支持目标对象为数组
 * @param restIndex 用于决定当前方法的返回值类型
 * @param matchValue 需要匹配的值,如 find 函数以 true 作为最终匹配得到的结果
 * @param defaultValue 该方法的默认返回值
 */
function helperCreateIterateHandle(
  prop,
  useArray,
  restIndex,
  matchValue,
  defaultValue
) {
  /**
   * obj 目标对象
   * iterate 过滤函数
   * context 执行环境上下文
   */
  return function (obj, iterate, context) {
    // 是否存在目标对象和过滤函数
    if (obj && iterate) {
      if (prop && obj[prop]) {
        // 1.若当前环境支持该方法,则直接调用并返回,如 find 方法只能在 ES6 中运行
        return obj[prop](iterate, context)
      } else {
        // 2.若目标对象为数组类型,且当前方法支持数组
        if (useArray && isArray(obj)) {
          for (var index = 0, len = obj.length; index < len; index++) {
            if (
              // 是否与目标的匹配值相等
              !!iterate.call(context, obj[index], index, obj) === matchValue
            ) {
              // 返回的数据类型。如find的restIndex为 3,则返回的是 obj[index],即返回匹配到的当前索引对应的值
              return [true, false, index, obj[index]][restIndex]
            }
          }
        } else {
          // 3.若目标对象为对象类型
          for (var key in obj) {
            // 是否为自身的属性
            if (hasOwnProp(obj, key)) {
              // 是否与目标的匹配值相等
              if (!!iterate.call(context, obj[key], key, obj) === matchValue) {
                // 返回的数据类型。如find的restIndex为 3,则返回的是 obj[key],即返回匹配到的当前key对应的值
                return [true, false, key, obj[key]][restIndex]
              }
            }
          }
        }
      }
    }
    // 目标对象或过滤函数不存在,直接返回默认值
    return defaultValue
  }
}

module.exports = helperCreateIterateHandle
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值