A modern JavaScript utility library delivering modularity, performance & extras.
lodash
是一个一致性、模块化、高性能的JavaScript
实用工具库
# 一、环境准备
lodash
版本v4.0.0
通过
github1s
网页可以 查看 (opens new window)lodash - difference
源码调试测试用例可以
clone
到本地
git clone https://github.com/lodash/lodash.git
cd axios
npm install
npm run test
# 二、结构分析
这是一张 difference
依赖引用路径图,相对复杂一些,按照功能划分,大致包括cache模块、index模块和flatten模块。接下来会自底向上分析各个依赖模块。由于依赖较多,篇幅较长,将按照模块分成四个部分,本篇主要讲述 flatten
模块,包含 getTag
、isObjectLike
、isArguments
、isFlattenable
、baseFlatten
。
# 三、函数研读
# 1. internal/getTag 模块
const toString = Object.prototype.toString;
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function getTag(value) {
if (value == null) {
return value === undefined ? "[object Undefined]" : "[object Null]";
}
return toString.call(value);
}
export default getTag;
- 使用非严格等
==
无法判断value
是null
orundefined
- 使用严格等
===
判断value
是null
orundefined
并设定 toStringTag (opens new window)(准确的说应该是Symbol.toStringTag
) - 如果
null
orundefined
直接使用Object
原型链函数toString()
获取toStringTag
Tips:许多内置的 JavaScript
对象类型即便没有 toStringTag
属性,也能被 toString()
方法识别并返回特定的类型标签,比如:Object.prototype.toString.call([1, 2]); // "[object Array]"
,但是有些对象类型则不然,toString()
方法能识别它们是因为引擎
为它们设置好了 toStringTag
标签,比如:Object.prototype.toString.call(new Map()); // "[object Map]"
# 2. isObjectLike 模块
检查“value”是否与对象类似,如果不为空则是一个对象,并且会有一个“typeof”运算结果为“object”返回值
/**
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* isObjectLike({})
* // => true
*
* isObjectLike([1, 2, 3])
* // => true
*
* isObjectLike(Function)
* // => false
*
* isObjectLike(null)
* // => false
*/
function isObjectLike(value) {
return typeof value === 'object' && value !== null
}
export default isObjectLike
- 可以通过
typeof
来获取未经计算的操作数
的类型,下面是一个typeof
运算结果集
类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
BigInt(ECMAScript 2020 新增) | "bigint" |
String | "string" |
Symbol (ECMAScript 2015 新增) | "symbol" |
宿主对象(由 JS 环境提供) | 取决于具体实现 |
Function 对象 (按照 ECMA-262 规范实现 [[Call]]) | "function" |
其他任何对象 | "object" |
# 3. isArguments 模块
检查'value'是否与'arguments'对象类似
import getTag from './.internal/getTag.js'
import isObjectLike from './isObjectLike.js'
/**
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object, else `false`.
* @example
*
* isArguments(function() { return arguments }())
* // => true
*
* isArguments([1, 2, 3])
* // => false
*/
function isArguments(value) {
return isObjectLike(value) && getTag(value) == '[object Arguments]'
}
export default isArguments
arguments
对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments
对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0
处。- 需要注意的是,
arguments
对象不是一个Array
,它类似于Array
,但除了length
属性和索引元素之外没有任何Array
属性。例如,它没有pop
方法。 arguments
对象只能在函数内使用,对其使用Object.prototype.toString.call(arguments)
运算的返回值是[object Arguments]
# 4. isFlattenable 模块
检查'value'是否为可展平的'arguments'对象或数组
import isArguments from '../isArguments.js'
/** Built-in value reference. */
const spreadableSymbol = Symbol.isConcatSpreadable
/**
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
*/
function isFlattenable(value) {
return Array.isArray(value) || isArguments(value) ||
!!(value && value[spreadableSymbol])
}
export default isFlattenable
- 重点关注
value[spreadableSymbol]
,在这之前我们需要知道Array
的concat
运算,正常情况下['a', 'b', 'c'].concat([1, 2, 3]) = ["a", "b", "c", 1, 2, 3]
,但可通过设定被连接array
,array[Symbol.isConcatSpreadable] = false;
,使得array
不被展开到发起连接的array
而是作为一个元素连接到其中,如['a', 'b', 'c'].concat([1, 2, 3]) = ["a", "b", "c", [ 1, 2, 3] ]
- 前文介绍过
!!
运算符表示逻辑非的取反运算,如!!obj
与obj != null && typeof obj === undefined && obj != "" && obj != false
在计算上等价
# 5. baseFlatten 模块
扁平化”的基本实现,支持限制扁平化
import isFlattenable from './isFlattenable.js'
/**
* @private
* @param {Array} array The array to flatten.
* @param {number} depth 最大递归深度
* @param {boolean} [predicate=isFlattenable] 每次迭代调用的函数
* @param {boolean} [isStrict] 限制为通过“谓词”检查的值
* @param {Array} [result=[]] 初始结果值
* @returns {Array} 返回新的展平数组
*/
function baseFlatten(array, depth, predicate, isStrict, result) {
predicate || (predicate = isFlattenable)
result || (result = [])
if (array == null) {
return result
}
for (const value of array) {
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// 递归展平阵列(易受调用堆栈限制的影响)
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
result.push(...value)
}
} else if (!isStrict) {
result[result.length] = value
}
}
return result
}
export default baseFlatten
- 如果待展平数组
array
是null
,直接返回result
(result=[]) - 使用
for...of
迭代待展平array
中的每一项,如果最大递归深度depth
仍然未减至1
则递归调用baseFlatten
,每次depth - 1
,直至depth = 1
将返回值放入result
。 depth = 1
时由于所有项都已展平predicate(value)
返回false
,进入else if (!isStrict)
语句块,目的是限制“谓词”展平到result
,这里我们就需要了解谓词的概念了- 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。通俗的说就是一个函数,会返回一个符合该条件(“truthy值”)的数组🐶