刷题是我们提高自己技术的一种好方法。下面的问题很有挑战性和“指导性”。如果你知道该怎样回答,那意味着自己的水平很好,但是如果你发现自己答错了,并能够搞清楚 为什么 错,我认为那会更好!
- 数组排序比较
看以下数组,在各种排序操作后都下输出什么?
1const arr1 = ['a', 'b', 'c'];
2const arr2 = ['b', 'c', 'a'];
3
4console.log(
5 arr1.sort() === arr1,
6 arr2.sort() == arr2,
7 arr1.sort() === arr2.sort()
8);
答案和解析
答案:true, true, false
这里有几个概念在起作用。首先,array 的 sort 方法对原始数组进行排序,并返回对该数组的引用。这意味着当你调用 arr2.sort() 时, arr2 数组内的对象将会被排序。
当你比较对象时,数组的排序顺序并不重要。由于 arr1.sort() 和 arr1 指向内存中的同一对象,因此第一个相等测试返回 true 。第二个比较也是如此: arr2.sort() 和 arr2 指向内存中的同一对象。
在第三个测试中, arr1.sort() 和 arr2.sort() 的排序顺序相同;但是,它们指向内存中的不同对象。因此,第三个测试的评估结果为 false 。
- set 的对象
把下面的 Set 对象转成一个新的数组,最后输出什么?
1const mySet = new Set([{ a: 1 }, { a: 1 }]);
2const result = [...mySet];
3console.log(result);
答案和解析
答案:[{a: 1}, {a: 1}]
尽管 Set 对象确实会删除重复项,但是我们用 Set 创建的两个值是对内存中不同对象的引用,尽管它们有相同的键值对。这与 { a: 1 } === { a: 1 }的结果为 false 的原因相同。
如果集合是用对象变量创建的,例如 obj = {a: 1} , new Set([obj,obj]) 将会只有一个元素,因为数组中的两个元素都引用了内存中的同一对象。
- 深层对象的可变性
下面的对象代表用户 Joe 和他的狗 Buttercup。我们用 Object.freeze 保存对象,然后尝试更改 Buttercup 的 name。最后会输出什么?
1const user = {
2 name: 'Joe',
3 age: 25,
4 pet: {
5 type: 'dog',
6 name: 'Buttercup',
7 },
8};
9
10Object.freeze(user);
11
12user.pet.name = 'Daffodil';
13
14console.log(user.pet.name);
答案和解析
答案:Daffodil
Object.freeze 将会使对象浅冻结,但不会保护深层属性不被修改。在这个例子中,不能对 user.age 进行修改,但是对 user.pet.name 进行修改却没有问题。如果我们觉得需要保护一个对象,避免其“从头到尾”发生改变,则可以递归地应用 Object.freeze 或使用现有的“深度冻结”库。
- 原型继承
在下面的代码中,有一个 Dog 构造函数。我们的 dog 显然有 speak 这个操作。当我们调用 Pogo 的 speak 时,会输出什么?
1function Dog(name) {
2 this.name = name;
3 this.speak = function() {
4 return 'woof';
5 };
6}
7
8const dog = new Dog('Pogo');
9
10Dog.prototype.speak = function() {
11 return 'arf';
12};
13
14console.log(dog.speak());
答案和解析
答案:woof
每次创建一个新的 Dog 实例时,我们都会将该实例的 speak 属性设置为返回字符串 woof 的函数。由于每次我们创建一个新的 Dog 实例时都要设置该值,因此解释器不会沿着原型链去找 speak 属性。结果就不会使用 Dog.prototype.speak 上的 speak 方法。
- Promise.all 的解决顺序
在这个问题中,我们有一个 timer 函数,它返回一个 Promise ,该 Promise 在随机时间后解析。我们用 Promise.all 解析一系列的 timer 。最后的输出是什么,是随机的吗?
1const timer = a => {
2 return new Promise(res =>
3 setTimeout(() => {
4 res(a);
5 }, Math.random() * 100)
6 );
7};
8
9const all = Promise.all([timer('first'), timer('second')]).then(data =>
10 console.log(data)
11);
答案和解析
答案: [“first”, “second”]
Promise 解决的顺序与 Promise.all 无关。我们能够可靠地依靠它们按照数组参数中提供的相同顺序返回。
Question 6: Reduce Math
6. Reduce 数学
数学时间!输出什么?
1const arr = [x => x * 1, x => x * 2, x => x * 3, x => x * 4];
2
3console.log(arr.reduce((agg, el) => agg + el(agg), 1));
Answer and Explanation
答案和解析
答案: 120
使用 Array#reduce 时,聚合器的初始值(在此称为 agg )在第二个参数中给出。在这种情况下,该值为 1 。然后可以如下迭代函数:
1 + 1 * 1 = 2(下一次迭代中聚合器的值)
2 + 2 * 2 = 6(下一次迭代中聚合器的值)
6 + 6 * 3 = 24(下一次迭代中聚合器的值)
24 + 24 * 4 = 120(最终值)
因此它是 120。
Question 7: Short-Circuit Notification(s)
7. 短路通知
让我们向用户显示一些通知。以下代码段输出了什么?
1const notifications = 1;
2
3console.log(
4 `You have ${notifications} notification${notifications !== 1 && 's'}`
5);
答案和解析
答案:“You have 1 notificationfalse”
不幸的是,我们的短路评估将无法按预期工作: notifications !== 1 && ‘s’ 被评估为 false ,这意味着我们实际上将会输出 You have 1 notificationfalse 。如果希望代码段正常工作,则可以考虑条件运算符: ${notifications === 1 ? ‘’ : ‘s’} 。
- 展开操作和重命名
查看以下代码中有单个对象的数组。当我们扩展该数组并更改 0 索引对象上的 firstname 属性时会发生什么?
1const arr1 = [{ firstName: 'James' }];
2const arr2 = [...arr1];
3arr2[0].firstName = 'Jonah';
4
5console.log(arr1);
答案和解析
答案: [{ firstName: “Jonah” }]
展开操作符会创建数组的浅表副本,这意味着 arr2 中包含的对象与 arr1 所指向的对象相同。所以在一个数组中修改对象的 firstName 属性,也将会在另一个数组中更改。
- 数组方法绑定
在以下情况下会输出什么?
1const map = ['a', 'b', 'c'].map.bind([1, 2, 3]);
2map(el => console.log(el));
答案和解析
答案:1 2 3
当 [‘a’, ‘b’, ‘c’].map 被调用时,将会调用 this’ 值为 '[‘a’,‘b’,‘c’] 的 Array.prototype.map 。但是当用作 引用 时, Array.prototype.map 的引用。
Function.prototype.bind 会将函数的 this 绑定到第一个参数(在本例中为 [1, 2, 3] ),用 this 调用 Array.prototype.map 将会导致这些项目被迭代并输出。
- set 的唯一性和顺序
在下面的代码中,我们用 set 对象和扩展语法创建了一个新数组,最后会输出什么?
1const arr = [...new Set([3, 1, 2, 3, 4])];
2console.log(arr.length, arr[2]);
答案和解析
答案:4 2
set 对象会强制里面的元素唯一(集合中已经存在的重复元素将会被忽略),但是不会改变顺序。所以 arr 数组的内容是 [3,1,2,4] , arr.length 为 4 ,且 arr[2] (数组的第三个元素)为 2 。
最后免费送大家一波福利
这些面试经验,希望会给大家带来帮助,好好做准备,放平心态,平时有用功的话,其实面试并不难!