今天才發現 JavaScript 中文字串排序有個大問題! 下圖是 KendoGrid 在 Chrome 使用 JavaScript 排序的結果,如圖所示,一到七由小到大排序結果為一、七、三、二、五、六、四,既不是依筆劃,也不是依注音: (SQL 的中文定序就區分筆劃跟注音,例如: Chinese_Taiwan_Stroke_CI_AS vs Chinese_Taiwan_Bopomofo_CI_AS 參考)

爬文後得知這是 JavaScript 中文字串排序的己知問題(我 Lag 真大),字串型別有個 localeCompare() 可傳入語系參數進行比較,貌似能解決問題但實際不行。

我寫了測試程式如下。測試字元陣列擷取自 BIG5 字碼表,前面的 BIG5 內碼較小,筆劃較少,並插花加入黑、暗兩個超過五劃的字。分別測試三種排序方法,分別是 直接使用 sort()、sort() 搭配 localeCompare() 不指定語系、sort() 搭配 localeCompare() 指定語系,排序結果以 document.write() 印出。

<html>
<body style="font-size: 9pt">
<script>
var raw = "一乙丁七乃九二人八力十三丈久元六公四黑暗";
var ary = [];
for (var i = 0; i < raw.length; i++)
  ary.push(raw.substr(i, 1));
document.write("原始順序(BIG5/筆劃)<br />")
document.write(JSON.stringify(ary, null ," "));
document.write("<hr />")
ary.sort();
document.write("內建 sort()<br />")
document.write(JSON.stringify(ary, null ," "));
document.write("<hr />")
ary.sort(function(a,b) { return a.localeCompare(b); });
document.write("localeCompare 排序<br />")
document.write(JSON.stringify(ary, null ," "));
document.write("<hr />")
ary.sort(function(a,b) { return a.localeCompare(b, "zh-TW"); });
document.write("localeCompare zh-TW 排序<br />")
document.write(JSON.stringify(ary, null ," "));
</script>
</body>
</html>

以下擷圖分別是 Edge、IE、Firefox 的執行結果,如圖所示,內建 sort() 順序即一開始 KendoGrid 案例的排序結果,而 localeCompare 排序與 localeCompare zh-TW 排序結果相同,雖然與 BIG5 順序有一些出入,至少有遵循筆劃由少到多的順序。

內建 sort() 應是依據 UTF-8 Byte 排序,數字部分依序為一、七、三、二、六、四,使用 Encoding.UTF8.GetBytes() 轉成位元組可證實推測:

同樣的測試在 Chrome 及 Safari 就精采了,sort() 與 localeCompare() 不指定語系的排序結果相同,即上述 UTF8 轉 Byte 後的順序;而 localeCompare() 指定 zh-TW 語系的排序結果讓人莫名其妙,暗字衝到第一個,我說不出是依什麼規則。(2017-10-20 補充,zh-TW 被 Chrome 誤為簡體漢語拼音,請見文章留言及文末補充說明)

另外我還試了用 C# 排序,其結果與 Edge、IE、Firefox 的 localeCompare() 排序順序一致。

由以上測試,我的結論是 localeCompare() 無法跨瀏覽器實現符合預期且一致的中文字串排序,可能因瀏覽器實作不同出現非預期結果,若求保險還是在 C# 或資料庫端處理排序為上。

2017-10-20 更新

貼文後蒙網友 Hsi-Lien Chin、diberium 留言解惑,在 Chrome 使用 localeCompare 排序正體中文時語系參數應傳入 zh-Hant 或 zh-Hant-TW,實測 Chrome 執行 localeCompare(b, "zh-Hant") ,結果就與 Edge/IE/Firefox 一致了。特此感謝。


Comments

# by diberium

看你的测试,chrome是汉语拼音规则… 暗(An) 八(Ba) 丁(Ding) 二(Er) 公(Gomg) 黑(Hei) 九(Jiu) 久(Jiu) 力(Li) 六(Liu) ……

# by Jeffrey

to diberium, 感謝解惑,Chrome localeCompare() 語系參數傳入"zh-Hant",其排序就會和 Edge/IE/Firefox一致,已補充本文。

# by jackyrong

那如果KENDO GRID的列排序中,有中文,数字,英文混合的话,如何比较好的进行排序呢?

# by Jeffrey

to jackyrong, 如果 localeCompare() 排序不符需求,可改在伺服器端排序,再不符要求,可考慮置換字元產生排序專用字串。

Post a comment