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
可以用于构造类似如 find
、every
等函数,主要做了这三件事,以 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