有一道面试题如下,给复杂嵌套的数组去重。
const arr = [1,'1',1,true,false,true,'2','1'];
const arr1 = [{a:1},{b:1},{a:1},{b:1},{b:1},{b:1}];
const arr2 = [{a:1,b:2},{b:1},{a:1,b:2},{a:1,b:2},];
const arr3 = [[1,{a:1}],[2],[3],[1,{a:1}]];
const arr4 = [{a:1,b:2},{b:1},{b:2,a:1}];
乍看之下很简单,就好像我们经常练习的题目一样,使用ES6的Set来去重
[...new Set(arr)]
然而测试一下,发现没有什么用,Set类型存储的是原始值或对象引用,对于里面嵌套的数组/对象根本不会比较。
这里涉及到一个严格比较(strict equality)的概念。
JavaScript 有两种比较方式:严格比较运算符和转换类型比较运算符。对于严格比较运算符(===)来说,仅当两个操作数的类型相同且值相等为 true,而对于被广泛使用的比较运算符(==)来说,会在进行比较之前,将两个操作数转换成相同的类型。对于关系运算符(比如 <=)来说,会先将操作数转为原始值,使它们类型相同,再进行比较运算。
对于这道面试题来说,实现的作用类似与lodash库里面的isEqual,比较的是里面的两个值是否相等。自己写了个简单的实现:
//此处只考虑标准的数组/对象,并且数组/对象的元素只可能是嵌套的基础类型/数组/对象,不考虑null,arguments,类数组等其他类型
//判断是否是对象或数组
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
//有人写了个更简单的,我就拿来用了
function isEqual(object,other){
//对于简单类型,直接严格相等判断
if(object === other){
return true;
}
// 对于数组/对象类型需要进行比较
let objType = Object.prototype.toString.call(object);
let otherType = Object.prototype.toString.call(object);
let isSameTag = objType===otherType;
if(!isSameTag){
return false;
}
if(objType === '[object Array]'){
if(object.length !== other.length){
return false;
}
for(let i=0,len=object.length;i<len;i++){
if(!isEqual(object[i],other[i])){
return false;
}
}
return true;
}else if(objType === '[object Object]'){
if(Object.keys(object).length !== Object.keys(other).length){
return false;
}
for(let item in object){
if(!Object.prototype.hasOwnProperty.call(other,item)){
return false;
}
if(!isEqual(object[item],other[item])){
return false;
}
}
return true;
}else{
return false;
}
//此处只考虑标准的数组/对象,并且数组/对象的元素只可能是嵌套的简单类型/数组/对象,不考虑null,symbol,类数组等其他类型
}
function unique(arr){
let res = [];
for(let item of arr){
let bool = res.some(e=>{
return isEqual(e,item)
})
if(!bool) res.push(item);
}
return res;
}
const arr = [1,'1',1,true,false,true,'2','1'];
const arr1 = [{a:1},{b:1},{a:1},{b:1},{b:1},{b:1}];
const arr2 = [{a:1,b:2},{b:1},{a:1,b:2},{a:1,b:2},];
const arr3 = [[1,{a:1}],[2],[3],[1,{a:1}]];
const arr4 = [{a:1,b:2},{b:1},{b:2,a:1}];
console.log(unique(arr));
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3));
console.log(unique(arr4))
意外发现的一个知识点:
let a = Symbol("b");
Object.prototype.toString.call(a);//"[object Symbol]"
Symbol.prototype.valueOf.call(a) == Symbol.prototype.valueOf.call(a)//true
参考文档: