【问题标题】:How can I compare software version number using JavaScript? (only numbers)如何使用 JavaScript 比较软件版本号? (只有数字)
【发布时间】:2022-11-14 18:30:25
【问题描述】:

这是软件版本号:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

我怎么能比较这个?

假设正确的顺序是:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

这个想法很简单......: 读取第一个数字,然后是第二个,然后是第三个...... 但我无法将版本号转换为浮点数... 您还可以看到这样的版本号:

"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"

这更清楚地看到背后的想法是什么...... 但是,我怎样才能将它转换成计算机程序呢?

【问题讨论】:

  • 这将是一个很好的 fizzbuzz 型面试问题。
  • 这就是为什么所有软件版本号都应该是整数,如 2001403。当你想以某种友好的方式显示它时,如“2.0.14.3”,然后在演示时格式化版本号。
  • 这里的一般问题是语义版本比较,这是非常重要的(参见semver.org 的#11)。幸运的是,有一个官方库,semantic versioner for npm
  • 找到了一个比较semvers的simple script
  • @jarmod 所以你有2001403,是2.0.14.3还是20.1.4.3还是2.0.1.43?如果没有缺陷,这种方法是有限制的。

标签: javascript algorithm sorting


【解决方案1】:

semver

npm 使用的 semantic version 解析器。

$ npm install semver
var semver = require('semver');

semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false

semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true

var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')

语义版本控制链接:
https://www.npmjs.com/package/semver#prerelease-identifiers

【讨论】:

  • 是的。这个是正确的答案 - 比较版本非常重要(请参阅semver.org 的#11),并且有生产级库可以完成这项工作。
  • 从技术上讲,这不是正确的答案,因为 node.js 和 javascript 是不同的。我认为最初的问题更多是针对浏览器的。但是谷歌把我带到这里,幸运的是我正在使用节点 :)
  • NodeJS 不仅仅是服务器端解决方案。 Electron 框架为桌面应用程序嵌入了一个 nodeJS。这实际上是我一直在寻找的答案。
  • semver 是一个 npm 包,可以在任何 JS 环境下使用!这是正确的答案
  • @artuska 好吧,然后简单地去寻找另一个包,比如 semver-compare - 233B(小于 0.5kB!)gzipped :)
【解决方案2】:

进行这种比较的基本思想是使用Array.split从输入字符串中获取零件数组,然后比较两个数组中的零件对;如果部分不相等,我们知道哪个版本更小。

需要记住一些重要的细节:

  1. 应该如何比较每对中的零件?这个问题想要比较数字,但是如果我们有不由数字组成的版本字符串(例如“1.0a”)怎么办?
  2. 如果一个版本字符串的部分多于另一个,会发生什么情况?很可能“1.0”应该被认为小于“1.0.1”,但是“1.0.0”呢?

    这是您可以直接使用的实现代码 (gist with documentation):

    function versionCompare(v1, v2, options) {
        var lexicographical = options && options.lexicographical,
            zeroExtend = options && options.zeroExtend,
            v1parts = v1.split('.'),
            v2parts = v2.split('.');
    
        function isValidPart(x) {
            return (lexicographical ? /^d+[A-Za-z]*$/ : /^d+$/).test(x);
        }
    
        if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
            return NaN;
        }
    
        if (zeroExtend) {
            while (v1parts.length < v2parts.length) v1parts.push("0");
            while (v2parts.length < v1parts.length) v2parts.push("0");
        }
    
        if (!lexicographical) {
            v1parts = v1parts.map(Number);
            v2parts = v2parts.map(Number);
        }
    
        for (var i = 0; i < v1parts.length; ++i) {
            if (v2parts.length == i) {
                return 1;
            }
    
            if (v1parts[i] == v2parts[i]) {
                continue;
            }
            else if (v1parts[i] > v2parts[i]) {
                return 1;
            }
            else {
                return -1;
            }
        }
    
        if (v1parts.length != v2parts.length) {
            return -1;
        }
    
        return 0;
    }
    

    此版本比较部分naturally,不接受字符后缀并认为“1.7”小于“1.7.0”。可以将比较模式更改为字典顺序,并且可以使用可选的第三个参数自动对较短版本的字符串进行零填充。

    有一个运行“单元测试”的 JSFiddle here;它是 ripper234's work 的稍微扩展版本(谢谢)。

    重要的提示:此代码使用Array.mapArray.every,这意味着它不会在 IE 9 之前的版本中运行。如果您需要支持它们,则必须为缺少的方法提供 polyfill。

【讨论】:

  • 这是带有一些单元测试的改进版本:jsfiddle.net/ripper234/Xv9WL/28
  • 例如,如果我们将“11.1.2”与“3.1.2”进行比较,您的算法将无法正常工作。在比较它们之前,您应该将字符串转换为整数。请解决这个问题 ;)
  • 大家好,我已经将这个要点与测试和所有内容整合到一个 gitrepo 中,并将它放在 npm 和 bower 上,这样我就可以更轻松地将它包含在我的项目中。 github.com/gabe0x02/version_compare
  • @GabrielLittman:嘿,感谢您抽出时间来做这件事!但是,默认情况下,SO 上的所有代码都使用 CC-BY-SA 许可。这意味着您不能让您的包获得 GPL 许可。我知道律师不是任何人来这里的目的,但如果你修好了它会很好。
  • @GabrielLittman:已经有 established libraries written by seasoned devs 执行 semver 比较。
【解决方案3】:

最简单的是使用 localeCompare

a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })

这将返回:

  • 0:版本字符串相等
  • 1:版本a大于b
  • -1:版本b大于a

【讨论】:

  • 这是最简单的答案,爱它!
  • 为什么这没有更多的选票?它有什么问题吗?它似乎通过了我写的所有测试。
  • @JuanMendes 简单的回答,我在问题发布 10 年后写了这篇文章 :) 但这是个好主意,让我们开始投票吧! ?
  • @JuanMendes 这确实有一个限制,即版本字符串必须始终具有相同数量的部分。所以当通过1.01.0.0.0时,localeCompare显示1.0.0.0更大。
  • 喜欢它,但不幸的是它可以通过这个测试1.0.0-alpha &lt; 1.0.0。见semver.org/#spec-item-11
【解决方案4】:
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
    if (a === b) {
       return 0;
    }

    var a_components = a.split(".");
    var b_components = b.split(".");

    var len = Math.min(a_components.length, b_components.length);

    // loop while the components are equal
    for (var i = 0; i < len; i++) {
        // A bigger than B
        if (parseInt(a_components[i]) > parseInt(b_components[i])) {
            return 1;
        }

        // B bigger than A
        if (parseInt(a_components[i]) < parseInt(b_components[i])) {
            return -1;
        }
    }

    // If one's a prefix of the other, the longer one is greater.
    if (a_components.length > b_components.length) {
        return 1;
    }

    if (a_components.length < b_components.length) {
        return -1;
    }

    // Otherwise they are the same.
    return 0;
}

console.log(compare("1", "2"));
console.log(compare("2", "1"));

console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));

【讨论】:

  • 我认为行:var len = Math.min(a_components.length, b_components.length); 将导致版本 2.0.1.1 和 2.0.1 被视为相同,对吗?
  • 不,只看循环之后!如果一个字符串是另一个字符串的前缀(即循环到达末尾),则较长的字符串被认为更高。
  • 也许你在评论中推迟了我对英语的绊脚石......
  • @Joe 我知道这是一个有点老的答案,但我正在使用该功能。测试 a = '7'b = '7.0' 返回 -1 因为 7.0 更长。有什么建议吗? (console.log(compare("7", "7.0")); //returns -1)
  • @RaphaelDDL 比较两个数组的长度并将 0 添加到最短的数组,直到长度相等。
【解决方案5】:

这个非常小但非常快的比较函数需要任意长度的版本号每段任意数字大小.

返回值:
- 数字&lt; 0 如果 a < b
- 一个数字&gt; 0 如果 a > b
- 0 如果 a = b

所以你可以用它作为Array.sort() 的比较函数;

编辑:错误修复版本剥离尾随零以将“1”和“1.0.0”识别为相等

function cmpVersions (a, b) {
    var i, diff;
    var regExStrip0 = /(.0+)+$/;
    var segmentsA = a.replace(regExStrip0, '').split('.');
    var segmentsB = b.replace(regExStrip0, '').split('.');
    var l = Math.min(segmentsA.length, segmentsB.length);

    for (i = 0; i < l; i++) {
        diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
        if (diff) {
            return diff;
        }
    }
    return segmentsA.length - segmentsB.length;
}

// TEST
console.log(
['2.5.10.4159',
 '1.0.0',
 '0.5',
 '0.4.1',
 '1',
 '1.1',
 '0.0.0',
 '2.5.0',
 '2',
 '0.0',
 '2.5.10',
 '10.5',
 '1.25.4',
 '1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]

【讨论】:

  • 因“0.0”和“0.0.0”而失败。见小提琴:jsfiddle.net/emragins/9e9pweqg
  • @emragins 你什么时候需要这样做?
  • @emragins:我看不出它失败的地方。它输出["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] ,而您的代码输出["0.0", "0.0.0", "0.4.1", "0.5", "1", "1.0.0", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] ,这完全相同,因为 0.0 和 0.0.0 被认为是平等的,这意味着“0.0”是否在“0.0.0”之前是无关紧要的,反之亦然。
  • 我同意这是一个常见的观点。我将它与 github.com/jonmiles/bootstrap-treeview 一起使用,它以类似于版本的方式对节点进行分层,只是它实际上只是父/子节点及其索引。前任。父级:0.0,子级:0.0.0、0.0.1。有关我为什么关心的更多详细信息,请参阅此问题:github.com/jonmiles/bootstrap-treeview/issues/251
  • 请在此处查看答案stackoverflow.com/questions/6611824/why-do-we-need-to-use-radix。如果未指定,较旧的浏览器过去常常猜测基数参数。数字字符串中的前导零(如“1.09.12”中的中间部分)曾经使用 radix=8 进行解析,导致数字 0 而不是预期的数字 9。
【解决方案6】:

简单而简短的功能:

function isNewerVersion (oldVer, newVer) {
  const oldParts = oldVer.split('.')
  const newParts = newVer.split('.')
  for (var i = 0; i < newParts.length; i++) {
    const a = ~~newParts[i] // parse int
    const b = ~~oldParts[i] // parse int
    if (a > b) return true
    if (a < b) return false
  }
  return false
}

测试:

isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false

【讨论】:

  • 您可以通过以下方式简化它:const a = ~~newParts[i];事实上,这是将字符串转换为整数的最有效方法,如果变量未定义或包含非数字字符,则返回 0。
  • 我经常需要知道它是更新的还是相同的,因此我的代码可以决定是否隐藏不支持的功能。这不是大多数人感兴趣的问题吗?
  • 又好又短,正是我要找的。您还可以添加 oldVer.replace(/[^0-9.]/g, '').trim()newVer.replace(/[^0-9.]/g, '').trim() 来处理添加如下文本的 alpha、beta 或发布候选版本:`1.0.0-rc'
【解决方案7】:

摘自http://java.com/js/deployJava.js

    // return true if 'installed' (considered as a JRE version string) is
    // greater than or equal to 'required' (again, a JRE version string).
    compareVersions: function (installed, required) {

        var a = installed.split('.');
        var b = required.split('.');

        for (var i = 0; i < a.length; ++i) {
            a[i] = Number(a[i]);
        }
        for (var i = 0; i < b.length; ++i) {
            b[i] = Number(b[i]);
        }
        if (a.length == 2) {
            a[2] = 0;
        }

        if (a[0] > b[0]) return true;
        if (a[0] < b[0]) return false;

        if (a[1] > b[1]) return true;
        if (a[1] < b[1]) return false;

        if (a[2] > b[2]) return true;
        if (a[2] < b[2]) return false;

        return true;
    }

【讨论】:

  • 简单,但仅限于三个版本字段。
  • 意识到我来得太晚了,但我真的很喜欢这个用于语义版本控制的简单解决方案,因为您将拥有三个版本字段。
  • 终于有一个我可以轻松阅读的版本。是的,三个版本字段是标准的,所以这对我们大多数人都很有用
【解决方案8】:

在这里找不到执行我想要的功能的功能。所以我写了我自己的。这是我的贡献。我希望有人觉得它有用。

优点:

  • 处理任意长度的版本字符串。 “1”或“1.1.1.1.1”。

  • 如果未指定,则将每个值默认为 0。仅仅因为一个字符串更长并不意味着它是一个更大的版本。 (“1”应与“1.0”和“1.0.0.0”相同。)

  • 比较数字而不是字符串。 ('3'<'21' 应该是真的。不是假的。)

  • 不要将时间浪费在循环中无用的比较上。 (比较==)

  • 您可以选择自己的比较器。

缺点:

  • 它不处理版本字符串中的字母。 (我不知道那是怎么回事?)

我的代码,类似于接受的答案乔恩:

function compareVersions(v1, comparator, v2) {
    "use strict";
    var comparator = comparator == '=' ? '==' : comparator;
    if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
        throw new Error('Invalid comparator. ' + comparator);
    }
    var v1parts = v1.split('.'), v2parts = v2.split('.');
    var maxLen = Math.max(v1parts.length, v2parts.length);
    var part1, part2;
    var cmp = 0;
    for(var i = 0; i < maxLen && !cmp; i++) {
        part1 = parseInt(v1parts[i], 10) || 0;
        part2 = parseInt(v2parts[i], 10) || 0;
        if(part1 < part2)
            cmp = 1;
        if(part1 > part2)
            cmp = -1;
    }
    return eval('0' + comparator + cmp);
}

例子:

compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false

【讨论】:

  • 我认为此版本比已批准答案中的版本更好!
  • 如果 comparator 参数与未经检查的用户输入一起使用,则此函数容易发生代码注入!示例:compareVersions('1.2', '==0;alert("cotcha");', '1.2');
  • @LeJared 是的。当我写它时,我们不打算将它用于用户提交的代码。应该把它作为一个骗局提出来。我现在已经更新了代码以消除这种可能性。不过现在,当 webpack 和其他 node.js 打包器变得流行时,我建议穆罕默德·阿克丁上面的答案,使用 semver,几乎总是这个问题的正确答案。
【解决方案9】:

这是另一个短版本,适用于任意数量的子版本、填充零和带字母的偶数 (1.0.0b3)

const compareVer = ((prep, repl) =>
{
  prep = t => ("" + t)
      //treat non-numerical characters as lower version
      //replacing them with a negative number based on charcode of first character
    .replace(/[^0-9.]+/g, c => "." + (c.replace(/[W_]+/, "").toLowerCase().charCodeAt(0) - 65536) + ".")
      //remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
    .replace(/(?:.0+)*(.-[0-9]+)(.[0-9]+)?.*$/g, "$1$2")
    .split('.');

  return (a, b, c, i, r) =>
  {
    a = prep(a);
    b = prep(b);
    for (i = 0, r = 0, c = Math.max(a.length, b.length); !r && i++ < c;)
    {
      r = -1 * ((a[i] = ~~a[i]) < (b[i] = ~~b[i])) + (a[i] > b[i]);
    }
    return r;
  }
})();

函数返回:

0如果a = b

1如果a &gt; b

-1如果a &lt; b

1.0         = 1.0.0.0.0.0
1.0         < 1.0.1
1.0b1       < 1.0
1.0b        = 1.0b
1.1         > 1.0.1b
1.1alpha    < 1.1beta
1.1rc1      > 1.1beta
1.1rc1      < 1.1rc2
1.1.0a1     < 1.1a2
1.1.0a10    > 1.1.0a1
1.1.0alpha  = 1.1a
1.1.0alpha2 < 1.1b1
1.0001      > 1.00000.1.0.0.0.01

/*use strict*/
const compareVer = ((prep, repl) =>
{
  prep = t => ("" + t)
      //treat non-numerical characters as lower version
      //replacing them with a negative number based on charcode of first character
    .replace(/[^0-9.]+/g, c => "." + (c.replace(/[W_]+/, "").toLowerCase().charCodeAt(0) - 65536) + ".")
      //remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
    .replace(/(?:.0+)*(.-[0-9]+)(.[0-9]+)?.*$/g, "$1$2")
    .split('.');

  return (a, b, c, i, r) =>
  {
    a = prep(a);
    b = prep(b);
    for (i = 0, r = 0, c = Math.max(a.length, b.length); !r && i++ < c;)
    {
      r = -1 * ((a[i] = ~~a[i]) < (b[i] = ~~b[i])) + (a[i] > b[i]);
    }
    return r;
  }
})();

//examples

let list = [
  ["1.0",         "1.0.0.0.0.0"],
  ["1.0",         "1.0.1"],
  ["1.0b1",       "1.0"],
  ["1.0b",        "1.0b"],
  ["1.1",         "1.0.1b"],
  ["1.1alpha",    "1.1beta"],
  ["1.1rc1",      "1.1beta"],
  ["1.1rc1",      "1.1rc2"],
  ["1.1.0a1",     "1.1a2"],
  ["1.1.0a10",    "1.1.0a1"],
  ["1.1.0alpha",  "1.1a"],
  ["1.1.0alpha2", "1.1b1"],
  ["1.0001",      "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
  console.log( list[i][0] + " " + "<=>"[compareVer(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}

https://jsfiddle.net/vanowm/p7uvtbor/

【讨论】:

    【解决方案10】:

    2017年答案:

    v1 = '20.0.12'; 
    v2 = '3.123.12';
    
    compareVersions(v1,v2) 
    // return positive: v1 > v2, zero:v1 == v2, negative: v1 < v2 
    function compareVersions(v1, v2) {
            v1= v1.split('.')
            v2= v2.split('.')
            var len = Math.max(v1.length,v2.length)
            /*default is true*/
            for( let i=0; i < len; i++)
                v1 = Number(v1[i] || 0);
                v2 = Number(v2[i] || 0);
                if (v1 !== v2) return v1 - v2 ;
                i++;
            }
            return 0;
        }
    

    现代浏览器的最简单代码:

     function compareVersion2(ver1, ver2) {
          ver1 = ver1.split('.').map( s => s.padStart(10) ).join('.');
          ver2 = ver2.split('.').map( s => s.padStart(10) ).join('.');
          return ver1 <= ver2;
     }
    

    这里的想法是比较数字,但是是以字符串的形式。要进行比较,两个字符串的长度必须相同。所以:

    "123" &gt; "99"变成"123" &gt; "099"
    填充短数字“修复”比较

    在这里,我用零填充每个部分到 10 的长度。然后只使用简单的字符串比较作为答案

    例子 :

    var ver1 = '0.2.10', ver2=`0.10.2`
    //become 
    ver1 = '0000000000.0000000002.0000000010'
    ver2 = '0000000000.0000000010.0000000002'
    // then it easy to see that
    ver1 <= ver2 // true
    

    【讨论】:

    • 你能解释一下函数compareVersion2到底发生了什么吗?
    • 好,那么你可以使用 substring 而不是 padStart 以获得更好的兼容性,即 var zeros = "0000000000"; '0.2.32'.split('.').map( s =&gt; zeros.substring(0, zeros.length-s.length) + s ).join('.') 会给你 0000000000.0000000002.0000000032 :)
    【解决方案11】:

    我遇到了类似的问题,我已经为它创建了一个解决方案。随意试一试。

    对于equal,它返回0,如果版本是greater,则返回1,如果是less,则返回-1

    function compareVersion(currentVersion, minVersion) {
      let current = currentVersion.replace(/./g," .").split(' ').map(x=>parseFloat(x,10))
      let min = minVersion.replace(/./g," .").split(' ').map(x=>parseFloat(x,10))
    
      for(let i = 0; i < Math.max(current.length, min.length); i++) {
        if((current[i] || 0) < (min[i] || 0)) {
          return -1
        } else if ((current[i] || 0) > (min[i] || 0)) {
          return 1
        }
      }
      return 0
    }
    
    
    console.log(compareVersion("81.0.1212.121","80.4.1121.121"));
    console.log(compareVersion("81.0.1212.121","80.4.9921.121"));
    console.log(compareVersion("80.0.1212.121","80.4.9921.121"));
    console.log(compareVersion("4.4.0","4.4.1"));
    console.log(compareVersion("5.24","5.2"));
    console.log(compareVersion("4.1","4.1.2"));
    console.log(compareVersion("4.1.2","4.1"));
    console.log(compareVersion("4.4.4.4","4.4.4.4.4"));
    console.log(compareVersion("4.4.4.4.4.4","4.4.4.4.4"));
    console.log(compareVersion("0","1"));
    console.log(compareVersion("1","1"));
    console.log(compareVersion("1","1.0.00000.0000"));
    console.log(compareVersion("","1"));
    console.log(compareVersion("10.0.1","10.1"));

    【讨论】:

    • 正则表达式是不必要的。您可以简单地将 . 附加到 map() 中: x=&gt;parseFloat("." + x, 10)
    【解决方案12】:

    虽然这个问题已经有一个很多的答案,每个人都在推广自己的后院酿造解决方案,而我们为此拥有一整套经过(实战)测试的库生态系统。

    快速搜索 NPMGitHub,X 会给我们一些可爱的库,我想浏览一下:

    semver-compare 是一个很棒的轻量级(~230 字节)库,如果您想按版本号排序,它特别有用,因为库的公开方法会适当地返回 -101

    库的核心:

    module.exports = function cmp (a, b) {
        var pa = a.split('.');
        var pb = b.split('.');
        for (var i = 0; i < 3; i++) {
            var na = Number(pa[i]);
            var nb = Number(pb[i]);
            if (na > nb) return 1;
            if (nb > na) return -1;
            if (!isNaN(na) && isNaN(nb)) return 1;
            if (isNaN(na) && !isNaN(nb)) return -1;
        }
        return 0;
    };
    

    compare-semver 的大小相当大(~4.4 kB gzipped),但允许进行一些不错的独特比较,比如找到一堆版本的最小值/最大值,或者找出提供的版本是唯一的还是小于其他任何版本版本的集合。

    compare-versions 是另一个小型库(压缩后约 630 字节)并且很好地遵循规范,这意味着您可以比较带有 alpha/beta 标志甚至通配符的版本(例如次要/补丁版本:1.0.x1.0.*

    关键是:并不总是需要从 Stack Overflow 复制粘贴代码,如果你能找到合适的,(unit-)测试过通过您选择的包管理器的版本。

    【讨论】:

    • 第一个名为semver-compare,但不支持Semantic Versioning。而且,this answer 比它更强大、更轻巧。
    • @Míng 语义版本控制实际上没有 v.* 前缀 (semver.org/#is-v123-a-semantic-version) 所以我想说 semver-compare 确实支持语义版本控制
    • 在某些情况下它可能没问题,但它的名字具有误导性。
    • 究竟如何?它确实支持 semver 规范?
    • 确切地说,如果支持 semver 规范,cmp("1.0.0-b", "1.0.0-a") 应该返回 1,但它返回 0。查看来自 Semantic Versioning 的更多示例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0 .0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。
    【解决方案13】:

    如果这个想法已经在我没有看到的链接中访问过,请原谅我。

    我在将零件转换为加权总和方面取得了一些成功,如下所示:

    partSum = this.major * Math.Pow(10,9);
    partSum += this.minor * Math.Pow(10, 6);
    partSum += this.revision * Math.Pow(10, 3);
    partSum += this.build * Math.Pow(10, 0);
    

    这使得比较变得非常容易(比较双)。 我们的版本字段从不超过 4 位。

    7.10.2.184  -> 7010002184.0
    7.11.0.1385 -> 7011001385.0
    

    我希望这对某人有所帮助,因为多个条件似乎有点矫枉过正。

    【讨论】:

    • 这将中断,如果 this.minor > 999(将与主要重叠)
    【解决方案14】:

    一个简单的方法:

    function compareVer(previousVersion, currentVersion) {
     try {
        const [prevMajor, prevMinor = 0, prevPatch = 0] = previousVersion.split('.').map(Number);
        const [curMajor, curMinor = 0, curPatch = 0] = currentVersion.split('.').map(Number);
    
        if (curMajor > prevMajor) {
          return 'major update';
        }
        if (curMajor < prevMajor) {
          return 'major downgrade';
        }
        if (curMinor > prevMinor) {
          return 'minor update';
        }
        if (curMinor < prevMinor) {
          return 'minor downgrade';
        }
        if (curPatch > prevPatch) {
          return 'patch update';
        }
        if (curPatch < prevPatch) {
          return 'patch downgrade';
        }
        return 'same version';
      } catch (e) {
        return 'invalid format';
      }
    }
    

    输出:

    compareVer("3.1", "3.1.1") // patch update
    compareVer("3.1.1", "3.2") // minor update
    compareVer("2.1.1", "1.1.1") // major downgrade
    compareVer("1.1.1", "1.1.1") // same version
    

    【讨论】:

      【解决方案15】:

      检查函数version_compare() from the php.js project。它类似于PHP's version_compare()

      您可以像这样简单地使用它:

      version_compare('2.0', '2.0.0.1', '<'); 
      // returns true
      

      【讨论】:

        【解决方案16】:

        我的回答比这里的大多数答案都简洁

        /**
         * Compare two semver versions. Returns true if version A is greater than
         * version B
         * @param {string} versionA
         * @param {string} versionB
         * @returns {boolean}
         */
        export const semverGreaterThan = function(versionA, versionB){
          var versionsA = versionA.split(/./g),
            versionsB = versionB.split(/./g)
          while (versionsA.length || versionsB.length) {
            var a = Number(versionsA.shift()), b = Number(versionsB.shift())
            if (a == b)
              continue
            return (a > b || isNaN(b))
          }
          return false
        }
        

        【讨论】:

        • 你应该把它做成一个模块并放在 node.js 上。在那之前,我正在窃取你的代码并归因于你。这次真是万分感谢。
        【解决方案17】:

        您可以将 String#localeCompareoptions 一起使用

        灵敏度

        字符串中的哪些差异应导致非零结果值。可能的值是:

        • "base":只有基字母不同的字符串才比较为不相等。示例:a ≠ ba = áa = A
        • "accent":只有基本字母或重音和其他变音符号不同的字符串比较为不相等。示例:a ≠ ba ≠ áa = A
        • "case":只有基本字母或大小写不同的字符串才比较为不相等。示例:a ≠ ba = áa ≠ A
        • "variant":基本字母、重音符号和其他变音符号不同或大小写比较不相等的字符串。也可以考虑其他差异。示例:a ≠ ba ≠ áa ≠ A

        对于用法“sort”,默认是“variant”;它依赖于使用“搜索”的区域设置。

        数字

        是否应使用数字排序规则,例如“1”<“2”<“10”。可能的值为truefalse;默认为false。可以通过选项属性或通过 Unicode 扩展密钥设置此选项;如果两者都提供,则 options 属性优先。不需要实现来支持此属性。

        var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];
        
        versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
        
        console.log(versions);

        【讨论】:

        • 这实际上是如何工作的?上面的undefined是什么语言?你怎么能在我阅读其他人的时候发布这个 ;)
        • undefined 是语言环境部分,这里没有使用。
        【解决方案18】:

        我们现在可以使用Intl.Collator API 来创建数字比较器。 Browser support 相当不错,但在撰写本文时 Node.js 不支持。

        const semverCompare = new Intl.Collator("en", { numeric: true }).compare;
        
        const versions = ['1.0.1', '1.10.2', '1.1.1', '1.10.1', '1.5.10', '2.10.0', '2.0.1'];
        
        console.log(versions.sort(semverCompare))
        
        const example2 = ["1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"];
        console.log(example2.sort(semverCompare))

        【讨论】:

          【解决方案19】:

          2020 年(大部分时间)正确的 JavaScript 答案

          2020 年 3 月的Nina Scholz和 2020 年 4 月的Sid Vishnoi都发布了现代答案:

          var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];
          
          versions.sort((a, b) => 
             a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
          );
          
          console.log(versions);
          

          localeCompare 已经存在一段时间了

          https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator

          但是关于1.0a1.0.1

          localCompare 没有解决这个问题,仍然返回1.0.1 , 1.0a

          迈克尔处理他的(较长且复杂的)解决方案already cracked that in 2013

          他皈依数字给另一个根据,所以他们可以更好地排序

          他的回答让我思考...

          666 - 不要用数字来思考 - 999

          排序是基于 ASCII 值的字母数字,所以让我们(ab)使用 ASCII 作为“基础”

          我的解决方案是转换1.0.2.1b.a.c.b细菌,然后排序

          这解决了1.1对比1.0.0.0.1和:bb对比baaab

          并立即解决1.0a1.0.1符号排序问题:宝贝

          转换完成:

              const str = s => s.match(/(d+)|[a-z]/g)
                                .map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
          

          = 计算 0...999 数字的 ASCII 值,否则连接字母

          1.0a>>>[ "1" , "0" , "a" ]>>>[ "b" , "a" , "a" ]

          为了比较起见,不需要将它连接到一个字符串 .join("")

          单班机

          const sortVersions=(x,v=s=>s.match(/(d+)|[a-z]/g)
                                      .map(c=>c==~~c?String.fromCharCode(97+c):c))
                              =>x.sort((a,b)=>v(b)<v(a)?1:-1)
          

          测试SN-P:

          function log(label,val){
            document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
          }
          
          let v = ["1.90.1", "1.9.1", "1.89", "1.090", "1.2", "1.0a", "1.0.1", "1.10", "1.0.0a"];
          log('not sorted input :',v);
          
          v.sort((a, b) => a.localeCompare(b,undefined,{numeric:true,sensitivity:'base'   }));
          log(' locale Compare :', v); // 1.0a AFTER 1.0.1
          
          const str = s => s.match(/(d+)|[a-z]/g)
                            .map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
          const versionCompare = (a, b) => {
            a = str(a);
            b = str(b);
            return b < a ? 1 : a == b ? 0 : -1;
          }
          
          v.sort(versionCompare);
          log('versionCompare:', v);

          注意如何1.090在两个结果中排序。

          我的代码会不是解决001.012.001一个答案中提到的符号,但 localeCompare 正确地解决了这部分挑战。

          您可以结合这两种方法:

          • 当涉及字母时,按.localCompareversionCompare排序

          最终的 JavaScript 解决方案

          const sortVersions = (
            x,
            v = s => s.match(/[a-z]|d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
          ) => x.sort((a, b) => (a + b).match(/[a-z]/) 
                                       ? v(b) < v(a) ? 1 : -1 
                                       : a.localeCompare(b, 0, {numeric: true}))
          
          let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1"];
          console.log(sortVersions(v));

          【讨论】:

            【解决方案20】:

            几行代码,如果您不想使用字母或符号,则很好。如果您控制版本控制方案并且这不是第 3 方提供的,则此方法有效。

            // we presume all versions are of this format "1.4" or "1.10.2.3", without letters
            // returns: 1 (bigger), 0 (same), -1 (smaller)
            function versionCompare (v1, v2) {
              const v1Parts = v1.split('.')
              const v2Parts = v2.split('.')
              const length = Math.max(v1Parts.length, v2Parts.length)
              for (let i = 0; i < length; i++) {
                const value = (parseInt(v1Parts[i]) || 0) - (parseInt(v2Parts[i]) || 0)
                if (value < 0) return -1
                if (value > 0) return 1
              }
              return 0
            }
            
            console.log(versionCompare('1.2.0', '1.2.4') === -1)
            console.log(versionCompare('1.2', '1.2.0') === 0)
            console.log(versionCompare('1.2', '1') === 1)
            console.log(versionCompare('1.2.10', '1.2.1') === 1)
            console.log(versionCompare('1.2.134230', '1.2.2') === 1)
            console.log(versionCompare('1.2.134230', '1.3.0.1.2.3.1') === -1)

            【讨论】:

              【解决方案21】:

              您可以使用 JavaScript localeCompare 方法:

              a.localeCompare(b, undefined, { numeric: true })

              这是一个例子:

              "1.1".localeCompare("2.1.1", undefined, { numeric: true })=>-1

              "1.0.0".localeCompare("1.0", undefined, { numeric: true })=>1

              "1.0.0".localeCompare("1.0.0", undefined, { numeric: true }) => 0

              【讨论】:

              【解决方案22】:
              // Returns true if v1 is bigger than v2, and false if otherwise.
              function isNewerThan(v1, v2) {
                    v1=v1.split('.');
                    v2=v2.split('.');
                    for(var i = 0; i<Math.max(v1.length,v2.length); i++){
                      if(v1[i] == undefined) return false; // If there is no digit, v2 is automatically bigger
                      if(v2[i] == undefined) return true; // if there is no digit, v1 is automatically bigger
                      if(v1[i] > v2[i]) return true;
                      if(v1[i] < v2[i]) return false;
                    }
                    return false; // Returns false if they are equal
                  }
              

              【讨论】:

              • 欢迎来到 SO。这个问题已经有很多好的答案,除非你添加新的东西,否则请不要添加新的答案。
              【解决方案23】:

              这个想法是比较两个版本并知道哪个是最大的。我们删除“。”然后我们将向量的每个位置与另一个进行比较。

              // Return 1  if a > b
              // Return -1 if a < b
              // Return 0  if a == b
              
              function compareVersions(a_components, b_components) {
              
                 if (a_components === b_components) {
                     return 0;
                 }
              
                 var partsNumberA = a_components.split(".");
                 var partsNumberB = b_components.split(".");
              
                 for (var i = 0; i < partsNumberA.length; i++) {
              
                    var valueA = parseInt(partsNumberA[i]);
                    var valueB = parseInt(partsNumberB[i]);
              
                    // A bigger than B
                    if (valueA > valueB || isNaN(valueB)) {
                       return 1;
                    }
              
                    // B bigger than A
                    if (valueA < valueB) {
                       return -1;
                    }
                 }
              }
              

              【讨论】:

              • 史诗般的答案,正是我想要的。
              【解决方案24】:

              replace() 函数仅替换字符串中的第一次出现。所以,让我们将 . 替换为 ,。之后删除所有.并再次将,变为.并将其解析为浮动。

              for(i=0; i<versions.length; i++) {
                  v = versions[i].replace('.', ',');
                  v = v.replace(/./g, '');
                  versions[i] = parseFloat(v.replace(',', '.'));
              }
              

              最后,排序:

              versions.sort();
              

              【讨论】:

                【解决方案25】:

                看看这个blog post。此函数适用于数字版本号。

                function compVersions(strV1, strV2) {
                  var nRes = 0
                    , parts1 = strV1.split('.')
                    , parts2 = strV2.split('.')
                    , nLen = Math.max(parts1.length, parts2.length);
                
                  for (var i = 0; i < nLen; i++) {
                    var nP1 = (i < parts1.length) ? parseInt(parts1[i], 10) : 0
                      , nP2 = (i < parts2.length) ? parseInt(parts2[i], 10) : 0;
                
                    if (isNaN(nP1)) { nP1 = 0; }
                    if (isNaN(nP2)) { nP2 = 0; }
                
                    if (nP1 != nP2) {
                      nRes = (nP1 > nP2) ? 1 : -1;
                      break;
                    }
                  }
                
                  return nRes;
                };
                
                compVersions('10', '10.0'); // 0
                compVersions('10.1', '10.01.0'); // 0
                compVersions('10.0.1', '10.0'); // 1
                compVersions('10.0.1', '10.1'); // -1
                

                【讨论】:

                  【解决方案26】:

                  例如,如果我们想检查当前的 jQuery 版本是否低于 1.8,parseFloat($.ui.version) &lt; 1.8 ) 会给出一个错误的结果如果版本是“1.10.1”,因为 parseFloat(“1.10.1”) 返回 1.1。 字符串比较也会出错,因为 "1.8" &lt; "1.10" 的计算结果为 false

                  所以我们需要这样的测试

                  if(versionCompare($.ui.version, "1.8") < 0){
                      alert("please update jQuery");
                  }
                  

                  以下函数可以正确处理此问题:

                  /** Compare two dotted version strings (like '10.2.3').
                   * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
                   */
                  function versionCompare(v1, v2) {
                      var v1parts = ("" + v1).split("."),
                          v2parts = ("" + v2).split("."),
                          minLength = Math.min(v1parts.length, v2parts.length),
                          p1, p2, i;
                      // Compare tuple pair-by-pair. 
                      for(i = 0; i < minLength; i++) {
                          // Convert to integer if possible, because "8" > "10".
                          p1 = parseInt(v1parts[i], 10);
                          p2 = parseInt(v2parts[i], 10);
                          if (isNaN(p1)){ p1 = v1parts[i]; } 
                          if (isNaN(p2)){ p2 = v2parts[i]; } 
                          if (p1 == p2) {
                              continue;
                          }else if (p1 > p2) {
                              return 1;
                          }else if (p1 < p2) {
                              return -1;
                          }
                          // one operand is NaN
                          return NaN;
                      }
                      // The longer tuple is always considered 'greater'
                      if (v1parts.length === v2parts.length) {
                          return 0;
                      }
                      return (v1parts.length < v2parts.length) ? -1 : 1;
                  }
                  

                  这里有些例子:

                  // compare dotted version strings
                  console.assert(versionCompare("1.8",      "1.8.1")    <   0);
                  console.assert(versionCompare("1.8.3",    "1.8.1")    >   0);
                  console.assert(versionCompare("1.8",      "1.10")     <   0);
                  console.assert(versionCompare("1.10.1",   "1.10.1")   === 0);
                  // Longer is considered 'greater'
                  console.assert(versionCompare("1.10.1.0", "1.10.1")   >   0);
                  console.assert(versionCompare("1.10.1",   "1.10.1.0") <   0);
                  // Strings pairs are accepted
                  console.assert(versionCompare("1.x",      "1.x")      === 0);
                  // Mixed int/string pairs return NaN
                  console.assert(isNaN(versionCompare("1.8", "1.x")));
                  //works with plain numbers
                  console.assert(versionCompare("4", 3)   >   0);
                  

                  请在此处查看实时示例和测试套件: http://jsfiddle.net/mar10/8KjvP/

                  【讨论】:

                  • arghh,刚刚注意到 ripper234 几个月前在 cmet 的 eof 上发布了一个非常相似的 fiddle URL。无论如何,我把我的答案留在这里......
                  • 在这些情况下,这个也将失败(作为周围的大多数变体):versionCompare('1.09', '1.1') 返回“1”,与 versionCompare('1.702', '1.8') 相同。
                  • 代码评估“1.09”>“1.1”和“1.702”>“1.8”,我认为这是正确的。如果您不同意:您能指出一些支持您观点的资源吗?
                  • 这取决于你的原则——据我所知,没有严格的规定之类的。关于资源,“递增序列”中“软件版本控制”的维基百科文章说 1.81 可能是 1.8 的次要版本,因此 1.8 应该读作 1.80。语义版本控制文章semver.org/spec/v2.0.0.html 也说了 1.9.0 -> 1.10.0 -> 1.11.0,所以 1.9.0 被当作 1.90.0 这样比较。所以,按照这个逻辑,版本 1.702 早于版本 1.8,被视为 1.800。
                  • 我看到一些规则对待 1.8 < 1.81 < 1.9。但是在 semver 中你会使用 1.8.1 而不是 1.81。 Semver(据我所知)是围绕这样的假设定义的,即增加一个部分总是会产生一个“以后”的版本,所以 1.8 < 1.8.1 < 1.9 < 1.10 < 1.81 < 1.90 < 1.100 。我也没有看到这限制为两位数的迹象。所以我会说我的代码完全符合 semver。
                  【解决方案27】:

                  这是一个巧妙的技巧。如果您正在处理数值,在特定范围的值之间,您可以为版本对象的每个级别分配一个值。例如,“largestValue”在这里设置为 0xFF,这为您的版本控制创建了一种非常“IP”的外观。

                  这也处理字母数字版本控制(即 1.2a < 1.2b)

                  // The version compare function
                  function compareVersion(data0, data1, levels) {
                      function getVersionHash(version) {
                          var value = 0;
                          version = version.split(".").map(function (a) {
                              var n = parseInt(a);
                              var letter = a.replace(n, "");
                              if (letter) {
                                  return n + letter[0].charCodeAt() / 0xFF;
                              } else {
                                  return n;
                              }
                          });
                          for (var i = 0; i < version.length; ++i) {
                              if (levels === i) break;
                              value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
                          }
                          return value;
                      };
                      var v1 = getVersionHash(data0);
                      var v2 = getVersionHash(data1);
                      return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
                  };
                  // Returns 0 or 1, correlating to input A and input B
                  // Direct match returns -1
                  var version = compareVersion("1.254.253", "1.254.253a", 3);
                  

                  【讨论】:

                    【解决方案28】:

                    我根据 Kons 的想法做了这个,并针对 Java 版本“1.7.0_45”进行了优化。它只是一个将版本字符串转换为浮点数的函数。这是功能:

                    function parseVersionFloat(versionString) {
                        var versionArray = ("" + versionString)
                                .replace("_", ".")
                                .replace(/[^0-9.]/g, "")
                                .split("."),
                            sum = 0;
                        for (var i = 0; i < versionArray.length; ++i) {
                            sum += Number(versionArray[i]) / Math.pow(10, i * 3);
                        }
                        console.log(versionString + " -> " + sum);
                        return sum;
                    }
                    

                    字符串“1.7.0_45”转换为 1.0070000450000001,这足以进行正常比较。错误解释在这里:How to deal with floating point number precision in JavaScript?。如果任何部分需要超过 3 位数字,您可以更改分隔符 Math.pow(10, i * 3);

                    输出将如下所示:

                    1.7.0_45         > 1.007000045
                    ver 1.7.build_45 > 1.007000045
                    1.234.567.890    > 1.23456789
                    

                    【讨论】:

                    • 这是一个很好的解决方案。也可以单行:("" + versionString).replace("_", ".").replace(/[^0-9.]/g, "").split(".").reverse().reduce((accumulator, value) =&gt; accumulator/1000 + Number(value), 0)
                    【解决方案29】:

                    这是一个适合与 Array.sort 一起使用的 coffeescript 实现,灵感来自此处的其他答案:

                    # Returns > 0 if v1 > v2 and < 0 if v1 < v2 and 0 if v1 == v2
                    compareVersions = (v1, v2) ->
                      v1Parts = v1.split('.')
                      v2Parts = v2.split('.')
                      minLength = Math.min(v1Parts.length, v2Parts.length)
                      if minLength > 0
                        for idx in [0..minLength - 1]
                          diff = Number(v1Parts[idx]) - Number(v2Parts[idx])
                          return diff unless diff is 0
                      return v1Parts.length - v2Parts.length
                    

                    【讨论】:

                    • 这是受LeJared's answer的启发。
                    • 这不能正常工作.. 这是结果.. 结果 [ '1.1.1', '2.1.1', '3.3.1.0', '3.1.1.0' ]
                    【解决方案30】:

                    我写了一个用于排序版本的节点模块,你可以在这里找到它:version-sort

                    特征:

                    • 序列“1.0.1.5.53.54654.114.1.154.45”没有限制
                    • 序列长度没有限制:'1.1546515465451654654654654138754431574364321353734'有效
                    • 可以按版本对对象进行排序(参见 README)
                    • 阶段(如 alpha、beta、rc1、rc2)

                    如果您需要其他功能,请不要犹豫,提出问题。

                    【讨论】:

                      猜你喜欢
                      • 2011-10-13
                      • 2021-07-22
                      • 2011-06-24
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-04-29
                      相关资源
                      最近更新 更多