以下是软件版本号:
"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"
这样可以更清楚地看到背后的想法。
但是,我怎样才能把它转换成计算机程序呢?
例如,如果我们想检查当前jQuery版本是否小于1.8,如果version是"1.10.1",parseFloat($.ui.version) < 1.8)将会给出错误的结果,因为parseFloat("1.10.1")返回1.1。
字符串比较也会出错,因为"1.8" < "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/
这里有一个面向对象的有趣方法:
function versionString(str) {
var parts = str.split('.');
this.product = parts.length > 0 ? parts[0] * 1 : 0;
this.major = parts.length > 1 ? parts[1] * 1 : 0;
this.minor = parts.length > 2 ? parts[2] * 1 : 0;
this.build = parts.length > 3 ? parts[3] * 1 : 0;
this.compareTo = function(vStr){
vStr = this._isVersionString(vStr) ? vStr : new versionString(vStr);
return this.compare(this, vStr);
};
this.toString = function(){
return this.product + "." + this.major + "." + this.minor + "." + this.build;
}
this.compare = function (str1, str2) {
var vs1 = this._isVersionString(str1) ? str1 : new versionString(str1);
var vs2 = this._isVersionString(str2) ? str2 : new versionString(str2);
if (this._compareNumbers(vs1.product, vs2.product) == 0) {
if (this._compareNumbers(vs1.major, vs2.major) == 0) {
if (this._compareNumbers(vs1.minor, vs2.minor) == 0) {
return this._compareNumbers(vs1.build, vs2.build);
} else {
return this._compareNumbers(vs1.minor, vs2.minor);
}
} else {
return this._compareNumbers(vs1.major, vs2.major);
}
} else {
return this._compareNumbers(vs1.product, vs2.product);
}
};
this._isVersionString = function (str) {
return str !== undefined && str.build !== undefined;
};
this._compareNumbers = function (n1, n2) {
if (n1 > n2) {
return 1;
} else if (n1 < n2) {
return -1;
} else {
return 0;
}
};
}
还有一些测试:
var v1 = new versionString("1.0");
var v2 = new versionString("1.0.1");
var v3 = new versionString("2.0");
var v4 = new versionString("2.0.0.1");
var v5 = new versionString("2.0.1");
alert(v1.compareTo("1.4.2"));
alert(v3.compareTo(v1));
alert(v5.compareTo(v4));
alert(v4.compareTo(v5));
alert(v5.compareTo(v5));
基于Idan的精彩回答,下面的函数semverCompare通过了语义版本2.0.0的大多数情况。了解更多要点。
function semverCompare(a, b) {
if (a.startsWith(b + "-")) return -1
if (b.startsWith(a + "-")) return 1
return a.localeCompare(b, undefined, { numeric: true, sensitivity: "case", caseFirst: "upper" })
}
它返回:
-1: a < b
0: a == b
1: a > b
我已经创建了这个解决方案,我希望你觉得它有用:
https://runkit.com/ecancino/5f3c6c59593d23001485992e
const quantify = max => (n, i) => n * (+max.slice(0, max.length - i))
const add = (a, b) => a + b
const calc = s => s.
split('.').
map(quantify('1000000')).
reduce(add, 0)
const sortVersions = unsortedVersions => unsortedVersions
.map(version => ({ version, order: calc(version) }))
.sort((a, b) => a.order - b.order)
.reverse()
.map(o => o.version)