JS基础知识面试题(3)

异步队列来实现依次执行函数【待思考】

  • 要求:完成函数executeQueue(),使得执行executeQueue(a,b,c);时会依次执行传入的函数a、b、c。
  • 重点:Promise
  • 思路:我们需要实现一个队列,将这些异步函数添加进队列并且管理它们的执行,队列具有First In First Out的特性,也就是先添加进去的会被先执行,接着才会执行下一个(注意跟栈作区别)
    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
    26
    27
    28
    29
    // 异步函数a
    var a = function () {
    return new Promise(function (resolve, reject) {
    setTimeout(function () {
    console.log("a");
    return resolve()
    }, 2000)
    })
    }

    // 异步函数b
    var b = function () {
    return new Promise(function (resolve, reject) {
    setTimeout(function () {
    console.log("b");
    return resolve()
    }, 1000)
    })
    }

    // 异步函数c
    var c = function () {
    return new Promise(function (resolve, reject) {
    setTimeout(function () {
    console.log("c");
    return resolve()
    }, 500)
    })
    }

思路:链式调用then

可参考Promise.prototype.then()与 链式操作,可以解决问题,但不合题意,只是一个思路过渡

1
2
3
4
5
6
7
8
9
//链式调用
a()
.then(function () {
return b()
})
.then(function () {
return c()
})
// a b c

方法1:构建队列

  • 特点:封装方法,可移植到别处使用
  • forEach不能和async/await一起用,async/await要与for-of才是异步的
  • then()里的函数在resolved状态下会被执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 构建队列
    function queue(arr) {
    var sequence = Promise.resolve()
    arr.forEach(function (item) {
    sequence = sequence.then(item)
    })
    }

    // 执行队列
    queue([a, b, c])
    注意:then里放的函数不需要(),联系事件绑定函数进行理解。下面方法2函数猴是需要()的。

方法2:使用async、await构建队列


扩展:顺序执行需要传值的异步任务

  • 场景:有a、b、c三个异步任务,要求必须先执行a,再执行b,最后执行c。且下一次任务必须要拿到上一次任务执行的结果,才能做操作
  • 模拟3个异步函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 异步函数a
    var a = function () {
    return new Promise(function (resolve, reject) {
    setTimeout(function () {
    resolve('a')
    }, 1000)
    })
    }

    // 异步函数b
    var b = function (data) {
    return new Promise(function (resolve, reject) {
    resolve(data + 'b')
    })
    }

    // 异步函数c
    var c = function (data) {
    return new Promise(function (resolve, reject) {
    setTimeout(function () {
    resolve(data + 'c')
    }, 500)
    })
    }

思路:链式操作then

1
2
3
4
5
6
7
8
9
10
11
//链式调用
a()
.then(function (data) {
return b(data)
})
.then(function (data) {
return c(data)
})
.then(function (data) {
console.log(data)// abc
})

方法1:构建队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 构建队列
function queue(arr) {
var sequence = Promise.resolve()
arr.forEach(function (item) {
sequence = sequence.then(item)
})
return sequence
}

// 执行队列
queue([a, b, c])
.then(data => {
console.log(data)// abc
})

执行第一轮的时候 sequencea的Promise对象
执行第二轮时, sequence 为aPromise成功时才进行的b函数最后返回的b的Promise对象,且b函数接受aPromise成功时传入的data。(b函数作为then()中的item,会自动接收aPromise成功时传入的data作为参数,所以不用另外传参!)
执行第三轮时, sequence 同上,为c的Promise对象
最后返回的sequence是c的Promise对象

方法2:使用async、await构建队列

使用await而不是then时就要注意自行传参,这里使用res暂存bc俩函数中所传参数:

1
2
3
4
5
6
7
8
9
10
11
12
async function queue(arr) {
// 函数中传(resolve中所传)的值保存在res中
let res = null
for (let promise of arr) {
res = await promise(res)
}
return await res // await abc自动转换为Promise对象,resolved状态下值为abc
}
queue([a, b, c])
.then(data => {
console.log(data)// abc
})

图片的懒加载

图片的懒加载也就是 延迟加载 ,图片没有出现在页面可视区域内之前都只加载一个内存小的图片用于占位,等到图片出现在页面可视区域内时才加载真正的图片。

react的react-lazy-load库

原生js

面试题:预加载 与 懒加载

  • **预加载(异步加载)**:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。(预加载 慕课
    • 例子: loading页面显示时就是正在提前加载众多图片的时候,等待切换到下一个页面时众多图片已经加载完毕。
    • 实现方法
    1. 显示loading页面时,将需要加载的图片src放入数组中
    2. 图片的load和error事件函数中就可以放进度条的进度计算遍历图片数组加载图片(赋值给image对象的src)(error中也是正常进度,不然error就卡住永远无法加载结束)
    3. 图片数组遍历加载结束,隐藏loading页,正常显示网页,此时所有图片已加载过一次,用户查看时会直接从本地读取
  • 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
  • 两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载
  • 懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力

异步进阶

描述event loop机制

  • 第一遍可以只到宏任务微任务(**执行宏任务(同步任务)=》清空微任务=》清空宏任务=》如有微任务则继续清空微任务=》…**)
  • 第二遍深入可加上和宏任务微任务与DOM渲染的关系
  • 不要混着讲,怕乱

宏任务与微任务的区别

  • 宏任务:script setTimeout setInterval ajax DOM 事件
    • 除script,其他都是浏览器引擎来执行,因为宏任务都是Web API,不属于ES语法
  • 微任务:Promise.then()(对于前端来说)async/await
    • js引擎来执行
    • 注意:Promise的函数体是定义时就立即执行的,then()是微任务。async函数体是同步,await当行代码也是同步,但await后的代码时异步(微任务)
  • 宏任务比微任务执行的更早,js引擎处理速度比浏览器引擎快,所以 异步任务中微任务执行时机先于宏任务
  • 微任务在DOM渲染触发,宏任务在DOM渲染触发

Promise的三种状态,如何变化

  • pending,resolved,rejected
  • pending–>resolved 或 pending–>rejected
  • 变化不可逆

Promise catch 连接 then

catch执行与否的问题

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
26
27
28
29
30
31
// 第一题
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 3

// 第二题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2) // catch正常返回resolved
}).then(() => {
console.log(3)
})
// 1 2 3

// 第三题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).catch(() => { // 注意这里是 catch,而catch正常返回resolved
console.log(3)
})
// 1 2

async/await 语法问题

1
2
3
4
5
6
7
async function fn() {
return 100
}
(async function () {
const a = fn() // promise 值是100
const b = await fn() // 100
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(async function () {
console.log('start') // 同步,1
const a = await 100 // 同步,2
console.log('a', a) // 异步(微任务),5
const b = await Promise.resolve(200) // 同步,3
console.log('b', b) // 异步(微任务),6
const c = await Promise.reject(300) // 同步,报错!后面的都不会执行。4
console.log('c', c) // 不执行
console.log('end') // 不执行
})() // 执行完毕,打印出那些内容?

// start
// a 100
// b 200
// 报错,Uncaught (in promise) 300

Promise 和 setTimeout 顺序

  • 同步任务先于异步任务
  • 宏任务(setTimeout)先于微任务(Promise.then)
  • 但是同步任务也属于宏任务,所以 执行宏任务(同步任务)=》清空微任务(Promise.then)=》清空宏任务(setTimeout)
1
2
3
4
5
6
7
8
9
console.log(100) // 同步,1
setTimeout(() => { // 异步,宏任务,4
console.log(200)
})
Promise.resolve().then(() => { // 异步,微任务,3
console.log(300)
})
console.log(400) // 同步,2
// 100 400 300 200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 关于宏任务与微任务的执行方式:取出一个宏任务(同步任务)=》清空微任务=》清空宏任务
console.log(100) // 同步,1
setTimeout(() => { // 异步,宏任务,5
console.log(200)
})
Promise.resolve().then(() => { // 异步,微任务,3
console.log(300)
})
setTimeout(() => { // 异步,宏任务,6
console.log(400)
})
Promise.resolve().then(() => { // 异步,微任务,4
console.log(500)
})
console.log(600) // 同步,2
// 100 600 300 500 200 400

【重点】async/await 执行顺序问题

  • 注意:
    • async()调用时函数体立即执行,await当行代码为同步,await后是微任务。
    • Promise 的函数体(即new 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
25
26
27
28
29
// 网上很经典的面试题
async function async1 () {
console.log('async1 start') // 2
await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行

// 微任务,上面有 await ,下面就变成了“异步”,类似 callback 的功能
console.log('async1 end') // 6
}

async function async2 () {
console.log('async2') // 3
}

console.log('script start') // 同步,1

setTimeout(function () { // 异步,宏任务
console.log('setTimeout') // 8
}, 0)

async1() // async的函数体是立即执行的

new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
console.log('promise1') // 4,Promise 的函数体会立刻执行
resolve()
}).then (function () { // 异步,微任务
console.log('promise2') // 7
})

console.log('script end') // 5

宏任务(同步,async)=》清空微任务(await后,then)=》宏任务(setTimeout)

script start
async1 start
async2
promise1
script end
(同步代码执行完毕)
async1 end
promise2
setTimeout

,