本篇分析下find
家族的方法,包括findLastIndex
、findLast
、findKey
、findLastKey
,以及这些方法引用到的一些 lodash 方法,包括keys
、isTypedArray
、isBuffer
、keys
、keys
。
其他的 find 方法 正常按官网的文档,lodash
的find
家族应该包括数组array
的findIndex
、findLastIndex
,集合collection
的find
、findLast
和对象object
的findKey
、findLastKey
。
对比现在 fork 的4.17.19 版本的代码 ,固定的打包出来的版本分支中可以看到完整的方法,但是master
分支中却不存在findIndex.js
和find.js
两个文件以及相关的任何代码,这就很令人费解。我专门在 lodash github 的issue 中提问了此问题。开发组给出的解释是,master 分支存放的是 V5 版本的 wip 代码。在打包时使用的是 lodash-cli 库,用来打包成完全体的代码。
前置知识 collection、array 和 object 的区别 ECMA262 规范 可以发现,find
分别为了三类,数组、集合和对象各有自己的正向和反向两个方法。那么在lodash
中,collection
、array
和object
有什么区别?专门翻了 ECMA262 规范,翻译下与collection
相关的定义部分。
ECMAScript 是基于对象的:基本语言和工具由对象提供,ECMAScript 程序是一个通信对象簇。在 ECMAScript 中,对象(object)是由零个或多个属性(property)组成的集合(collection ),每个属性都具有决定如何使用自身的属性(attribute)ーー例如,当 property 的 Writable attribute 设置为 false 时,任何已执行 ECMAScript 代码为 property 分配其他值的尝试都将失败。属性(property)是容纳其他对象(object)、原始值(primitive values)或函数(function)的容器。原始值是下列内置类型之一的成员:Undefined , Null , Boolean , Number , BigInt , String 和 Symbol ; 对象是内置类型 Object 的成员; 函数是可调用对象。通过属性(property)与对象(object)关联的函数称为方法(method)。
ECMAScript 定义了一组内置对象(_built-in objects_),这些对象完善了 ECMAScript 实体 的定义。这些内置对象包括global
对象; 还包括对运行时语义 有重要意义的对象,包括 Object
, Function
, Boolean
, Symbol
和各种 Error
对象; 表示和操作数值(包括 Math
, Number
和 Date
)的对象; 文本处理对象 String
和 RegExp
; 包括 Array
和 9 种不同种类的类型化数组(Typed Array)(其元素都由具有特定的数值数据表示)的索引集合对象; 包括 Map
和 set
对象的键集合;支持结构化数据(包括 JSON
对象、 ArrayBuffer
、 sharedarbuffer
和 DataView
)的对象;支持控制抽象——包括生成器函数(generator
)和期约(promise
) 对象——的对象; 以及包括 Proxy
和 Reflect
对象在内的反射(reflection
)对象。
根据 ECMA262 的解释,简单来说所有的非原始类型的值都是 collection,同时指出了对象是符合任意多个属性组成
的集合,数组是是由连续数字索引做键
的对象,也就是collection ⊇ object ⊇ array
underscore 接下来再看看,lodash 的爸爸underscore 是怎么区分collection
的:
Note: Collection functions work on arrays, objects, and array-like objects such as arguments
, NodeList
and similar. But it works by duck-typing, so avoid passing objects with a numeric length
property. It’s also good to note that an each
loop cannot be broken out of — to break, use **\ .find** instead._
注意: 集合(collection)的函数可以应用到数组、对象、类数组对象(比如arguments
, NodeList
*和其他类似)。但是函数执行的原理是 duck-typing 的,所以应该避免传递一个具有数字格式 length 属性的对象。值得注意的是,each
循环不能被打断——想要打断,应使用_.find
方法代替。
总体来说还是符合规范的。
Object()方法 在本篇的代码中,使用了很多次Object(collection)
方法强制转换为对象,不加new
而单独使用Object()
方法的原理 如下:
Object
构造函数将给定的值包装为一个新对象。
如果给定的值是 null
或 undefined
, 它会创建并返回一个空对象。
否则,它将返回一个和给定的值相对应的类型的对象。
如果给定值是一个已经存在的对象,则会返回这个已经存在的值(相同地址)。
在非构造函数上下文中调用时, Object
和 new Object()
表现一致。
所以在非构造函数的上下文中执行Object(collection)
时,其实是返回collection
强制转化后对应类型的对象。
global 在之前的浏览器和 node.js 环境中,对于全局global
的定义是不同的。最近的版本中,在 web 端全局环境下globalThis
、self
和this
是相同的,都指向window
;在node
全局环境下,globalThis
和this
是相同的,都指向global
;
所以说在之后的规范中,是可以用globalThis
在两个环境中通用的指向全局。
lodash 中类对象、类数组、类数组对象的定义 类对象 类对象(objectLike
)的判断:
value
不为null
或undefined
。
typeof value
返回值为 object
。
也就是说function
、array
和object
都算类对象
类数组 类数组(arrayLike
)主要是满足两个条件:
value
不为null
或undefined
。
typeOf value !== 'function'
,也就是说可以为object
、boolean
、number
、bigint
、string
、symbol
。
带有一个正确的数字类型的length
属性。
类数组对象 类数组对象(arrayLikeObject
)相当于在类数组的基础上添加一个条件:value
必须为类对象。
换句话说,类数组对象就是typeof value
返回值必须为 object
且带有一个正确的数字类型的length
属性。
引用的内置方法 baseFindKey eachFunc
方法其实是自己选的迭代方法,用来决定使用什么样的方式来迭代collection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function baseFindKey (collection, predicate, eachFunc ) { let result; eachFunc(collection, (value, key, collection) => { if (predicate(value, key, collection)) { result = key; return false ; } }); return result; } export default baseFindKey;
root 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import freeGlobal from './freeGlobal.js' ;const freeGlobalThis = typeof globalThis === 'object' && globalThis !== null && globalThis.Object == Object && globalThis; const freeSelf = typeof self === 'object' && self !== null && self.Object === Object && self; const root = freeGlobalThis || freeGlobal || freeSelf || Function ('return this' )(); export default root;
isIndex 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const MAX_SAFE_INTEGER = 9007199254740991 ;const reIsUint = /^(?:0|[1-9]\d*)$/ ;function isIndex (value, length ) { const type = typeof value; length = length == null ? MAX_SAFE_INTEGER : length; return ( !!length && (type === 'number' || (type !== 'symbol' && reIsUint.test(value))) && value > -1 && value % 1 == 0 && value < length ); } export default isIndex;
freeGlobal 1 2 3 4 5 6 7 8 9 const freeGlobal = typeof global === 'object' && global !== null && global.Object === Object && global; export default freeGlobal;
nodeTypes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import freeGlobal from './freeGlobal.js' ;const freeExports = typeof exports === 'object' && exports !== null && !exports.nodeType && exports; const freeModule = freeExports && typeof module === 'object' && module !== null && !module .nodeType && module ; const moduleExports = freeModule && freeModule.exports === freeExports;const freeProcess = moduleExports && freeGlobal.process;const nodeTypes = (( ) => { try { const typesHelper = freeModule && freeModule.require && freeModule.require('util' ).types; return typesHelper ? typesHelper : freeProcess && freeProcess.binding && freeProcess.binding('util' ); } catch (e) {} })(); export default nodeTypes;
getTag 获取目标的标签,之前分析过
arrayLikeKeys 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import isArguments from '../isArguments.js' ;import isBuffer from '../isBuffer.js' ;import isIndex from './isIndex.js' ;import isTypedArray from '../isTypedArray.js' ;const hasOwnProperty = Object .prototype.hasOwnProperty;function arrayLikeKeys (value, inherited ) { const isArr = Array .isArray(value); const isArg = !isArr && isArguments(value); const isBuff = !isArr && !isArg && isBuffer(value); const isType = !isArr && !isArg && !isBuff && isTypedArray(value); const skipIndexes = isArr || isArg || isBuff || isType; const length = value.length; const result = new Array (skipIndexes ? length : 0 ); let index = skipIndexes ? -1 : length; while (++index < length) { result[index] = `${index} ` ; } for (const key in value) { if ( (inherited || hasOwnProperty.call(value, key)) && !( skipIndexes && (key === 'length' || isIndex(key, length)) ) ) { result.push(key); } } return result; } export default arrayLikeKeys;
baseForOwnRight 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import baseForRight from './baseForRight.js' ;import keys from '../keys.js' ;function baseForOwnRight (object, iteratee ) { return object && baseForRight(object, iteratee, keys); } export default baseForOwnRight;
引用的 lodash 方法 isObject、isObjectLike、isArrayLike 之前文章分析过
isTypedArray 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import getTag from './.internal/getTag.js' ;import nodeTypes from './.internal/nodeTypes.js' ;import isObjectLike from './isObjectLike.js' ;const reTypedTag = /^\[object (?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)Array\]$/ ;const nodeIsTypedArray = nodeTypes && nodeTypes.isTypedArray;const isTypedArray = nodeIsTypedArray ? (value) => nodeIsTypedArray(value) : (value) => isObjectLike(value) && reTypedTag.test(getTag(value)); export default isTypedArray;
keys 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import arrayLikeKeys from './.internal/arrayLikeKeys.js' ;import isArrayLike from './isArrayLike.js' ;function keys (object ) { return isArrayLike(object) ? arrayLikeKeys(object) : Object .keys(Object (object)); } export default keys;
find 家族 findLastIndex 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import baseFindIndex from './.internal/baseFindIndex.js' ;import toInteger from './toInteger.js' ;function findLastIndex (array, predicate, fromIndex ) { const length = array == null ? 0 : array.length; if (!length) { return -1 ; } let index = length - 1 ; if (fromIndex !== undefined ) { index = toInteger(fromIndex); index = fromIndex < 0 ? Math .max(length + index, 0 ) : Math .min(index, length - 1 ); } return baseFindIndex(array, predicate, index, true ); } export default findLastIndex;
findLast 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import findLastIndex from './findLastIndex.js' ;import isArrayLike from './isArrayLike.js' ;function findLast (collection, predicate, fromIndex ) { let iteratee; const iterable = Object (collection); if (!isArrayLike(collection)) { collection = Object .keys(collection); iteratee = predicate; predicate = (key ) => iteratee(iterable[key], key, iterable); } const index = findLastIndex(collection, predicate, fromIndex); return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined ; } export default findLast;
findKey 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function findKey (object, predicate ) { let result; if (object == null ) { return result; } Object .keys(object).some((key ) => { const value = object[key]; if (predicate(value, key, object)) { result = key; return true ; } }); return result; } export default findKey;
findLastKey 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import baseFindKey from './.internal/baseFindKey.js' ;import baseForOwnRight from './.internal/baseForOwnRight.js' ;function findLastKey (object, predicate ) { return baseFindKey(object, predicate, baseForOwnRight); } export default findLastKey;