【问题标题】:What's the most elegant way to cap a number to a segment?将数字限制为段的最优雅的方法是什么?
【发布时间】:2012-07-09 17:27:52
【问题描述】:

假设xab 是数字。我需要将x 限制在[a, b] 段的范围内。

换句话说,我需要一个clamp function

clamp(x) = max( a, min(x, b) )

谁能想出一个更易读的版本?

【问题讨论】:

    标签: javascript clamp


    【解决方案1】:

    你做这件事的方式很标准。你可以定义一个实用程序clamp函数:

    /**
     * Returns a number whose value is limited to the given range.
     *
     * Example: limit the output of this computation to between 0 and 255
     * (x * 255).clamp(0, 255)
     *
     * @param {Number} min The lower boundary of the output range
     * @param {Number} max The upper boundary of the output range
     * @returns A number in the range [min, max]
     * @type Number
     */
    Number.prototype.clamp = function(min, max) {
      return Math.min(Math.max(this, min), max);
    };
    

    (虽然扩展语言内置插件通常不受欢迎)

    【讨论】:

    • 扩展原生原型(在这种情况下为Number.prototype)是不明智的,原因有很多。请参阅developer.mozilla.org/en-US/docs/Web/JavaScript/… 上的“不良做法:扩展原生原型”
    • 如果不受欢迎,为什么要推荐它...?
    • JavaScript 本身应该包含很多这些函数。开发人员不应该因为语言的松懈而皱眉。
    • 开发人员不会因为“弥补语言的不足”而皱眉,而是因为以错误或草率的方式做事。扩展原型is wrong and should be avoided for a variety of good reasons,尤其是当正确的方法与使用函数一样简单时。 const clamp = (num, min, max) => Math.min(Math.max(num, min), max).
    • 应该扩展 Math 而不是 Number
    【解决方案2】:

    一种较少“数学”导向的方法,但也应该有效,这样,< / > 测试就暴露出来了(可能比最小化更容易理解),但这真的取决于你所说的“可读”是什么意思

    function clamp(num, min, max) {
      return num <= min 
        ? min 
        : num >= max 
          ? max 
          : num
    }
    

    【讨论】:

    • 最好在这个习语中用 = 替换 ,这样可能会少做一次比较。有了这个更正,这绝对是我的首选答案:分钟的最大值令人困惑且容易出错。
    • 不是更快。 Math.min/max 解最快jsperf.com/math-clamp/9
    • @ManuelDiIorio 嗯?在当前版本的 Chrome 下,简单的三元组速度是 Math.min/max 的两倍 - 如果你从更昂贵的 Math.random() 调用中移除开销,这个简单的方法是 10 x faster
    • 这在参数为 min > max 的意外情况下具有意外行为
    【解决方案3】:

    有一个建议是在内置 Math 对象中添加一个附加项来执行此操作:

    Math.clamp(x, lower, upper)
    

    但请注意,截至今天,它是第 1 阶段 proposal。在它得到广泛支持(不能保证)之前,您可以使用polyfill

    【讨论】:

    【解决方案4】:

    一个简单的方法是使用

    Math.max(min, Math.min(number, max));
    

    你显然可以定义一个包装这个的函数:

    function clamp(number, min, max) {
      return Math.max(min, Math.min(number, max));
    }
    

    最初这个答案也将上述函数添加到全局 Math 对象中,但这是过去时代的遗物,因此已被删除(感谢 @Aurelio 的建议)

    【讨论】:

    • 这个解决方案实际上要快得多,虽然我认为在实际使用该功能时扩展 prototype 非常优雅。
    • 这是 2012 年的答案,所以看到扩展原型的实现在某种程度上是可以理解的,但必须注意它现在广泛使用 considered a bad practiceMath.max(min, Math.min(number, max))(相关位)仍然有效并且值得一票,所以@CAFxX 你会考虑编辑答案并避免原型突变吗?我认为你会得到一些赞成票被转移到this answer
    【解决方案5】:

    这不想成为“仅使用库”的答案,但以防万一您使用 Lodash,您可以使用 .clamp

    _.clamp(yourInput, lowerBound, upperBound);
    

    这样:

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

    这是它的实现,取自 Lodash source

    /**
     * The base implementation of `_.clamp` which doesn't coerce arguments.
     *
     * @private
     * @param {number} number The number to clamp.
     * @param {number} [lower] The lower bound.
     * @param {number} upper The upper bound.
     * @returns {number} Returns the clamped number.
     */
    function baseClamp(number, lower, upper) {
      if (number === number) {
        if (upper !== undefined) {
          number = number <= upper ? number : upper;
        }
        if (lower !== undefined) {
          number = number >= lower ? number : lower;
        }
      }
      return number;
    }
    

    此外,值得注意的是,Lodash 将单个方法作为独立模块提供,因此如果您只需要此方法,则可以在没有库的其余部分的情况下安装它:

    npm i --save lodash.clamp
    

    【讨论】:

    • 比较number === number的目的是什么?当然它总是评估为true?
    • @JivanPal JS 中number === number 为假的唯一值是NaN,这是您在编写数字逻辑时通常想要处理的情况。 Object.is(number, number) 总是返回 true。
    • @Rafi,谢谢 — 与其他语言相比,JavaScript 的特性总是让我惊叹。
    【解决方案6】:

    如果你能够使用 es6 箭头函数,你也可以使用部分应用方法:

    const clamp = (min, max) => (value) =>
        value < min ? min : value > max ? max : value;
    
    clamp(2, 9)(8); // 8
    clamp(2, 9)(1); // 2
    clamp(2, 9)(10); // 9
    
    or
    
    const clamp2to9 = clamp(2, 9);
    clamp2to9(8); // 8
    clamp2to9(1); // 2
    clamp2to9(10); // 9
    

    【讨论】:

    • 每次想要钳位数字时创建一个函数看起来效率很低
    • 可能取决于您的用例。这只是创建具有设定范围的钳位的工厂功能。您可以创建一次并在整个应用程序中重复使用。不必多次调用。也可能不是您不需要锁定在设定范围内的所有用例的工具。
    • @George 如果你不需要保留范围,只需移除内箭头函数并将值参数移动到外箭头函数
    【解决方案7】:

    如果你不想定义任何函数,写成Math.min(Math.max(x, a), b) 也不错。

    【讨论】:

    • 用户@CAFxX(上图)比你早 6 年回答了同样的问题
    【解决方案8】:

    这将三元选项扩展为 if/else,它缩小相当于三元选项,但不会牺牲可读性。

    const clamp = (value, min, max) => {
      if (value < min) return min;
      if (value > max) return max;
      return value;
    }
    

    缩小到 35b(如果使用 function,则缩小到 43b):

    const clamp=(c,a,l)=>c<a?a:c>l?l:c;
    

    此外,根据您使用的性能工具或浏览器,您会获得基于数学的实现还是基于三元的实现更快的各种结果。在性能大致相同的情况下,我会选择可读性。

    【讨论】:

      【解决方案9】:

      本着箭头性感的精神,你可以创造一个微 钳位/约束/门/&c。使用剩余参数的函数

      var clamp = (...v) => v.sort((a,b) => a-b)[1];
      

      然后传入三个值

      clamp(100,-3,someVar);
      

      同样,如果说性感,你的意思是“短”

      【讨论】:

      • 你不应该对clamp使用数组和排序,如果你有复杂的逻辑(即使它很“性感”),性能会受到严重打击
      【解决方案10】:

      我最喜欢的:

      [min,x,max].sort()[1]
      

      【讨论】:

      • 投反对票,因为它不起作用。 [1,5,19].sort()[1] 返回 19。可以这样修复:[min,x,max].sort(function(a, b) { return a - b; })[1] 但这不再性感了。除此之外,在大量使用时,创建一个新数组只是为了比较三个数字可能是一个性能问题。
      • 恕我直言,这比任何其他选项都更具可读性,尤其是箭头函数:[min, x, max].sort((a,b) =&gt; a-b)[1]
      • 这并不总是有效的原因是因为排序方法不能正确比较数值。 (它将它们视为字符串)
      • @JohnLeuenhagen 现在怎么了?!?
      • @SreenikethanI 是的,不幸的是,JavaScript 有很多像这样的讨厌的“陷阱”。 JavaScript 程序员只需要了解它们并解决它们。
      猜你喜欢
      • 1970-01-01
      • 2017-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-08
      • 2023-03-05
      • 2010-09-26
      • 2010-09-16
      相关资源
      最近更新 更多