JavaScript面试题汇总
js中的同步和异步
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务。
异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。异步模式:每一个任务都有一个或多个回调函数,前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务择时不等前一个任务结束就执行,所以程序的执行顺序与任务排列顺序是不一致的、异步的。当我们打开网站时,像图片的加载,音乐的加载,其实都是一个异步任务。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax请求。
JavaScript的异步机制:
- 所有的同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,哪些对应的异步任务,然后结束等待状态,进入执行栈,开始执行
- 主线程不断的重复上面三步
异步编程的实现方式
回调函数:
这是异步编程最基本的方法,当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就成为回调函数。优点是简单。容易理解和部署,缺点是多个回调函数嵌套的时候就会掉造成回调地狱,上下两层的回调函数之间的代码耦合度太高,不利于代码的可维护性。事件监听:
事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。发布/订阅:
嘉定存在一个“信号中心”,某个任务执行完成,就向中心发布一个信号,其他任务可以向中心订阅这个信号,从而知道什么时候自己可以开始执行,又称“观察者模式”Promise
字面上的意思就是承诺。可以将嵌套的回调函数用于链式调用。每一个异步任务返回一个Promise对象,该对象有一个then方法,允许置顶回调函数,但有多个then的链式调用时,同样会造成回调地狱。Promise总共有三种状态:Panding(初始)Fulfilled(成功)Rejected(失败)Generator
ES6提供的一种异步编程解决方案,当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕再将执行权给转移回来。async/await异步终极解决方案)
generator和promise实现的一个自动执行的语法糖。相对于Promise,优势在于处理then的调用链,更信息准确的写出代码,并且也能优雅的解决毁掉地狱问题,缺点,await将异步代码改造成了同步代码,如果多个异步没有依赖性却使用了await会导致性能上的降低,代码没有依赖性的话,完全可以使用Promise.all的方式。async/await与Promise一样,是非阻塞的。async/await是基于Promise实现的,是改良版的Promise,不能用于普通的回调函数。
闭包closure
JavaScript函数内部可以读取外部的变量,但是在函数外部无法直接读取函数内部的局部变量
闭包就是能够读取其他函数内部变量的函数。
由于在JavaScript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单的理解成定义在一个函数内的函数。闭包可以让你在一个内层函数中访问到其他外层函数的作用域,从而使这个外层函数的变量可以被外部访问到。闭包最大的作用就是延续函数内部某变量的生命周期。
闭包的特点:
- 让外部访问函数内部变量
- 让这些变量的值始终保持在内存中,不会再外部函数调用后被自动清楚
- 避免使用全局变量,防止变量名污染
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,导致内存泄露。解决办法是函数提供清除变量的方法,在适当的时机调用它用以删除变量。
防抖(debounce)
防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。
1 | <button onclick="click(() => console.log(Date.now()), 1000)">按钮</button> |
防抖应用场景:
- 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
- 表单验证
- 按钮提交事件
- 浏览器窗口缩放,resize时间(如窗口停止改变大小以后重新计算布局)
节流(throttle)
控制流量,单位时间内事件只能触发一次,一个函数执行一次之后,只有大于设定的执行周期后才会执行第二次,有个需要频繁触发的函数,出于优化性能角度,在规定时间内,只让函数生效一次,后面不生效。所以节流会稀释函数的执行频率。代码实现种在开锁关锁机制。
1 | function throttle(fn, wait) { |
节流应用场景:
- 按钮点击事件
- 拖拽事件
- 滚动事件Scroll
- 计算鼠标移动的距离
宏任务与微任务(事件循环机制)
宏任务: 当前调用栈中执行的代码成为宏任务。(主代码块、定时器等)
I/O setTimeout setInterval setImmediate requestAnimationFrame
微任务: 当前(此次事件循环中)宏任务执行完,下一个宏任务开始之前需要执行的任务,可以理解为回调函数。
process.nextTick MutationObserver Promise.then catch finally
宏任务中的事件放在callback quene(有多个)中,由事件触发现成维护;微任务的时间放在微任务对列中(只有一个)中,由js引擎线程维护。
运行机制:
- 在执行栈中执行第一个宏任务(只有一个任务:执行主线程的JS代码)
- 执行过程中遇到微任务,将微任务添加到微任务对列中(微任务只有一个)
- 当前宏任务执行完毕,立即执行微任务队列中的所有任务
- 当前微任务队列中的任务执行完毕,检查渲染、GUI线程接管渲染
- 渲染完毕后,JS线程接管,开启下一次事件循环,执行下一次宏任务(事件队列中取出)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
})
.then(() => {
console.log('then1')
})
.then(() => {
console.log('then2')
})
console.log('end')
//输出:
start
promise
end
then1
then2
setTimeout
1 | async function async1 () {//函数创建未执行,所以不输出 |
总结:
- 先执行同步和理解执行任务,比如console.log(),new Promise()
- 再依次执行微任务,比如then函数catch函数
- 当微任务执行完后再开始执行宏任务,比如定时器,回调函数等