140期
1. 是否使用过generator,请举例说明?
2. 怎么实现图片的懒加载?
3. 请手写一个防抖函数?
上面问题的答案会在第二天的公众号(程序员每日三问)推文中公布
也可以小程序刷题,已收录500+面试题及答案
139期问题及答案
1. 是否使用过运算符:??,它在什么场景下使用?
是的,??
是空值合并运算符(Nullish Coalescing Operator)。它在处理空值(null
或 undefined
)时非常有用。该运算符返回其左侧操作数,除非其左侧操作数为 null
或 undefined
,此时它会返回右侧操作数。
const value = someVariable ?? defaultValue;
在这里,value
将获得 someVariable
的值,但如果 someVariable
是 null
或 undefined
,则会获得 defaultValue
的值。
使用场景:
默认值赋予:
const username = inputUsername ?? 'Guest';
当你希望给变量赋予默认值,但不想在变量为假值(如空字符串
''
或数字0
)时赋予默认值时,??
是一个更安全的选择,因为它只对null
或undefined
生效。
函数参数默认值:
function greet(name) {
name = name ?? 'Guest';
console.log(`Hello, ${name}!`);
}
在函数参数中,可以使用
??
设置默认值,避免使用短路操作符||
时产生的意外结果。
对象属性取值:
const age = user.age ?? 18;
当你希望从对象中取值,并在该值为
null
或undefined
时提供默认值时,??
也很方便。
总体而言,空值合并运算符是对空值处理的一种更严格的方式,确保只在值为 null
或 undefined
时提供默认值。在某些场景下,它能够更精准地处理默认值的赋予。
2. 微任务和宏任务执行的时机是什么样的?
微任务(Microtasks)和宏任务(Macrotasks)是异步编程中的两个概念,它们分别在事件循环的不同阶段执行。
宏任务(Macrotasks):
宏任务包括整体的代码块、setTimeout、setInterval、requestAnimationFrame、I/O 操作等,它们会被放入宿主环境(如浏览器或Node.js)的任务队列中等待执行。宿主环境会在执行栈为空时,从队列中取出一个宏任务执行。
例如:
console.log('Start');
setTimeout(function () {
console.log('Timeout');
}, 0);
console.log('End');
在上面的例子中,setTimeout
的回调函数将作为宏任务被推入队列,它会在主代码执行完毕后被执行,所以结果是 'Start'
、'End'
、'Timeout'
。
微任务(Microtasks):
微任务包括 Promise 回调、process.nextTick(Node.js 独有)、Object.observe(已废弃)、MutationObserver 等。微任务会在当前宏任务执行完毕且在下一个宏任务开始之前执行。
例如:
console.log('Start');
Promise.resolve().then(function () {
console.log('Promise Microtask 1');
}).then(function () {
console.log('Promise Microtask 2');
});
console.log('End');
在上面的例子中,Promise 的回调函数将作为微任务被推入队列,它会在当前宏任务执行完毕后执行,所以结果是 'Start'
、'End'
、'Promise Microtask 1'
、'Promise Microtask 2'
。
执行时机总结:
宏任务:
在执行栈为空时,从宏任务队列中取出一个任务执行。
微任务:
在每个宏任务执行完毕后,在下一个宏任务开始之前执行微任务队列中的所有任务。
这样的设计使得微任务能够在宏任务之间执行,从而保证了一些异步操作的执行时机。例如,在同一个事件循环中,Promise 的回调函数会在当前宏任务执行完毕后立即执行,而不必等到下一个宏任务。
大家也可以在评论区给出下面代码的输出顺序:
console.log('Start');
// 宏任务1
setTimeout(function () {
console.log('Timeout 1');
// 微任务1
Promise.resolve().then(function () {
console.log('Promise Microtask 1');
});
}, 0);
// 宏任务2
setTimeout(function () {
console.log('Timeout 2');
// 微任务2
Promise.resolve().then(function () {
console.log('Promise Microtask 2');
});
}, 0);
console.log('End');
3. 请写一个快速排序?
快速排序(Quick Sort)是一种常用的排序算法,它使用分治的思想,在数组中选择一个基准元素,将小于基准的元素放在基准的左侧,大于基准的元素放在基准的右侧,然后对左右两个子数组分别递归地进行快速排序。
以下是一个简单的JavaScript实现:
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
// 选择基准元素
const pivot = arr[0];
// 分区
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 递归地对左右两个子数组进行快速排序
return [...quickSort(left), pivot, ...quickSort(right)];
}
// 示例
const unsortedArray = [5, 3, 7, 2, 8, 4, 1, 6];
const sortedArray = quickSort(unsortedArray);
console.log(sortedArray);
这个例子中,我们选择数组的第一个元素作为基准,然后将小于基准的元素放在左侧,大于基准的元素放在右侧。接着,对左右两个子数组分别递归地进行快速排序。这个过程一直递归下去,直到子数组的长度为1或0,最终合并排序完成。
请注意,这里使用了ES6的展开运算符...
来合并左、基准和右三个部分。实际上,快速排序算法的性能在平均情况下是很好的,但在最坏情况下可能达到O(n^2)。一些改进的版本(如随机选择基准)可以在某些情况下提高性能。