JavaScript:關於數組的四道面試題

已知後端返回一個對象數組,格式相似這樣:前端

const arr = [
  { id: 1, name: 'a', birth: 896630400000 },
  { id: 2, name: 'b', birth: 725817600000 },
  ...,
]

按要求寫代碼:面試

  1. 按照 name 屬性降序排序(字母順序從大到小)
  2. 去除 id 屬性相同的元素(如出現重複,後出現的覆蓋先出現的,不要求保留原始順序)
  3. 過濾出全部的95後(birth >= 1995年1月1日)
  4. 如何作前端分頁

因爲公司是大數據處理業務,數組相關操做使用仍是挺頻繁的。這是咱們公司前端JS面試題中的編程題部分。雖然都是基礎,可是答上來的彷佛很少。下面咱們依次分析。算法

  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼
  • 在看答案前不本身先試試麼

數組排序

  • 按照 name 屬性降序排序(字母順序從大到小)

看到排序固然能夠聯想到 Array#sort。你們都寫過 [1, 3, 2].sort((a, b) => a - b),可是這個數組有點特殊,它是對象數組。排序規則也有些特殊:是按照字符串排序,並且是降序排序。編程

考點:segmentfault

  1. Array#sort
  2. String#localCompare

Array#sort 這個這個毫無疑問。手寫排序算法的人會被一律刷掉。難點在於在 sort 的回調函數中如何比較字符串的大小。首先字符串相減是不對的,對於 'a'-'b' 這個表達式,JS 引擎會首先把字符串 'a''b' 轉換爲數字,由於 JS 標準規定只有數字才能相減。因而:後端

+'a' // => NaN
+'b' // => NaN
'a' - 'b' = NaN - NaN = NaN

有些同窗會提出挨個比較字符編碼的方法,此法雖然可行,可是你要知道字符串長度是不固定的,你必須判斷前一位相等後比下一位,因而不得不引入循環。這並非面試官想要的答案。數組

雖然字符串不可相減,可是字符串卻能夠用大於小於號比較大小,並且比較的方式就是字典序依次比較。函數

'a' < 'b' // true
'a' < 'a' // false
'aa' < 'ab' // true

使用比較運算符比較字符串的問題就是你沒法一次性獲得他們的準確關係。若是 a < b 返回 false,還有兩種可能,一種是 a 等於 b,另外一種是 a 大於 b。因此若是使用比較運算符,你就必須在 sort 的回調函數中判斷兩次。大數據

arr.sort((a, b) => a.name < b.name ? 1 : a.name > b.name ? -1 : 0); // 記得是逆序

最優方案是使用 String#localeCompare,這個函數能夠一次性返回正負零,完美適用於 Array#sort編碼

arr.sort((a, b) => b.name.localeCompare(a.name)); // 逆序,因此 b 在 a 前面

數組去重

  • 去除 id 屬性相同的元素(如出現重複,後出現的覆蓋先出現的,不要求保留原始順序)

網上看過一些面試題的同窗,看到數組去重確定立刻會聯想到 Set,這個玩意用來作數組去重簡直是帥到沒朋友

[...new Set([1, 1, 2, 3, 3, 1])] // => [1, 2, 3]

然而這道題卻不按套路出牌:它要去重的是對象數組,不是簡單的數字數組。

遇到這道題時能夠按照這樣的思路來:題目說須要按照字段 id 去重,那麼 id 是必需拿出來單獨處理的。可是 id 去重卻不是去 id 自己,而是去 id 所關聯的對象,那麼確定須要一個 id 到對象的映射。想到映射這一層,那麼很容易聯想到對象和 Map

考點:

  1. 對象 key 的惟一性
  2. Map

若是使用對象,笨一點的話能夠這樣:

const map = {};
const resultArr = [];

arr.forEach(x => {
  if (!map[x]) {
    map[x] = true;
    resultArr.push(x);
  }
}

這種寫法不徹底符合題設要求,它總會保留最早出現的值。若是要保留後出現的值,能夠把數組先反轉再遍歷。

arr.concat().reverse().forEach(...) // reverse 會改變數組的值,用以前需先克隆原始數組

這裏可能有同窗會提到 reduceRight,這裏建議若是你不能保證回調函數爲純函數,請不要使用 map 或 reduce。

另一個方式是始終用後面的對象覆蓋前面的。

const map = {};
arr.forEach(x => {
  map[x] = x;
}

const resultArr = [];
for (let key in map) {
  // 嚴謹的話,這裏要用 hasOwnProperty 去除父級對象上的 key
  resultArr.push(map[key]);
}

分爲兩步,前一步有些相似把數組轉換爲對象,後一步就是取一個對象裏的全部值。前者能夠用 Object#assignObject#fromEntries 代替,後者就是 Object#values。因此簡潔的寫法是這樣:

Object.values(Object.assign(...arr.map(x => ({ [x.id]: x }))));

對象其實不是一個完美的解決方案,由於對象的 key 都會被強制轉換爲字符串,雖然題設中的 id 都是數字類型,可是難保後面會出現字符串的數字。完美的解決方案是 Set 的同胞兄弟 Map

[...new Map(arr.map(x => [x.id, x])).values()] // 注意 Map#values 返回的不是數組而是一個迭代器,須要手工再轉爲數組

數組過濾

  • 過濾出全部的95後(birth >= 1995年1月1日)

考點:

  1. Array#filter
  2. Date

此題明顯比前面兩題簡單很多,但此題卻不多有人徹底答對,問題就出在日期對象的使用上

我在以前的博文 說過 Date 對象的坑:當你使用數字年月日構造 Date 對象時記得月份是從 0 開始的;當你使用字符串構造 Date 對象時請使用斜槓(/)避免出現時區問題。

arr.filter(x => x.birth >= new Date(1995, 0, 1);

前端分頁

  • 如何作前端分頁

考點:

  1. Array#slice
  2. 分頁的基本概念

所謂「分頁」就是從數組中截取要顯示的部分放到頁面上顯示,截取就是 slice,但彷佛好多人把 slice 和 splice 搞混。

slice 是截取數組的一部分,但不改變原數組,兩個參數都是下標。splice 雖然也能夠做爲截取數組使用,但 splice 會改變原數組,並且 splice 要求的第二個參數是截取項的長度。slice 就是截取數組,而 splice 一般用做數組中部的插入刪除操做。

slice 和 splice,中間差了一個字母 p,用法含義大不相同。

const from = (頁碼 - 1) * 每頁條數;
arr.slice(from, from + 每頁條數);

數組對象有不少的原生方法,數組操做是 JS 數據操做中最經常使用的操做,常常溫習一下沒錯的。

相關文章
相關標籤/搜索