接獲 Bug 通報,某網頁下拉選單出現重複選項,追查資料來自兩段 jQuery.map() 吐回字串陣列,以 Array.concat() 合併後再以 jQuery.unique() 排除重複項目,乍看合理。過去沒聽過有 jQuery.unique() 這個函式,但望文生義應類似 LINQ 的 Distinct()。

但實測發現 jQuery.unique() 沒效果,處理後重複項目依舊存在,又胡亂測了幾個案例,發現若重複元素連續排列時倒是會被 $.unique() 移除,有時歪打正著也能試出有效濾掉重複項目的例子。

//狀況模擬
var a=[{k:1,v:"A"},{k:2,v:"B"},{k:3,v:"C"}],b=[{k:1,v:"A"},{k:2,v:"C"},{k:3,v:"D"}];
var m=$.map(a,function(o){return o.v}).concat($.map(b,function(o){return o.v;}));
console.log(m);
$.unique(m);

//意外發現 unique() 只對連續重複元素有效
$.unique(["A","A","B","B","B","C","C"]);
$.unique(["A","A","B","C","B","C","C"]);


(補充: 最下方例子之完整說明為「只有 A 被正確移除重複項目」)

測到這裡,心想這個 $.unique() 也太兩光,是哪門子的 Uinque?但查過官方文件才發現我誤會它:

This function only works on plain JavaScript arrays of DOM elements, and is chiefly used internally by jQuery. You probably will never need to use it.

jQuery.unique() 函式只能用於 DOM 元素陣列(3.0+ 改為 uniqueSort())且屬於 jQuery 內部函式,開發人員可能永遠用不到。換言之,是原作者錯用了! (難怪未曾聽人推薦)

回到 Bug 上,如果 $.unique() 不能用,那麼該怎麼用 JavaScript 濾掉重複值?

在 Stackoverflow 找到一段被讚爆的美妙簡潔寫法,用 JavaScript 陣列物件內建的 indexOf()、filter(),寫成 .filter(function(value, index, self) { return self.indexOf(value) == index; }) 巧妙實現:(IE 9+ 支援,應該全天下瀏覽器都能用)

var a=[{k:1,v:"A"},{k:2,v:"B"},{k:3,v:"C"}],b=[{k:1,v:"A"},{k:2,v:"C"},{k:3,v:"D"}];
var m=$.map(a,function(o){return o.v}).concat($.map(b,function(o){return o.v;}));
m.filter(function (value,index,self) { return self.indexOf(value) == index; });

[2020-10-15 補充] 陸續得到讀者回饋分享,又學到不少好方法:

  • ES6 有個新物件 - Set,寫成[...new Set(arr)]就能巧妙轉換。但,IE 仍然是我心裡最軟的一塊(補聲暗),要以後再找機會用了。(感謝 Ammon、程凱大、Shu Huan Huang 分享)
  • slouchy 分享了一個 JavaScript 去重複大全:JavaScript 取出陣列重複/不重複值的方法 (選擇太多也讓人煩惱,啊啊啊~)

Tips of don't use the internal jQueue.unique() and how to get distinct elements from JavaScript array.


Comments

# by Ammon

黑大怎能略過介紹現代寫法? [...new Set(arr)]

# by slouchy

推薦這篇,有各種陣列取值不取值的操作 https://guahsu.io/2017/06/JavaScript-Duplicates-Array/

# by Tom

IE 9+ 支援,應該全天下瀏覽器都能用 :D

# by Jeffrey

to Ammon, slouchy,感謝分享,已補充於本文。

Post a comment