面试题系列均来自鱼皮的知识星球——编程导航

JS 如何顺序执行 10 个异步任务?

JS 中可以使用 Promise 和 async/await 来顺序执行异步任务。

使用 Promise 可以通过 then() 方法的链式调用来实现顺序执行异步任务,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function asyncTask1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('Async task 1');
resolve();
}, 1000);
});
}

function asyncTask2() {
return new Promise(resolve => {
setTimeout(() => {
console.log('Async task 2');
resolve();
}, 2000);
});
}

// 顺序执行异步任务
asyncTask1().then(() => {
return asyncTask2();
}).then(() => {
// 执行完异步任务1和异步任务2后的逻辑
});

使用 async/await 可以将异步任务看作同步代码来执行,例如:

1
2
3
4
5
6
7
async function runAsyncTasks() {
await asyncTask1();
await asyncTask2();
// 执行完异步任务1和异步任务2后的逻辑
}

runAsyncTasks();

在这里,runAsyncTasks() 函数会先执行异步任务 1,等待异步任务 1 完成后再执行异步任务 2。

React 组件间怎么进行通信?

React 组件间通信可以通过以下方式实现:

  1. Props 传递:父组件可以通过 Props 将数据传递给子组件,从而实现数据通信。
  2. Context:Context 是 React 提供的一种组件间通信的机制,可以通过 Context 在组件树中传递数据,避免 Props 层层传递的麻烦。
  3. Refs:Refs 允许我们直接操作组件实例或者 DOM 元素,从而实现组件间通信。
  4. 自定义事件:可以通过自定义事件的方式实现组件间的通信。在组件中定义一个事件,当需要在其他组件中触发这个事件时,可以通过回调函数的方式实现。
  5. 全局状态管理:使用全局状态管理工具(如 Redux、Mobx)来管理组件状态,从而实现组件间通信。

需要根据实际场景选择适合的通信方式。

介绍一下 JS 中 setTimeout 的运行机制?

setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行。它接受两个参数:第一个参数是需要执行的代码块,第二个参数是代码块的延迟时间(以毫秒为单位)。它返回一个整数,表示定时器timer的编号,可以用来取消该定时器。是一个异步函数。

1
2
3
4
5
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
console.log(3);

最后的打印顺序是:1 3 2 无论setTimeout的执行时间是0还是1000,结果都是先输出3后输出2。

任务队列

一个先进先出的队列,它里面存放着各种事件和任务。 所有任务可以分成两种,一种是同步任务,另一种是异步任务。

同步任务

在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

  1. 输出 如:console.log()
  2. 变量的声明
  3. 同步函数:如果在函数返回的时候,调用者就能够拿到预期的返回值或者看到预期的效果,那么这个函数就是同步的。

异步任务

  1. setTimeout和setInterval
  2. DOM事件
  3. Promise
  4. process.nextTick
  5. fs.readFile
  6. http.get
  7. 异步函数:如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。

优先关系

异步任务要挂起,先执行同步任务,同步任务执行完毕才会响应异步任务。

JS执行机制

由于 JS 是单线程,所以同一时间只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行。 为了避免因为某些长时间任务造成的无意义等待,JS 引入了异步的概念,用另一个线程来管理异步任务。

同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程; 等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如 ajax 请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环;

定时器也是一种异步任务,通常浏览器都有一个独立的定时器模块,定时器的延迟时间就由定时器模块来管理,当某个定时器到了可执行状态,就会被加入主线程队列。

setTimeout 注册的函数 fn 会交给浏览器的定时器模块来管理,延迟时间到了就将 fn 加入主进程执行队列,如果队列前面还有没有执行完的代码,则又需要花一点时间等待才能执行到 fn,所以实际的延迟时间会比设置的长; 如在 fn 之前正好有一个超级大循环,那延迟时间就不是一丁点了。

setInterval 的实现机制跟 setTimeout 类似,只不过 setInterval 是重复执行的。 对于 setInterval(fn, 100) 容易产生一个误区: 并不是上一次 fn 执行完了之后再过 100ms 才开始执行下一次 fn。 事实上,setInterval 并不管上一次 fn 的执行结果,而是每隔 100ms 就将 fn 放入主线程队列; 而两次 fn 之间具体间隔多久就不一定了,跟 setTimeout 实际延迟时间类似,和 JS 执行情况有关。