前端高级面试问到关于异步的问题

608 阅读6分钟

前端高级面试问到关于异步的问题

导言:说到异步,你可能会想到event-loop Promise Async/Await等,下面我们就来聊聊这几个问题:

  • 什么是单线程,和异步有什么关系
  • 什么是event-loop
  • 目前JS解决异步有哪些方案
  • 如果使用jquery如何解决异步
  • Promise标准
  • async/await的使用

什么是单线程,和异步有什么关系

  1. 单线程 —— 只有一个单线程,只能做一件事情(代码演示)
// 循环运行期间,JS 执行和 DOM 渲染暂时卡顿
var i, sum = 0;
for (i = 0; i < 100000000; i++) {
    sum += i;
}
console.log(sum);

//alert 不处理,JS 执行和 DOM 渲染暂时卡顿
console.log(1);
alert('hello');
console.log(2)
  1. 原因 —— 避免DOM渲染冲突
  • 浏览器需要渲染DOM
  • JS可以修改DOM结构
  • JS执行的时候,浏览器DOM暂时不会渲染
  • 两段JS代码也不能同时执行(都修改DOM就冲突了)
  • webworder支持多线程,但不能访问DOM
  1. 解决方案 —— 异步(代码演示)
console.log(100)
setTimeout(function () { 
   console.log(200)           //反正 1000ms 之后执行
},1000)                         //先不管他,先让其他JS代码运行
console.log(300)
console.log(400)
console.log(100)
$.ajax({
    url:'xxx',
    success: function (result) {     //ajax 加载完才执行
        console.log(result)         //先不管他,先让其他JS代码运行
    }
})
console.log(300)
console.log(400)

异步存在的问题

问题一:没有按照格式执行,可读性差
问题二:callback 中不容易模块化

问题解答:

1.单线程就是只能做一件事,两段js不能同时执行
2.原因就是为了避免dom渲染冲突
3.异步是一种无奈的解决方案,虽然有很多问题

什么是event-loop

知识串联:
    1.单线程-同时只能做一件事
    2.原因避免DOM渲染冲突
    3.解决方案:异步
    4.实现方式:event-loop

文字解释

  • 事件轮询,JS实现异步的具体方案
  • 同步代码,同步执行
  • 异步函数先放在异步队列中
  • 待同步函数 执行完毕 ,轮询执行 异步队列函数

实例分析

第一种:

    setTimeout(function () {
        console.log(100)
    })
    console.log(200)

第二种:

    setTimeout(function () {
        console.log(1)
    },100)
    setTimeout(function () {
        console.log(2)
    })
    console.log(3)

第三种:

    $.ajax({
        url: 'xxxx',
        success : function (result) {
            console.log('a')
        }
    })
    setTimeout(function () {
        console.log('b')
    },100)
    setTimeout(function () {
        console.log('c')
    })
    console.log('d')

问题解答

1.事件轮询,JS异步的解决方案
2.什么是异步队列,何时放入异步对列
3.轮询的过程

目前JS解决异步有哪些方案

1.jQuery deferred
2.Promise
3.Async/Await
4.Generator

如果使用jquery如何解决异步

  • 是否使用过jquery的Deferred

    1. jquery 1.5的变化
    • jquery 1.5的变化 - 1.5 之前

          var ajax = $.ajax({
              url:'XXX',
              success: function () {
                  console.log('success1')
                  console.log('success2')
                  console.log('success3')
              },
              error: function () {
                  console.log('error')
              }
          })
          console.log(ajax) //返回一个XHR对象
      
    • jquery 1.5的变化 - 1.5 之后

      var ajax = $.ajax('xxxx');
      ajax.done(function () {
          console.log('success1')
      }).fail(function () {
          console.log('error')
      }).done(function () {
          console.log('success2')
      })
      console.log(ajax)  //返回一个Deferred对象
      
      //很像Promise的写法
      var ajax = $.ajax('xxx')
      ajax.then(function () {
          console.log('success 1')
      },function () {
          console.log('error 1')
      }).then(function () {
          console.log('success 2')
      },function () {
          console.log('error 2')
      })
      

    问题解答:

      1.无法改变JS异步和单线程的本质
      2.只能从写法上杜绝callback这种形式
      3.它是一种语法糖形式,但是解耦了代码
      4.很好体现:开放封闭原则
    
    1. 使用jQuery Deferred
        // 给出一段很简单的异步操作,使用setTimeout函数
        var wait = function () {
            var tast = function () {
                console.log('执行完成')
            }
            setTimeout(tast, 2000)
        }
        wait()
        //新增需求:要在执行完成之后进行某些特别复杂的操作,代码可能很多,而且分好几个步骤
    
        function waitHandle () {
            var dtd = $.Deferred() //创建一个Deferred 对象
            
            var wait = function (dtd) {  //要求传入一个Deferred 对象
                var tast = function () {
                    console.log('执行完成')
                    dtd.resolve()       //表示异步任务已经完成
                    //dtd.reject()      //表示异步任务失败或出错
                }
                setTimeout(tast, 2000)
                return dtd
            }
            //注意,这里一定要有返回值  
            return wait(dtd)
        }
        
        var w = waitHandle()
        w.then(function () {
            console.log('ok 1')
        },function () {
            console.log('error 1')
        }).then(function () {
            console.log('ok 2')
        },function () {
            console.log('error 2')
        })
        
        //还有 dtd.done dtd.fail
    

    问题解答:

      1.总结:dtd的API可分为两类,用意不同
      2.第一类:dtd.resolve dtd.reject
      3.第二类:dtd.then dtd.done dtd.fail
      4.这两类应该分开,否则后果很严重
      5.可以在上面的代码最后执行dtd.reject() 试一下后果
      6.使用dtd.promise
    
        function waitHandle () {
            var dtd = $.Deferred()
            var wait = function (dtd) {
                var tast = function () {
                    console.log('执行完成')
                    dtd.reject()
                }
                setTimeout(tast, 2000)
                return dtd.Promise() //注意,这里返回的是Promise,而不是直接返回Deferred对象
            }
            return wait(dtd)
        }    
        
    
        var w = waitHandle()  //经过上面的改动,w接收一个Promise对象
        $.when(w)
            .then(function () {
                console.log('task 1')
            })
            .then(function () {
                console.log('task 2')
            })
        //w.reject() //执行这个会报错
    

    3.初步引入Promise概念

    问题解答:

      1.可以jquery 1.5 对ajax的改变举例
      2.说明如何简单封装,使用Deferred
      3.说明Promise和Deferred的区别
    

Promise 的标准

Promise 的基本使用和原理

  • 基本语法的回顾
function loadImg (src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resovle(img)
        }
        img.onerror = function () {
            reject()
        }
        img.src = src
    })
    return promise
}

var src = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583670709077&di=30593978e23e2d1c6830cbdd7d2bdd18&imgtype=0&src=http%3A%2F%2Fwww.piaodown.com%2Fupload%2F20187%2F2018072626027685.png'
var result = loadImg(src)

result.then(function (img) {
    console.log(img.width)
},function () {
    console.log('failed')
}).then(function (img) {
    console.log(img.height)
})

  • 异常捕获
//then只能接收一个参数,最后统一用catch 异常捕获
result.then(function (img) {
    console.log(img.width)
}).then(function (img) {
    console.log(img.hieght)
}).catch(function (ex) {
    //最后统一用 catch
    console.log(ex)
})
  • 多个串联
var src1 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583670709077&di=30593978e23e2d1c6830cbdd7d2bdd18&imgtype=0&src=http%3A%2F%2Fwww.piaodown.com%2Fupload%2F20187%2F2018072626027685.png'
var result1 = loadImg(src1)
var src2 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583739408720&di=71d5005f34a42c7ee37e05e4f48f4c83&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F68%2F61%2F300000839764127060614318218_950.jpg'
var result2 = loadImg(src2)

result1.then(function (img) {
    console.log('第一张图片', img.width)
    return result2
}).then(function (img) {
    console.log('第二张图片', img.height)
}).catch(function (ex) {
    console.log(ex)
})


  • Promise.all和Promise.race
//Promise.all 接收一个Promise 对象的数组
//待全部完成之后,统一执行 success
Promise.all([result1, result2]).then(datas => {
//接收data是一个数组,依次包含多个Promise 返回的内容
    console.log(datas[0])
    console.log(datas[1])
})

//Promise.race 接收一个包含多个Promise对象数组
//只要有一个完成,就执行success
Promise.race([result1, result2]).then(data => {
//data 即最先执行完成的Promise的值
    console.log(data)
})

  • Promise标准

    1.关于"标准"的闲谈

      - 任何技术推广需要使用一套标准来支撑
      - 如html,js,css,http等,无规矩不成方圆
      - 任何不符合标准的东西,终将被用户抛弃
      - 不要挑战标准,不要自造标准
    

    2.状态的变化

      - 三种状态:pending fulfilled rejected
      - 初始状态为pending
      - pending 变为 fulfilled ,或者pending 变为 reject
      - 状态变化不可逆
    

    3.then

      - Promise 实例必须实现then这个方法
      - then() 必须可以接受两个函数参数
      - then() 返回的必须是一个Promise 实例
    

    代码实例:

    var result = loadImg(src)
    result.then(function (img) {
        console.log(img.width)
    },function () {
        console.log('failed')
    }).then(function (img) {
        console.log(img.height)
    })
    
    result1.then(function (img) {
        console.log('第一张图片加载完')
        return result2
    }).then(function (img) {
        console.log('第二张图片加载完')
    })
    

问题解答:

  • 基本语法
  • 如何异常捕获
  • 多个串联 - 链式执行的好处
  • Promise.race 和 Promise.all
  • Promise的标准-状态变化,then函数

async/await 的使用

  • then只是将callback拆分
var w = waitHandle()
w.then(function () {
    console.log('ok 1')
},function () {
    console.log('err 1')
}).then(function () {
    console.log('ok 2')
},function () {
    console.log('err 1')
})
  • async/await是最直接的同步写法
const load = async function () {
    const result1 = await loadImg(src1)
    console.log(result1)
    const resutl2 = await loadImg(src2)
    console.log(result2)
}
load()
  • 语法

    1.使用await,函数必须用async标识

    2.await后面跟的是一个Promise实例

    3.需要babel-polyfill

    function loadImg () {
        var promise = new Promise(function (resolve, reject) {
            var img = document.createElement('img')
            img.onload = function () {
                resovle(img)
            }
            img.onerrror = function () {
                reject()
            }
            img.src = img
        })
        return promise
    }
    
    var src1 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583670709077&di=30593978e23e2d1c6830cbdd7d2bdd18&imgtype=0&src=http%3A%2F%2Fwww.piaodown.com%2Fupload%2F20187%2F2018072626027685.png'
    var src2 = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583739408720&di=71d5005f34a42c7ee37e05e4f48f4c83&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F68%2F61%2F300000839764127060614318218_950.jpg'
     
    const load = async function () {
        const result1 = await loadImg(src1)
        consoel.log(result1)
        const result2 = await loadImg(src2)
        console.log(result2)
    }
    load()
    

问题解答:

a.基础语法
b.使用Promise,并没有跟Promise冲突
c.完全是同步写法,再也没有回调函数
d.但是改变不了JS单线程,异步的本质

总结:以上是关于当前异步的解决方案,如果觉得文章不错,希望你能给小🐟的文章轻轻的点个赞,希望能够更多的面试者带来帮助,谢谢你!