lodash里的max

370 阅读3分钟

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

使用说明

lodash里的max方法主要是计算参数中的最大值。 如果参数是空的或者假值将会返回undefined。

参数说明:参数只有一个,需要是数组类型。

_.max(1)
// => undefined

_.max(1,2)
// => undefined

_.max([])
// => undefined

_.max([1])
// => 1

_.max([2,1,3])
// => 3

_.max([1,2,{},4])
// => 4

_.max([10,'a',{},4])
// => 10

_.max([99999,{}])
// => 99999

_.max({})
// => undefined

_.max(['a',{}])
// => a

求指定数组的最大值

对于指定数组,如果我们想要知道求其最大值,那么就需要遍历每一个值,在每次遍历的时候去比较和存储,思路如下:

function getMaxValue(array) {
    if (!Array.isArray(array)) return undefined
    const length = array.length;
    let maxValue = array[0]
    for (let i = 1; i < length; i++) {
        if (maxValue < array[i]) {
            maxValue = array[i]
        }
    }
    return maxValue
}

上述代码实现了求指定数组的最大值,而主要逻辑在于最大值的存储和比较。

源码实现

下面我们来看看lodash里的求最大数的源码实现,首先我们从入口函数分析:

function max(array) {
    return (array && array.length)
        ? baseExtremum(array, identity, baseGt)
        : undefined;
}

上面的代码我们可以看到max方法首先会对参数进行分析,对于参数不存在或者参数存在但是身上没有length属性的情况,直接返回undefined。如果参数存在并且身上存在length属性,则做下一步的处理。

所以我们可以看出,虽然官方文档说参数是array数组的形式,但是我们依旧可以传入字符串:

_.max('hello')
// => 'o'

_.max('1342')
// => '4'

_.max('你好呀')
// => '好'

可见,该方法是可以比较字符串参数的。

回归正题,我们看到如果条件成立,其内部还会调用baseExtremum方法,baseExtremum方法的第一个参数是使用者传入的参数,而identity和baseGt则是传递给baseExtremum使用的方法。

baseExtremum方法字面的意思就是求极值的意思,这里进一步封装成方法就是为了达到复用的目的。

identity方法会返回它接收到的第一个参数,这种写法在于语义化操作,用于获取一个变量的值,源码实现如下:

function identity(value) {
  return value;
}

baseGt主要是比较两个值的大小并返回布尔值,并不强制参数的类型,源码实现如下:

function baseGt(value, other) {
  return value > other;
}

对于参数的遍历,loadsh源码采用while循环,每次索引值增加,对数组进行对比和验证处理。

这里运用了var关键字的变量提升,current最开始存储array[0],即参数第一项的值,然后通过调用传入的对比方法comparator,只有满足current>computed时进入判断里赋值。

在对比的过程中,我们并不确定数组里的每一项都是有效的值,如果某一项是null的话,那将进入不了判断。

value和current本质是同一个东西。只是为了在语义上区分,value保留的是原始的数据源,而current则作为比较去操作的数据。

function baseExtremum(array, iteratee, comparator) {
    var index = -1,
        length = array.length;

    while (++index < length) {
        var value = array[index],
            current = iteratee(value);

        if (current != null && (computed === undefined
            ? (current === current && !isSymbol(current))
            : comparator(current, computed)
        )) {
            var computed = current,
                result = value;
        }
    }
    return result;
}

在第一次执行if语句时,由于变量提升,computed初始值为undefined,所以执行判断current的值是否属于Symbol类型,Symbol类型判断的基础方法实现在之前的篇章已经讲解,这里就不做赘述了。

下面为完整max方法实现。

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 identity(value) {
    return value;
}

function baseGt(value, other) {
    return value > other;
}

function baseExtremum(array, iteratee, comparator) {
    var index = -1,
        length = array.length;

    while (++index < length) {
        var value = array[index],
            current = iteratee(value);

        if (current != null && (computed === undefined
            ? (current === current && !isSymbol(current))
            : comparator(current, computed)
        )) {
            var computed = current,
                result = value;
        }
    }
    return result;
}

function max(array) {
    return (array && array.length)
        ? baseExtremum(array, identity, baseGt)
        : undefined;
}

小结

从源码分析可见lodash对于参数的类型的接收是宽泛的,而在内部处理上又是严谨的。

max方法不仅可以对数组进行操作,还可以对于字符串进行相应的比较操作。

同时lodash也抽离出比较操作方法baseGt,主要是判断第一个参数是否大于第二个参数。