lodash里的clamp和inRange

131 阅读2分钟

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

前言

lodash的Number类有三个方法,分别为:clamp、inRange、random。

截屏2022-08-12 下午8.37.32.png

本篇章主要讲解clamp、inRange方法以及inRange方法内部实现借助的toFinite方法。

使用说明

clamp

clamp方法可以返回限制在lowerupper之间的值,实际上就是先对比第一第二个数,去计算最大的那一个值,然后用这个计算了的值再去对比第三个数,取最小的值并返回。

_.clamp(-10, -5, 5);
// => -5
 
_.clamp(10, -5, 5);
// => 5

_.clamp()
// => NaN

_.clamp('3',1,2)
// => 2

_.clamp('a',1,2)
// => NaN

inRange

clamp方法可以检查 n 是否在 start 与 end 之间,但不包括 end。 如果 end 没有指定,那么 start 设置为0。 如果 start 大于 end,那么参数会交换以便支持负范围。

_.inRange(3, 2, 4);
// => true
 
_.inRange(4, 8);
// => true
 
_.inRange(4, 2);
// => false
 
_.inRange(2, 2);
// => false
 
_.inRange(1.2, 2);
// => true
 
_.inRange(5.2, 4);
// => false
 
_.inRange(-3, -2, -6);
// => true

_.inRange()
// => false

_.inRange({})
// => false

手动实现

clamp

对于实现一个clamp方法,我们可以做如下处理:

function clamp(number, lower, upper) {
    number = +number
    lower = +lower
    upper = +upper
    if (typeof number === "number" && number !== NaN) {
        number = number > lower ? number : lower
        number = number < upper ? number : upper
        return number
    }
    return NaN
}

在实现上,我们先对参数进行类型转换,均转换为数字类型,对于NaN的数据我们不做处理直接返回NaN结果。

当参数均为数字时,我们先比较参数一和参数二,二者取最大的那个数进行赋值,然number参数进行存储;其次比较存储赋值之后的计算值number与参数三,二者取最小的结果并进行赋值存储然后返回结果。

inRange

实现inRange方法,我们可以进行如下处理:

  • 先判断end是否为undefined,如果是undefined则将第二个参数start赋值为0,第三个参数end赋值为第二个参数的值。
  • 将所有数值转换为数字类型,如果其中有数字为NaN则返回false。
  • 只有当第一个参数介于第二个参数和第三个参数之间才返回true。
 function inRand(number, start, end) {
    if(end === undefined){
        end = start
        start = 0
    }
    number = +number
    start = +start
    end = +end
    if(number !== number ||start !== start || end !== end) return false
    if(number > start && number < end) return true
    return false   
}

源码实现

clamp

lodash里的clamp方法实现源码里,先对参数进行处理。主要是对非正常的数据进行赋值。

对于第二个参数lower和第三个参数upper的处理如下:

  • 第一步:如果第三个参数不存在,则将第二个参数赋值给第三个参数,然后第二个数赋值为undefined。
  • 第二步:如果第三个参数存在,则将第三个参数转换为数字类型,由于NaN是数字类型,但是NaN不等于NaN,所以源码里通过该特性来处理NaN的情况,如果转换后为NaN则将第三个参数赋值为0。
  • 第三步,重复第二步的设置,只是操作的对象数据是第二个参数。

对于源数据,则调用toNumber方法将其转换为数字类型的数据。

下面为lodash实现clamp方法的源码:

function clamp(number, lower, upper) {
  if (upper === undefined) {
    upper = lower;
    lower = undefined;
  }
  if (upper !== undefined) {
    upper = toNumber(upper);
    upper = upper === upper ? upper : 0;
  }
  if (lower !== undefined) {
    lower = toNumber(lower);
    lower = lower === lower ? lower : 0;
  }
  return baseClamp(toNumber(number), lower, upper);
}

inRange

在讲inRange方法之前需要提到toFinite方法的实现。 toFinite方法是暴露出去的方法,可以将参数转换为一个有限的数字。

_.toFinite(3.2);
// => 3.2

_.toFinite(Number.MIN_VALUE);
// => 5e-324

_.toFinite(Infinity);
// => 1.7976931348623157e+308

_.toFinite('3.2');
// => 3.2

实现如下:

  • 首先对参数进行非0判断,是0的话返回0,不是0则调用toNumber将参数转换为数字类型,然后进行下一步处理。
  • 其次判断参数时正无穷还是负无穷,是正数还是负数,最后返回相应的最大值。
  • 最后进行NaN判断,NaN返回0,正常数据则返回转换后的数字类型结果。
import toNumber from './toNumber.js';

var INFINITY = 1 / 0,
    MAX_INTEGER = 1.7976931348623157e+308;
    
function toFinite(value) {
  if (!value) {
    return value === 0 ? value : 0;
  }
  value = toNumber(value);
  if (value === INFINITY || value === -INFINITY) {
    var sign = (value < 0 ? -1 : 1);
    return sign * MAX_INTEGER;
  }
  return value === value ? value : 0;
}

inRange方法在实现上进行如下处理:

  • 先处理第二个参数,调用toFinit方法,将其转换为有限的数字类型值。
  • 判断第三个参数,如果第三个参数是undefined则将第二个参数赋值为0,第三个参数赋值为第二个参数的值。否则的话则将第三个参数调用toFinite方法转换为有限的数字类型值。
  • 同样的,将第一个参数转换为数字类型。
  • 调用封装的baseInRange方法进行处理,baseInRange内部是对两个数值进行对比判断。

下面为lodash实现inRange方法的源码:

var nativeMax = Math.max, nativeMin = Math.min;
function baseInRange(number, start, end) { 
    return number >= nativeMin(start, end) && number < nativeMax(start, end); 
}

function inRange(number, start, end) {
  start = toFinite(start);
  if (end === undefined) {
    end = start;
    start = 0;
  } else {
    end = toFinite(end);
  }
  number = toNumber(number);
  return baseInRange(number, start, end);
}

小结

本篇章手写了clamp和inRange方法实现,然后通过分析其实现源码,发现内部对多个方法进行封装,其中inRange方法在实现上调用了对外暴露的toFinite方法。

toFinite方法则是对数据进行数字类型转换并限制在有限范围内。