lodash里的divide

414 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

方法说明

divide方法为两个数相除。第一个参数为除数,类型为number;第二个参数为被除数,类型为number。

具体使用如下:

_.divide()
// => 1

_.divide(6, 4);
// => 1.5

_.divide('2',2)
// => 1

_.divide('2','2')
// => 1

_.divide('2',[1,2,3])
// => NaN

_.divide([],1)
// => 0

_.divide(()=>{},1)
// => NaN

在前面的篇章《 lodash里的add》 ,我们可以根据add方法实现的原理,借助原生/符号去进行除法,源码内部依旧有容错判断,对于非数字类型有相应的处理。

源码实现

第一步,首先我们像前面篇章的add方法实现一样,先创建工厂函数,对该方法赋予默认值。

const divide = createMathOperation((dividend, divisor) => dividend / divisor, 1)

在这里我们取名和add方法的工厂函数命名一样,取为createMathOperation,取默认值为1。(其实就是对于数字操作单独提炼了一个名为createMathOperation的方法)

第二步,容错处理。我们需要对两个参数进行判断,非数字类型进行相应处理。(这里其实就是和add方法公用的处理逻辑。)

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)
  }
}

这里关键还是调用传入的operator回调函数,因为operator回调函数里便定义了除法操作。

对于有至少一个为字符串类型的,则参数都转为字符串,其余情况将数据转为数字类型。

借由/符号的隐式转换,结果均为数字类型。

对于baseToString方法和baseToNumber方法的实现,在之前实现add方法的篇章中已经讲解其实现,这里就不再赘述了,通过其语义便知其功能。

下面为divide的完整源码:

function isSymbol(value) {
    const toString = Object.prototype.toString
    function getTag(value) {
        if (value == null) {
            return value === undefined ? '[object Undefined]' : '[object Null]'
        }
        return toString.call(value)
    }

    const type = typeof value
    return type == 'symbol' || (type === 'object' && value != null && getTag(value) == '[object Symbol]')
}

function baseToString(value) {
    if (typeof value === 'string') {
        return value
    }
    if (Array.isArray(value)) {
        return `${value.map(baseToString)}`
    }
    if (isSymbol(value)) {
        return symbolToString ? symbolToString.call(value) : ''
    }
    const INFINITY = 1 / 0
    const result = `${value}`
    return (result === '0' && (1 / value) === -INFINITY) ? '-0' : result
}

function baseToNumber(value) {
    if (typeof value === 'number') {
        return value
    }
    if (isSymbol(value)) {
        const NAN = 0 / 0
        return NAN
    }
    return +value
}

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)
    }
}

const divide = createMathOperation((dividend, divisor) => dividend / divisor, 1)

其实单纯实现一个加法或者除法并不需要定义工厂函数,但是在实现后续的乘法,减法中我们会发现都是一样的逻辑判断处理,为了方便代码抽离与封装,以及提高可读性,所以lodash源码抽象出一个数字操作的工厂函数,方便后续代码实现封装。

小结

本篇章主要讲解lodash源码里divide方法的实现,其主要借助/符号进行处理,该方法在原生除法的基础上做了大量的容错处理,由于隐式转换,所以该方法返回的结果都是数字类型。

同时我们意识到对于重复的操作,lodash做了提炼封装,抽离出一个createMathOperation的工厂函数,用于常见的数字操作,主要是对参数的类型进行严谨性判断和验证。

所以在实际项目中,当重复逻辑出现两次及以上时,就可以考虑创建一个工厂函数去处理通用逻辑。