摘要:專(zhuān)題系列第三篇,講解各種數(shù)組去重方法,并且跟著寫(xiě)一個(gè)前言數(shù)組去重方法老生常談,既然是常談,我也來(lái)談?wù)劇K?lèi)似于數(shù)組,但是成員的值都是唯一的,沒(méi)有重復(fù)的值。
前言JavaScript 專(zhuān)題系列第三篇,講解各種數(shù)組去重方法,并且跟著 underscore 寫(xiě)一個(gè) unique API
數(shù)組去重方法老生常談,既然是常談,我也來(lái)談?wù)劇?/p> 雙層循環(huán)
也許我們首先想到的是使用 indexOf 來(lái)循環(huán)判斷一遍,但在這個(gè)方法之前,讓我們先看看最原始的方法:
var array = [1, 1, "1", "1"]; function unique(array) { // res用來(lái)存儲(chǔ)結(jié)果 var res = []; for (var i = 0, arrayLen = array.length; i < arrayLen; i++) { for (var j = 0, resLen = res.length; j < resLen; j++ ) { if (array[i] === res[j]) { break; } } // 如果array[i]是唯一的,那么執(zhí)行完循環(huán),j等于resLen if (j === resLen) { res.push(array[i]) } } return res; } console.log(unique(array)); // [1, "1"]
在這個(gè)方法中,我們使用循環(huán)嵌套,最外層循環(huán) array,里面循環(huán) res,如果 array[i] 的值跟 res[j] 的值相等,就跳出循環(huán),如果都不等于,說(shuō)明元素是唯一的,這時(shí)候 j 的值就會(huì)等于 res 的長(zhǎng)度,根據(jù)這個(gè)特點(diǎn)進(jìn)行判斷,將值添加進(jìn) res。
看起來(lái)很簡(jiǎn)單吧,之所以要講一講這個(gè)方法,是因?yàn)椤嫒菪院茫?/p> indexOf
我們可以用 indexOf 簡(jiǎn)化內(nèi)層的循環(huán):
var array = [1, 1, "1"]; function unique(array) { var res = []; for (var i = 0, len = array.length; i < len; i++) { var current = array[i]; if (res.indexOf(current) === -1) { res.push(current) } } return res; } console.log(unique(array));排序后去重
試想我們先將要去重的數(shù)組使用 sort 方法排序后,相同的值就會(huì)被排在一起,然后我們就可以只判斷當(dāng)前元素與上一個(gè)元素是否相同,相同就說(shuō)明重復(fù),不相同就添加進(jìn) res,讓我們寫(xiě)個(gè) demo:
var array = [1, 1, "1"]; function unique(array) { var res = []; var sortedArray = array.concat().sort(); var seen; for (var i = 0, len = sortedArray.length; i < len; i++) { // 如果是第一個(gè)元素或者相鄰的元素不相同 if (!i || seen !== sortedArray[i]) { res.push(sortedArray[i]) } seen = sortedArray[i]; } return res; } console.log(unique(array));
如果我們對(duì)一個(gè)已經(jīng)排好序的數(shù)組去重,這種方法效率肯定高于使用 indexOf。
unique API知道了這兩種方法后,我們可以去嘗試寫(xiě)一個(gè)名為 unique 的工具函數(shù),我們根據(jù)一個(gè)參數(shù) isSorted 判斷傳入的數(shù)組是否是已排序的,如果為 true,我們就判斷相鄰元素是否相同,如果為 false,我們就使用 indexOf 進(jìn)行判斷
var array1 = [1, 2, "1", 2, 1]; var array2 = [1, 1, "1", 2, 2]; // 第一版 function unique(array, isSorted) { var res = []; var seen = []; for (var i = 0, len = array.length; i < len; i++) { var value = array[i]; if (isSorted) { if (!i || seen !== value) { res.push(value) } seen = value; } else if (res.indexOf(value) === -1) { res.push(value); } } return res; } console.log(unique(array1)); // [1, 2, "1"] console.log(unique(array2, true)); // [1, "1", 2]優(yōu)化
盡管 unqique 已經(jīng)可以試下去重功能,但是為了讓這個(gè) API 更加強(qiáng)大,我們來(lái)考慮一個(gè)需求:
新需求:字母的大小寫(xiě)視為一致,比如"a"和"A",保留一個(gè)就可以了!
雖然我們可以先處理數(shù)組中的所有數(shù)據(jù),比如將所有的字母轉(zhuǎn)成小寫(xiě),然后再傳入unique函數(shù),但是有沒(méi)有方法可以省掉處理數(shù)組的這一遍循環(huán),直接就在去重的循環(huán)中做呢?讓我們?nèi)ネ瓿蛇@個(gè)需求:
var array3 = [1, 1, "a", "A", 2, 2]; // 第二版 // iteratee 英文釋義:迭代 重復(fù) function unique(array, isSorted, iteratee) { var res = []; var seen = []; for (var i = 0, len = array.length; i < len; i++) { var value = array[i]; var computed = iteratee ? iteratee(value, i, array) : value; if (isSorted) { if (!i || seen !== value) { res.push(value) } seen = value; } else if (iteratee) { if (seen.indexOf(computed) === -1) { seen.push(computed); res.push(value); } } else if (res.indexOf(value) === -1) { res.push(value); } } return res; } console.log(unique(array3, false, function(item){ return typeof item == "string" ? item.toLowerCase() : item })); // [1, "a", 2]
在這一版也是最后一版的實(shí)現(xiàn)中,函數(shù)傳遞三個(gè)參數(shù):
array:表示要去重的數(shù)組,必填
isSorted:表示函數(shù)傳入的數(shù)組是否已排過(guò)序,如果為 true,將會(huì)采用更快的方法進(jìn)行去重
iteratee:傳入一個(gè)函數(shù),可以對(duì)每個(gè)元素進(jìn)行重新的計(jì)算,然后根據(jù)處理的結(jié)果進(jìn)行去重
至此,我們已經(jīng)仿照著 underscore 的思路寫(xiě)了一個(gè) unique 函數(shù),具體可以查看 Github。
filterES5 提供了 filter 方法,我們可以用來(lái)簡(jiǎn)化外層循環(huán):
比如使用 indexOf 的方法:
var array = [1, 2, 1, 1, "1"]; function unique(array) { var res = array.filter(function(item, index, array){ return array.indexOf(item) === index; }) return res; } console.log(unique(array));
排序去重的方法:
var array = [1, 2, 1, 1, "1"]; function unique(array) { return array.concat().sort().filter(function(item, index, array){ return !index || item !== array[index - 1] }) } console.log(unique(array));Object 鍵值對(duì)
去重的方法眾多,盡管我們已經(jīng)跟著 underscore 寫(xiě)了一個(gè) unqiue API,但是讓我們看看其他的方法拓展下視野:
這種方法是利用一個(gè)空的 Object 對(duì)象,我們把數(shù)組的值存成 Object 的 key 值,比如 Object[value1] = true,在判斷另一個(gè)值的時(shí)候,如果 Object[value2]存在的話,就說(shuō)明該值是重復(fù)的。示例代碼如下:
var array = [1, 2, 1, 1, "1"]; function unique(array) { var obj = {}; return array.filter(function(item, index, array){ return obj.hasOwnProperty(item) ? false : (obj[item] = true) }) } console.log(unique(array)); // [1, 2]
我們可以發(fā)現(xiàn),是有問(wèn)題的,因?yàn)?1 和 "1" 是不同的,但是這種方法會(huì)判斷為同一個(gè)值,這是因?yàn)閷?duì)象的鍵值只能是字符串,所以我們可以使用 typeof item + item 拼成字符串作為 key 值來(lái)避免這個(gè)問(wèn)題:
var array = [1, 2, 1, 1, "1"]; function unique(array) { var obj = {}; return array.filter(function(item, index, array){ return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true) }) } console.log(unique(array)); // [1, 2, "1"]ES6
隨著 ES6 的到來(lái),去重的方法又有了進(jìn)展,比如我們可以使用 Set 和 Map 數(shù)據(jù)結(jié)構(gòu),以 Set 為例,ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。它類(lèi)似于數(shù)組,但是成員的值都是唯一的,沒(méi)有重復(fù)的值。
是不是感覺(jué)就像是為去重而準(zhǔn)備的?讓我們來(lái)寫(xiě)一版:
var array = [1, 2, 1, 1, "1"]; function unique(array) { return Array.from(new Set(array)); } console.log(unique(array)); // [1, 2, "1"]
甚至可以再簡(jiǎn)化下:
function unique(array) { return [...new Set(array)]; }
還可以再簡(jiǎn)化下:
var unique = (a) => [...new Set(a)]
此外,如果用 Map 的話:
function unique (arr) { const seen = new Map() return arr.filter((a) => !seen.has(a) && seen.set(a, 1)) }JavaScript 的進(jìn)化
我們可以看到,去重方法從原始的 14 行代碼到 ES6 的 1 行代碼,其實(shí)也說(shuō)明了 JavaScript 這門(mén)語(yǔ)言在不停的進(jìn)步,相信以后的開(kāi)發(fā)也會(huì)越來(lái)越高效。
特殊類(lèi)型比較去重的方法就到此結(jié)束了,然而要去重的元素類(lèi)型可能是多種多樣,除了例子中簡(jiǎn)單的 1 和 "1" 之外,其實(shí)還有 null、undefined、NaN、對(duì)象等,那么對(duì)于這些元素,之前的這些方法的去重結(jié)果又是怎樣呢?
在此之前,先讓我們先看幾個(gè)例子:
var str1 = "1"; var str2 = new String("1"); console.log(str1 == str2); // true console.log(str1 === str2); // false console.log(null == null); // true console.log(null === null); // true console.log(undefined == undefined); // true console.log(undefined === undefined); // true console.log(NaN == NaN); // false console.log(NaN === NaN); // false console.log(/a/ == /a/); // false console.log(/a/ === /a/); // false console.log({} == {}); // false console.log({} === {}); // false
那么,對(duì)于這樣一個(gè)數(shù)組
var array = [1, 1, "1", "1", null, null, undefined, undefined, new String("1"), new String("1"), /a/, /a/, NaN, NaN];
以上各種方法去重的結(jié)果到底是什么樣的呢?
我特地整理了一個(gè)列表,我們重點(diǎn)關(guān)注下對(duì)象和 NaN 的去重情況:
方法 | 結(jié)果 | 說(shuō)明 |
---|---|---|
for循環(huán) | [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] | 對(duì)象和 NaN 不去重 |
indexOf | [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] | 對(duì)象和 NaN 不去重 |
sort | [/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] | 對(duì)象和 NaN 不去重 數(shù)字 1 也不去重 |
filter + indexOf | [1, "1", null, undefined, String, String, /a/, /a/] | 對(duì)象不去重 NaN 會(huì)被忽略掉 |
filter + sort | [/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] | 對(duì)象和 NaN 不去重 數(shù)字 1 不去重 |
優(yōu)化后的鍵值對(duì)方法 | [1, "1", null, undefined, String, /a/, NaN] | 全部去重 |
Set | [1, "1", null, undefined, String, String, /a/, /a/, NaN] | 對(duì)象不去重 NaN 去重 |
想了解為什么會(huì)出現(xiàn)以上的結(jié)果,看兩個(gè) demo 便能明白:
// demo1 var arr = [1, 2, NaN]; arr.indexOf(NaN); // -1
indexOf 底層還是使用 === 進(jìn)行判斷,因?yàn)?NaN ==== NaN的結(jié)果為 false,所以使用 indexOf 查找不到 NaN 元素
// demo2 function unique(array) { return Array.from(new Set(array)); } console.log(unique([NaN, NaN])) // [NaN]
Set 認(rèn)為盡管 NaN === NaN 為 false,但是這兩個(gè)元素是重復(fù)的。
寫(xiě)在最后雖然去重的結(jié)果有所不同,但更重要的是讓我們知道在合適的場(chǎng)景要選擇合適的方法。
專(zhuān)題系列JavaScript專(zhuān)題系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript專(zhuān)題系列預(yù)計(jì)寫(xiě)二十篇左右,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類(lèi)型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。
如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/88313.html
摘要:將元素作為對(duì)象的鍵,默認(rèn)鍵對(duì)應(yīng)的值為如果對(duì)象中沒(méi)有這個(gè)鍵,則將這個(gè)元素放入結(jié)果數(shù)組中去。 前言 數(shù)組去重在日常開(kāi)發(fā)中的使用頻率還是較高的,也是網(wǎng)上隨便一抓一大把的話題,所以,我寫(xiě)這篇文章目的在于歸納和總結(jié),既然很多人都在提的數(shù)組去重,自己到底了解多少呢。又或者是如果自己在開(kāi)發(fā)中遇到了去重的需求,自己能想到更好的解決方案嗎。 這次我們來(lái)理一理怎么做數(shù)組去重才能做得最合適,既要考慮兼容性,...
摘要:專(zhuān)題系列共計(jì)篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類(lèi)型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專(zhuān)題之函數(shù)組合專(zhuān)題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫(xiě)一個(gè)函數(shù),輸入,返回。 JavaScript 專(zhuān)題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專(zhuān)題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...
摘要:寫(xiě)在前面專(zhuān)題系列是我寫(xiě)的第二個(gè)系列,第一個(gè)系列是深入系列。專(zhuān)題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫(xiě)在前面 JavaScript 專(zhuān)題系列是我寫(xiě)的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專(zhuān)題系列共計(jì) 20 篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類(lèi)型判斷、拷貝、最值、扁平、柯里...
摘要:專(zhuān)題系列第九篇,講解如何實(shí)現(xiàn)數(shù)組的扁平化,并解析的源碼扁平化數(shù)組的扁平化,就是將一個(gè)嵌套多層的數(shù)組嵌套可以是任何層數(shù)轉(zhuǎn)換為只有一層的數(shù)組。 JavaScript 專(zhuān)題系列第九篇,講解如何實(shí)現(xiàn)數(shù)組的扁平化,并解析 underscore 的 _.flatten 源碼 扁平化 數(shù)組的扁平化,就是將一個(gè)嵌套多層的數(shù)組 array (嵌套可以是任何層數(shù))轉(zhuǎn)換為只有一層的數(shù)組。 舉個(gè)例子,假設(shè)有個(gè)...
摘要:專(zhuān)題系列第八篇,講解多種方式求數(shù)組的最大值和最小值前言取出數(shù)組中的最大值或者最小值是開(kāi)發(fā)中常見(jiàn)的需求,但你能想出幾種方法來(lái)實(shí)現(xiàn)這個(gè)需求呢提供了函數(shù)返回一組數(shù)中的最大值,用法是值得注意的是如果有任一參數(shù)不能被轉(zhuǎn)換為數(shù)值,則結(jié)果為。 JavaScritpt 專(zhuān)題系列第八篇,講解多種方式求數(shù)組的最大值和最小值 前言 取出數(shù)組中的最大值或者最小值是開(kāi)發(fā)中常見(jiàn)的需求,但你能想出幾種方法來(lái)實(shí)現(xiàn)這個(gè)...
閱讀 3322·2021-11-25 09:43
閱讀 3007·2021-10-15 09:43
閱讀 1964·2021-09-08 09:36
閱讀 2918·2019-08-30 15:56
閱讀 741·2019-08-30 15:54
閱讀 2683·2019-08-30 15:54
閱讀 2971·2019-08-30 11:26
閱讀 1236·2019-08-29 17:27