唉!你好前端面试!(ㄒoㄒ)同步异步

609 阅读6分钟

问题一:同步、异步、阻塞、非阻塞,傻傻搞不清

  • 同步:提交请求=》等待服务器处理=》处理完毕返回=》顺次执行功能函数(在此期间未得到明确回复之前不能处理其他函数或功能)【调用者主动等待这个调用结果】

  • 异步:请求通过事件触发=》请求服务器处理同时顺次执行接下来(不需要此函数执行结果的其他函数)=》处理完毕后通知返回结果

    同步和异步关注的是消息的通信机制

  • 阻塞:调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回

  • 非阻塞:在不能立刻得到结果之前,该调用不会阻塞当前线程

    阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态

同步、异步、阻塞、非阻塞是四种概念,可以进行随机的组合,通常情况下开发过程中遇到的是:同步阻塞,异步非阻塞的情况

问题二:前端开发中常见的同步阻塞和异步非阻塞的场景及解决方案

  • 异步处理的四种常见情况:

    1. 回调函数,很多内置函数都支持接受回调函数来异步代码
    2. 事件监听,大部分Dom操作,click事件等都是异步
    3. 订阅与发布,常见于angularvue中,比如:用on来监听事件,emit来发布事件,常用于父子组件交互
    4. Promise ES6的新特性,通过resolvereject来执行异步操作,常与asyncawait配合使用
  • 再次强调同步和异步是一种消息通知机制(在通知的过程中,一方是否会痴痴的坚守)(同步、异步、阻塞、非阻塞)组合而非同等

    • 同步阻塞:A调用B,B处理获得结果,才返回给A。A会一直等待B的回应
    • 异步非阻塞:A调用B,A仅仅是调用无需等待,有结果后在通过状态来告知,或通过回到函数进行处理
  • 异步非阻塞应用场景

    • 回调函数callback、通过setTimeout函数实现超时加载

      function text() {			//||
          setTimeout(() => {			//||
              console.log("第一项输出");       //||
          }, 3000);			        //||
      }					//||
      text();					//||正常的执行逻辑应当先输出1,在输出2
      console.log("第二项输出");		//||但在这里setTimeout存在异步非阻塞
      					//||输出结果:先输出2,再输出1
      

      //改变输出顺序,执行同步阻塞(正常逻辑)
      function text(cb) {			//||
          setTimeout(() => {		        //||
              console.log("第一项输出");	//||
              cb && cb();			//||
          }, 3000)				//||
      }					//||
      text(function () {			//||通过回调函数来执行同步阻塞的顺序输出
          console.log("第二项输出");		//||当存在回调函数时
      })					//||在setTimeout函数内部执行回调函是的内容
      
    • 回调地狱问题,如何解决(函数回调执行自身【递归】,多次循环产生回调地狱)

      function move(ele, direction, target, cb) {
              //getComputedStyle是干什么的?
              //parseInt为什么能够将0px转换成0?
              let start = parseInt(window.getComputedStyle(ele, null)[direction])
              let speed = (target - start) / Math.abs(target - start) * 2 
              //通过相减计算正负,再进行除以自身的绝对值得到正负通过乘运算赋予speed
              function fn() {
                  start += speed
                  ele.style[direction] = start + "px"
                  if (start === target) {
                      // 改变输出结果,执行回调函数
                      cb && cb();
                  } else {
                      //这个函数和setTimeout实现每秒递增位移有什么区别
                      window.requestAnimationFrame(fn)
                  }
              }
              fn()
      
          }
      let ele = document.querySelector(".box")
      //这里开始出现层级回调嵌套,无尽的地狱模式
      move(ele, "left", 400, function () {
              console.log("向右运动完成");
              move(ele, "top", 400, function () {
                  console.log("向下运动完成");
                  move(ele, "left", 0, function () {
                      console.log("向左运动完成");
                      move(ele, "top", 0, function () {
                          console.log("向上运动完成");
                      })
                  })
              })
          })
      
      
    • 图片或数据的懒加载【通过Promise对象进行异步加载处理】

      Promise对象接受两个参数,resolve[成功返还状态信息]和reject[失败返回状态信息及错误原因]

      then方法同样有两个resolvereject两个参数,但这里返回的是具体的信息

      function loadImg() {
              return new Promise((res, rej) => { //直接将加载完成后的结果存入res、rej中
                  let img = new Image();
                  img.src = "./个人职业照.jpg"
                  img.style.width = "300px"
                  img.onload = function () {
                      setTimeout(() => {
                          document.querySelector("body").appendChild(img)
                          res("加载成功") //加载成功后的返还值
                      }, 1000)
                  }
                  img.onerror = function () {
                      rej("加载失败") //加载失败后的返还值
                  }
              })
          }
          // loadImg函数的return为promise对象,所有可以在其自身调用then方法获取res和rej的返还值
          loadImg().then(res => {
              console.log(res);
          }, err => {
              console.log(err);
          })
      
    • asyncawait实现异步之【事件循环深度理解

      • asyncGenerator函数的语法糖,使用async表示同步,在函数内部使用await表示异步

        优点:①内置执行器,自动执行;②async代替*,await代替yield,语义化;③返回值是一个Promise对象,可以进行对应的链式操作

      • await意思是async wait(异步等待),只能在使用async定义的函数里面使用。

        await会解析Promise对象的值,async会等所有的await命令的Promise对象执行完,才会发生状态改变

      	async function async1() {
              console.log("async start");
              await async2();
              console.log("async end");
              return 'async return'
          }
          async function async2() {
              console.log("async2");
          }
      
          console.log("script start");            //||立即执行
      
          setTimeout(function () {                //为什么最后执行?
              console.log("setTimeout");
          }, 0)
      
          async1().then(function (message) {      //执行async1函数,先输出log信息,接着执行async2函数输出async内部数据
              console.log(message);               //执行完async2为什么跳到Promise对象
          })
      
          new Promise(function (res) {            //new对象立即执行,输出log(promise1),跳到最后script end?为什么跳走
              console.log("promise1");
              res();
          }).then(function () {
              console.log("promise2");
          })
      
          console.log("script end");
      
      //以上代码输出结果
      script start	        //主线程 立即执行
      async start		//setTimeout函数存在异步推入任务队列,主线程顺次执行async1函数log
      async2			//主线程顺次执行await async2函数,await所在行本次执行,后续为异步需等待,放入任务队列
      promise1		//执行async外部的下一个函数,new Promise对象 立即打印,之后的then推入任务队列
      script end		//顺次执行非异步函数log("script end")
      async end		//主线程完成后,开始执行任务队列 FIFO  第一个async内部await后面的函数, return 为then方法输出,再次被推入任务队列
      promise2		//执行任务队列,promise对象的then,log("promise2")
      async return	        //接着执行第二次推入任务队列的async的then方法  res
      setTimeout		//最后执行setTimeout
      

      async会被await分为两部分执行,虽为异步非阻塞,但事件循环机制未被改变(可以这样理解吗)

      好难理解的事件循环(Event Loop

      1. JS首先判断代码是同步还是异步,同步进入主线程,异步进入任务队列
      2. 同步任务进入主线程后一直执行,直到主线程空闲后,才会去任务队列中查看是否有可执行的异步任务,如果有就顺次推入到主线程中执行
      3. 事件循环是一个先进先出(FIFO)队列,说明回调时按照它们被加入队列的顺序执行的