陌上寒

陌上寒个人博客

谈一谈javascript异步

新的一周开始了,从今天开始研究一下javascript的异步相关内容,感兴趣的请关注

什么是js异步?

我们知道JavaScript的单线程的,这与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所谓”单线程”,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
ajax的同步请求就会导致浏览器产生假死,因为它会锁定浏览器的UI(按钮,菜单,滚动条等),并阻塞所有用户的交互,jquery中的ajax有这样一个同步请求的功能,一定要慎用,尤其是在请求的数据量很大的时候,要避免使用同步请求。
举几个栗子🌰感受一下异步
后台接口使用easy-mock,官方地址:https://easy-mock.com/
ajax使用axios,基本代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>javascript异步</title>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
  <button>点击</button>
  <script>
    {
      let myData = null
      //ajax请求
      function ajax() {
        axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock')
          .then(data => {
            console.log("ajax返回成功");// handle success
            myData = data.data
            console.log(myData);

          })
          .catch(error => {
            // console.log(error); // handle error
            console.log("ajax返回失败");
          })
      }
    }
  </script>
</body>
</html>

我们通过添加一些js来看下效果,

异步-定时器

      console.log(myData);
      setTimeout(() => {
        console.log('定时器');
      }, 2000);
      console.log(myData);

输出,应该没什么悬念

//null
//null
//定时器

执行顺序:
先执行第一个 console.log(myData);
然后遇到了定时器,将定时器挂起(就是暂停了这个定时器)
继续执行第二个 console.log(myData);
没有可以执行的js代码,回头把挂起的任务继续执行下去
继续看下一个栗子

异步-ajax

      console.log(myData);
      ajax()
      console.log(myData);

看下输出,依然没有悬念

//null
//null
//ajax返回成功
//{success: true, data: {…}}(这是接口返回的数据,我们不必关心返回的具体内容,只要知道返回了就好,陌上寒注)

执行顺序和上面的定时器基本类似,不在此赘述。
将两个栗子合并,我们看下

      console.log(myData);
      ajax()
      setTimeout(() => {
        console.log('定时器');
      }, 2000);
      console.log(myData);

输出,

//null
//null
//ajax返回成功
//{success: true, data: {…}}
//定时器

发现问题了吗?两个异步函数相遇了,先执行谁?谁跑的快就先执行谁?
也可以这么说,其实这引发了另外一个知识点,

任务队列和事件循环

两个 console.log(myData);是同步执行的,他们都在js的主线程上执行,
在主线程之外还存在一个任务队列,任务队列中存放着需要异步执行的内容
当主线程运行完毕之后,就会去执行任务队列中的任务(不断的重复扫描)直到任务队列清空

观察这段代码

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

输出:1,3,2,这没什么可解释的
再看一段代码

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

输出:2,1,为什么会这样?
console.log(2);在主线程中,先执行,
setTimeout(function(){console.log(1);}, 0);放在了任务队列中,
只有在主线程执行完了才会去执行任务列队中的内容

为什么主线程的任务执行完了后需要不断的扫描任务列队中的内容呢?

看这段代码,有助于你的理解

      console.log(myData);
      ajax()
      setTimeout(() => {
        console.log('定时器');
      }, 2000);
      console.log(myData);
      const btn = document.querySelector('button')
      btn.onclick = () => {
        console.log("点击了");
      }

我们为button按钮添加了点击事件,在浏览器刷新的同时不停地对按钮进行点击操作(当然是手动点击)
看下输出:

//null
//null
//(10次输出)点击了
//ajax返回成功
//{success: true, data: {…}}
//定时器
//点击了

这样是不是可以理解为什么主线程要去循环扫描任务列队了?
事件循环的每一轮称为一个tick(有没有联想到vue中的nextTick?)
当产生用户交互(鼠标点击事件,页面滚动事件,窗口大小变化事件等等),ajax,定时器,计时器等,会向事件循环中的任务队列添加事件,然后等待执行,

前端异步有哪些场景?

  1. 定时任务:setTimeout,setInverval
  2. 网络请求:ajax请求,img图片的动态加载
  3. 事件绑定或者叫DOM事件,比如一个点击事件,我不知道它什么时候点,但是在它点击之前,我该干什么还是干什么。用addEventListener注册一个类型的事件的时候,浏览器会有一个单独的模块去接收这个东西,当事件被触发的时候,浏览器的某个模块,会把相应的函数扔到异步队列中,如果现在执行栈中是空的,就会直接执行这个函数。
  4. ES6中的Promise

什么时候需要异步:

  1. 在可能发生等待的情况
  2. 等待过程中不能像alert一样阻塞程序的时候
  3. 因此,所有的“等待的情况”都需要异步

一句话总结就是需要等待但是又不能阻塞程序的时候需要使用异步

异步和并行

千万不要把异步和并行搞混了,
异步是单线程的,并行是多线程的
异步:主线程的任务以同步的方式执行完毕,才会去依次执行任务列队中的异步任务
并行:两个或多个事件链随时间发展交替执行,以至于从更高的层次来看,就像是同时在运行(尽管在任意时刻只处理一个事件)

参考链接
关于js中的同步和异步
异步和单线程——什么时候需要异步,前端使用异步的场景
Javascript异步编程的4种方法

  1. glpla说道:

    好棒!!!
    就是理解不了那么好

发表评论

电子邮件地址不会被公开。 必填项已用*标注