我们可以挑战自己,使自己成长为JavaScript开发者的一个方法是用测验题来练习!下面的问题旨在挑战和指导我们的工作。下面的问题旨在具有挑战性和指导性。如果你确切地知道如何回答每一个问题,那很好,但是如果你错了一些,并且知道了你为什么会错,我认为那就更好了!
问题1:数组排序比较
考虑以下数组。在各种排序条件下,什么会被记录下来?
const arr1 = ['a', 'b', 'c'];
const arr2 = ['b', 'c', 'a'];
console.log(
arr1.sort() === arr1,
arr2.sort() == arr2,
arr1.sort() === arr2.sort()
);
答案和解释
答案:真,真,假
这里有几个概念在起作用。首先,数组sort 方法对你的原始数组进行排序,同时返回该数组的引用。这意味着当你写arr2.sort() ,arr2 数组对象就被排序了。
然而,事实证明,在你比较对象时,数组的排序顺序并不重要。因为arr1.sort() 和arr1 在内存中指向同一个对象,所以第一个平等测试返回true 。这对第二次比较也是如此:arr2.sort() 和arr2 在内存中指向同一个对象。
在第三个测试中,arr1.sort() 和arr2.sort() 的排序是相同的;但是,它们仍然指向内存中的不同对象。因此,第三次测试的结果是:false 。
问题2:一个对象集
考虑以下Set 的对象分散到一个新的数组中。什么会被记录下来?
const mySet = new Set([{ a: 1 }, { a: 1 }]);
const result = [...mySet];
console.log(result);
答案和解释
答案:是的。 [{a: 1}, {a: 1}]
虽然一个Set 对象确实会删除重复的东西,但是我们创建Set 的两个值是对内存中不同对象的引用,尽管有相同的键值对。这与{ a: 1 } === { a: 1 } 是false 的原因相同。
应该注意的是,如果这个集合是用一个对象变量创建的,比如说obj = { a: 1 } ,new Set([ obj, obj ]) 将只有一个元素,因为数组中的两个元素都引用了内存中的同一个对象。
问题3:深度对象可变性
考虑以下对象,代表一个用户,Joe,和他的狗,Buttercup。我们使用Object.freeze 来保存我们的对象,然后尝试突变Buttercup的名字。什么会被记录下来?
const user = {
name: 'Joe',
age: 25,
pet: {
type: 'dog',
name: 'Buttercup',
},
};
Object.freeze(user);
user.pet.name = 'Daffodil';
console.log(user.pet.name);
答案和解释
答案:Daffodil
Object.freeze 将对一个对象进行浅层冻结,但不会保护深层属性不被变异。在这个例子中,我们将不能突变 ,但我们突变 是没有问题的。如果我们觉得需要保护一个对象不被 "一直向下 "突变,我们可以递归应用 ,或者使用一个现有的 "深度冻结 "库。user.age user.pet.name Object.freeze
问题4:原型继承
在这个问题中,我们有一个Dog 的构造函数。我们的狗显然知道说话的命令。在下面的例子中,当我们要求Pogo说话时,什么会被记录下来?
function Dog(name) {
this.name = name;
this.speak = function () {
return 'woof';
};
}
const dog = new Dog('Pogo');
Dog.prototype.speak = function () {
return 'arf';
};
console.log(dog.speak());
答案和解释
答案:woof
每次我们创建一个新的Dog 实例时,我们都会将该实例的speak 属性设置为一个返回字符串woof 的函数。由于每次我们创建一个新的Dog 实例时都会设置这个属性,解释器就不必在原型链上找一个speak 属性。因此,Dog.prototype.speak 上的speak 方法从未被使用。
问题5:Promise.all Resolve Order
在这个问题中,我们有一个timer 函数,该函数返回一个Promise ,该函数在一个随机的时间后解析。我们用Promise.all 来解决一个数组的timers 。什么会被记录下来,还是随机的?
const timer = (a) => {
return new Promise((res) =>
setTimeout(() => {
res(a);
}, Math.random() * 100)
);
};
const all = Promise.all([timer('first'), timer('second')]).then((data) =>
console.log(data)
);
答案和解释
答案:是的。 ["first", "second"]
Promises解析的顺序对Promise.all 并不重要。我们可以可靠地指望它们以数组参数中提供的相同顺序返回。
问题6:减少数学
数学时间!什么会被记录下来?
const arr = [(x) => x * 1, (x) => x * 2, (x) => x * 3, (x) => x * 4];
console.log(arr.reduce((agg, el) => agg + el(agg), 1));
答案和解释
答案是120
使用Array#reduce ,聚合器的初始值(这里命名为agg )在第二个参数中给出。在这种情况下,就是1 。然后我们可以对我们的函数进行如下迭代。
1 + 1 * 1 = 2(聚合器在下一次迭代中的值)
2 + 2 * 2 = 6(聚合器在下一次迭代中的值)
6 + 6 * 3 = 24(聚合器在下一次迭代中的值)
24 + 24 * 4 = 120(最终值)。
那么,就是120了!
问题7:短路通知(s)
让我们给我们的用户显示一些通知!在下面的片段中,什么会被记录下来?
const notifications = 1;
console.log(
`You have ${notifications} notification${notifications !== 1 && 's'}`
);
答案和解释
答案:"你有1个通知false"
不幸的是,我们的短路评估在这里不会如愿:notifications !== 1 && 's' 评估为false ,这意味着我们实际上将记录You have 1 notificationfalse 。如果我们想让我们的片段正确工作,我们可以考虑使用条件运算符:${notifications === 1 ? '' : 's'} 。
问题8:传播和重命名
考虑下面这个带有单个对象的数组。当我们展开该数组并改变0-index对象的firstName 属性时会发生什么?
const arr1 = [{ firstName: 'James' }];
const arr2 = [...arr1];
arr2[0].firstName = 'Jonah';
console.log(arr1);
答案和解释
答案:传播 [{ firstName: "Jonah" }]
展开创建了一个数组的浅层拷贝,意味着arr2 中包含的对象仍然指向内存中arr1 对象所指向的同一个对象。所以,改变一个数组中的对象的firstName 属性,会被另一个数组中的对象也反映出来。
问题9:数组方法绑定
在下面的情况下,什么会被记录下来?
const map = ['a', 'b', 'c'].map.bind([1, 2, 3]);
map((el) => console.log(el));
答案和解释
答案:1 2 31 2 3
['a', 'b', 'c'].map this ,当被调用时,将调用Array.prototype.map ,其值为['a', 'b', 'c'] 。但是,当作为一个引用,而不是被调用时,['a', 'b', 'c'].map 只是一个对Array.prototype.map 的引用。
Function.prototype.bind 将把函数的 绑定到第一个参数上(在这个例子中,就是 ),用这样的 ,调用 ,结果是这些项目被迭代和记录。this [1, 2, 3] this Array.prototype.map
问题10:集合唯一性和排序
在下面的问题中,我们使用Set 对象和传播语法来创建一个新的数组。什么会被记录下来(要考虑:项目是否被强制要求是唯一的? 它们是否被排序?)
const arr = [...new Set([3, 1, 2, 3, 4])];
console.log(arr.length, arr[2]);
答案和解释
答案:4 2
Set 对象会强制要求元素唯一(已经在集合中的重复元素会被忽略),但不会改变顺序。结果arr 数组将是[3, 1, 2, 4] ,意味着arr.length 是4 ,arr[2] (数组的第三个元素)是2 。