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 数据操做中最经常使用的操做,常常温习一下没错的。

相关文章
相关标签/搜索