这是来自字节俩道面试题。。。汗颜

313 阅读4分钟

1. 让代码成立

题目:

// 使之成立
var [a, b] = {a:1, b:2}
console.log(a, b) // 输出1 2

这题比较抽象,许多人看的一脸懵。。。

分析

我们先来看一下原本的对象解构方法:

var {a, b} = {a:1, b:2}
console.log(a, b) // 输出1 2

我们知道在js中,数组是具有内置迭代器的可迭代对象。通过使用 for...of 循环,可以遍历数组中的每个元素:

var arr = [1, 2, 3, 4, 5, 6];
for (let i of arr) {
    console.log(i); // 输出 1 2 3 4 5 6
}

那我们能不能去遍历对象来操作呢?

image.png

可以看到我们对普通对象使用for...of方法会报错,它说明普通对象没有迭代属性,而数组是具有迭代属性Symbol.iterator,我们可以在window上看到。

数组的原型: image.png

那思路不就有了吗?能不能给对象加上迭代属性使得可以像数组一样迭代遍历,以数组的形式返回对象的值,并利用解构赋值语法将数组中的元素赋值给变量。

// 过程如下:
// {a:1, b:2} => [1, 2]
const [a, b] = [1 , 2]
console.log(a, b) // 输出1 2

迭代属性:Symbol.iterator

  1. for...in 循环用于迭代对象的可枚举属性。它遍历对象及其原型链上所有可枚举的属性,包括继承的属性。通常用于遍历对象属性键值对。
  2. for...of 循环用于迭代可迭代对象(如数组、字符串、Set、Map 等)。它遍历对象的可迭代属性值,而不是属性键。它只能用于迭代实现了迭代器接口的对象。

关于迭代器的详细内容可以去es6官网查看:es6 中的 Iterator 和 for...of 循环

对象增加迭代器:

我们可以给对象的原型上添加迭代器[Symbol.iterator]来实现对象的迭代遍历

Object.prototype[Symbol.iterator] = function() {
    return Object.values(this)[Symbol.iterator]();
}

var [a, b] = {a: 1, b: 2};
console.log(a, b); // 输出 1 2
  • Object.values(this):调用了 Object.values 方法,它会返回一个给定对象自身的所有可枚举属性值的数组,它会返回 this 指向的对象中所有的值组成的数组。

  • [Symbol.iterator]:使用了 Symbol.iterator 符号,它是在迭代器协议中使用的特殊符号。当在一个对象上调用 [Symbol.iterator] 时,它会返回一个迭代器对象,使得该对象可以被迭代(比如在 for...of 循环中使用)。

所以,我们返回 Object.values(this)[Symbol.iterator]() 可以将对象的值转换为一个数组,然后获取该数组的迭代器对象。这个迭代器对象可以用于遍历数组中的值。

真就汗流浃背了。。。。

image.png

2. 数组去重

let arr = [1, 1, '2', 3, 1, 2,
    { name: '张三', id: { n: 1 }, a: undefined },
    { name: '张三', id: { n: 1 }, a: undefined },
    { name: '张三', id: { n: 2 } },
]

喂,醒一醒,这可是字节的题目,没你想的那么简单用set去重就可以了。。。

let newArr = [...new Set(arr)]  
console.log(newArr);

image.png

可以看到我们去重并不完整,因为Set只能去重原始类型的数据,对于引用类型没用。

方法一:子元素全部转化为字符串去重再转化为对象

// 转化为字符串
let arr2 = arr.map((item) => {
  return JSON.stringify(item);
})
// 字符串去重
let newArr = new Set(arr2);
// 转化为对象
newArr = Array.from(newArr).map((item) => {
  return JSON.parse(item)
})
console.log(newArr);

image.png

通过结果来看,这种方法是有缺陷的**,对于 undefined 它无法转化为字符串!

方法二:通过原型遍历

整体思路为: 从一个数组中移除重复的元素,并返回一个只包含唯一值的新数组。同时,它利用辅助函数equal来判断两个值是否相等。

let arr = [1, 1, '2', 3, 1, 2,
    { name: '张三', id: { n: 1 }, a: undefined },
    { name: '张三', id: { n: 1 }, a: undefined },
    { name: '张三', id: { n: 2 } },
]

function uniqueArr(arr) {
    let res = []
    for (let item of arr) {
        let isFind = false
        // 对于每个元素,通过equal函数检查它是否与res数组中的任何元素相等:
        for (let resItem of res) {
        // 如果相等,则将isFind设置为 true,表示找到了重复项,跳出内层循环。
            if (equal(item, resItem)) {
                isFind = true
                break;
            }
        }
        // 如果不相等,则将该元素添加到 `res` 数组中。
        if (!isFind) res.push(item);
    }
    // 返回去重结果
    return res
}

function equal(v1, v2) {
     // 判断两个值是否都是对象类型
    if ((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)) { // 都是引用类型
        // 属性数量不同,直接返回 false
        if (Object.keys(v1).length !== Object.keys(v2).length)
            return false
        // 遍历第一个对象的属性,递归比较对应属性的值,如果有不相等的情况则返回 false。
        for (let key in v1) {
            if (v2.hasOwnProperty(key)) {
                // 递归比较它们的属性。
                if (!equal(v1[key], v2[key])) {
                    return false
                }
            }
            else {
                return false
            }
        }
        // 如果两个对象完全相等(包括属性和属性值),则返回 true。
        return true
    }
    else {
        // 如果两个值不都是对象类型,则直接比较它们是否相等。
        return v1 === v2
    }
}

console.log(uniqueArr(arr))

结果:

image.png

总结:

image.png

image.png