前言
在开发过程中,lodash 算是必不可少的基础库了,而且其模块划分清晰,用来学习源码阅读真是再合适不过了。今天按照顺序从 add 方法开始了解。
add 介绍
_.add(6, 4);
// => 10
它的作用其实就是将两个数值相加。
这个方法比较基础蛤,可能大家在开发过程中也不太会去使用到,但其实它的源码实现也是比较丰富的。
从测试用例看起
lodash 每个方法都有单元测试,我们可以先通过单元测试来大概了解这个方法的功能和处理。
单元测试
// test/add.test.js
describe('add', function() {
it('should add two numbers', function() {
assert.strictEqual(add(6, 4), 10);
assert.strictEqual(add(-6, 4), -2);
assert.strictEqual(add(-6, -4), -10);
});
it('should not coerce arguments to numbers', function() {
assert.strictEqual(add('6', '4'), '64');
assert.strictEqual(add('x', 'y'), 'xy');
});
});
可以看出 add 方法主要给出了两种场景:
- 都是数值
- 不都是数值
这里的处理方式与
JS中的+是一样的。如果是数值则相加,如果非数值则拼接。
源代码
const add = createMathOperation((augend, addend) => augend + addend, 0)
export default add
lodash 代码自注释,所以比较能明确的了解方法含义。
这里通过 createMathOperation 这个通用方法,传入了两个参数。第一个参数是一个函数,可以看出是我们所理解的 add 含义,第二个值是数值。这两个参数含义暂时未知,进入 createMathOperation 方法。
createMathOperation
// .internal/createMathOperation.js
function createMathOperation(operator, defaultValue) {
return (value, other) => {
if (value === undefined && other === undefined) {
return defaultValue
}
if (value !== undefined && other === undefined) {
return value
}
if (other !== undefined && value === undefined) {
return other
}
if (typeof value === 'string' || typeof other === 'string') {
value = baseToString(value)
other = baseToString(other)
}
else {
value = baseToNumber(value)
other = baseToNumber(other)
}
return operator(value, other)
}
}
通过函数的参数名,我们能知道第二个参数是默认值,所以 add 方法的默认值为 0
createMathOperation 方法返回值是一个闭包函数,也有两个参数,它针对传入值做了类型判断,并根据类型做了相应的处理。大概流程如下:
- 参数都为 undefined,则返回默认值
- 参数有一个为 undefined,另一个不为 undefined,则返回非 undefined 值
- 参数中有一个类型为
string,则将两个参数都通过baseToString转换,然后进入步骤 5,否则进入步骤 - 将两个参数都通过
baseToNumber转换,进入步骤 5 - 执行
operator方法并返回结果 这里的通用逻辑处理了参数为 undefined 的情况,所以add方法表现上会与+有一定的区别,这一点是需要注意的。lodash里做string和number转换用到了两个基础库baseToString和baseToNumber
baseToString
.internal/baseToString.js
/** Used as references for various `Number` constants. */
const INFINITY = 1 / 0
/** Used to convert symbols to primitives and strings. */
const symbolToString = Symbol.prototype.toString
function baseToString(value) {
// Exit early for strings to avoid a performance hit in some environments.
if (typeof value === 'string') {
return value
}
if (Array.isArray(value)) {
// Recursively convert values (susceptible to call stack limits).
return `${value.map(baseToString)}`
}
if (isSymbol(value)) {
return symbolToString ? symbolToString.call(value) : ''
}
const result = `${value}`
return (result === '0' && (1 / value) === -INFINITY) ? '-0' : result
}
这个方法主要是将值转换为字符串,这里主要注意类型为数组和默认类型(非 string``array``symbol)的情况。
- 类型为数组:调用 map 方法逐一处理数组内的值,最后通过模板字符串进行强制转换,这里会默认调用
toString方法 - 其他类型:使用模板字符串强制转换为字符串,但这里会额外进行一个判断,即 value 为 -0 的情况,这里也能看出 -0 和 0 转为字符串都是 0。
baseToNumber
// .internal/baseToNumber.js
/** Used as references for various `Number` constants. */
const NAN = 0 / 0
function baseToNumber(value) {
if (typeof value === 'number') {
return value
}
if (isSymbol(value)) {
return NAN
}
return +value
}
转换为数值就很简单了,除开 Symbol 类型,直接调用 +,具体结果可以查看下表。
总结
至此,add 方法的整个执行链路我们都已经走完了。整个过程我们能知道:
- 大部分情况下,
add方法和直接使用+没什么区别,除非参数可能值为undefined Number的NaNPOSITIVE_INFINITY常量可以通过0 / 01 / 0来模拟- 对参数的类型通用处理可以抽象为一个公共方法
不过,这里为什么 lodash 要通过0 / 0 1 / 0 来模拟常量呢?有知道的朋友可以评论告诉我。