lodash 源码阅读计划——add

522 阅读4分钟

前言

在开发过程中,lodash 算是必不可少的基础库了,而且其模块划分清晰,用来学习源码阅读真是再合适不过了。今天按照顺序从 add 方法开始了解。

add 介绍

官方文档

_.add(64);
// => 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 方法返回值是一个闭包函数,也有两个参数,它针对传入值做了类型判断,并根据类型做了相应的处理。大概流程如下:

  1. 参数都为 undefined,则返回默认值
  2. 参数有一个为 undefined,另一个不为 undefined,则返回非 undefined 值
  3. 参数中有一个类型为 string,则将两个参数都通过 baseToString 转换,然后进入步骤 5,否则进入步骤
  4. 将两个参数都通过 baseToNumber 转换,进入步骤 5
  5. 执行 operator 方法并返回结果 这里的通用逻辑处理了参数为 undefined 的情况,所以 add 方法表现上会与 + 有一定的区别,这一点是需要注意的。 lodash 里做 stringnumber 转换用到了两个基础库 baseToStringbaseToNumber

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 类型,直接调用 +,具体结果可以查看下表。

image.png

总结

至此,add 方法的整个执行链路我们都已经走完了。整个过程我们能知道:

  • 大部分情况下,add 方法和直接使用 + 没什么区别,除非参数可能值为 undefined
  • NumberNaN POSITIVE_INFINITY 常量可以通过 0 / 0 1 / 0 来模拟
  • 对参数的类型通用处理可以抽象为一个公共方法

不过,这里为什么 lodash 要通过0 / 0 1 / 0 来模拟常量呢?有知道的朋友可以评论告诉我。