今天才發現 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>
<bodystyle="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 語系的排序結果讓人莫名其妙,暗字衝到第一個,我說不出是依什麼規則。
另外我還試了用 C# 排序,其結果與 Edge、IE、Firefox 的 localeCompare() 排序順序一致。
由以上測試,我的結論是 localeCompare() 無法跨瀏覽器實現符合預期且一致的中文字串排序,可能因瀏覽器實作不同出現非預期結果,若求保險還是在 C# 或資料庫端處理排序為上。