【异步编程】理解异步

目录:

  1、同步与异步

  2、JavaScript 单线程

  3、定时器

     定时器的执行过程

     定时器可能存在的问题

     定时器的应用场景

     定时器的应用

同步与异步

一段同步代码 :

 1 <script>
 2     //test() 是一个定时 2s 的方法
 3     const test = () => {
 4         let t = +new Date();
 5         while( true ){
 6             if( +new Date() - t >= 2000 ){
 7                 break;
 8             }
 9         }
10     };
11     console.log( 1 );   
12     test();
13     console.log( 2 );
14     console.log( 3 );
15 </script>

一段异步代码 :

1 <script>
2     console.log(1);
3     setTimeout(() => {
4         console.log( 2 );
5     }, 2000);
6     console.log( 3 );
7 </script>

运行结果的区别 :

  同步代码先输出 1 ,然后 2s 后输出 2,然后输出 3 。异步代码先输出 1 ,然后这里开始定时 2s, 然后输出 3 ,然后前面的定时满 2s 后输出 2。

  同步代码是顺序执行的,等定时任务完成之后才继续往下执行输出 2、输出3。异步代码没有等定时任务执行完毕之后再继续往下执行,而是开始定时任务完成之前就执行了输出 3,所以输出 3 比输出 2 早。

同步与异步的区别 :

  同步是调用之后得到结果,再干别的任务。异步是调用之后先不管结果,继续干别的任务。

进程跟线程的概念:

  进程是程序运行的实例,同一程序可以产生多个进程。比如说启动 chrome 浏览器,是有多个进程在后台跑的。一个进程可以包含多个线程。

  线程是操作系统能够进行运算调度的最小单位,特点是一次只能执行一个任务。线程有自己的调用栈、寄存器环境,还有自己本地的存储空间等。同一个进程的线程共享进程资源。

(阮一峰老师的一篇简单解释进程与线程的文章:https://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

查看进程的常用命令

  在 Linux 中,可以用 ps 命令查看当前系统运行的进程信息,ps 是 process status 的缩写。可以用 top 命令查看动态的进程变化,top 是 table of processes 的缩写。

JavaScript 单线程

单线程 JS 怎么实现异步?

  通过浏览器内核多线程实现异步。

  浏览器是一个多进程的架构,以开源的 chrome 浏览器为例,它是有多进程的(浏览器进程、渲染进程、GPU进程、网络进程跟插件进程) 。其中我们要关心的是渲染进程。渲染进程是浏览器的一个核心的进程,渲染进程包括 GUI 线程、 JS 引擎线程、 定时触发器线程、事件触发线程、异步 HTTP 请求线程。

  GUI 线程:渲染布局,解析 HTML、CSS,构建 DOM 树和渲染树

  JS 引擎线程:解析、执行 JS 程序,比如 Chrome V8 就是一个 JS 引擎。JS 引擎线程只有一个,这是说 JS 是单线程语言的原因,其实语言是没有单线程多线程之说的,因为解释这个语言的引擎是单线程的,所以才把它称为单线程语言。JS引擎线程与 GUI 线程是互斥的,因为 JS 引擎线程也可以操作 DOM,所以如果 JS引擎线程和 GUI 线程同时操作 DOM 的话,就会引起混乱。这两个线程互斥导致的问题是:如果 JS引擎线程的执行时间太长,页面的渲染就会收到影响,所以要尽量控制 JS 文件的大小,不要让 JS 执行的时间过长。

  定时器触发线程:setTimeout 跟 setInterval 定时的操作是由这个线程完成的。上面例子中,为什么 setTimeout 不阻塞 console.log( 3 ); 呢?是因为 setTimeout 的定时任务不是由 JS 线程完成的,它是由定时器触发线程完成的,所以 它们是可以同时进行的。定时器触发线程会在定时这个任务完成之后通知事件触发线程往任务队列里添加事件。

  事件触发线程:将满足触发条件的事件放入任务队列。

  异步HTTP请求线程:用于处理 AJAX 请求。当请求完成时,如果有回调函数,它就会通知事件触发线程往任务队列里添加事件。

(学习这篇文章可以了解浏览器的架构以及每个模块负责的工作,宏观上了解浏览器的工作原理:https://juejin.im/post/5bd7c761518825292d6b0217

  

前端有哪些异步场景呢?

  1.定时器

  2.网络请求

  3.事件绑定

  4.ES6 Promise

定时器

定时器的执行过程

  比如上面的例子,首先有一个主线程,主线程有一个执行栈,代码是在执行栈这里执行。首先调用 webAPI,webAPI 可以理解成浏览器提供的一种能力。setTimeout 就是一种 webAPI,调用了 setTimeout 之后,定时器线程会计数两秒钟,计数两秒这个任务结束之后,会通知事件触发线程将定时器事件放入任务队列中。主线程通过 Event Loop 来遍历任务队列。Event Loop 是一个循环,主要检查任务队列和主线程的执行栈。

  通过一段代码来看定时器的执行过程:

    console.log( 1 );
    setTimeout(()=>{
        console.log( 2 );
    }, 2000);
    console.log( 3 );
    //依次输出: 1 3 2

   上面,console.log( 1 ) 是一个同步任务,同步任务入栈执行,执行完出栈,打印出来1。然后遇到了 setTimeout,setTimeout 是一个 webAPI,调用 webAPI,通知定时器线程定时 2 s。再执行 console.log( 3 ) ,同步任务入栈执行,执行后出栈,打印出来3。这时候执行栈空了,会检查任务队列,任务队列里还不到 2s 是没有任务的,继续循环检查,达到 2s 后,事件触发线程往任务队列里面添加定时器的事件,这时去检查任务队列任务队列里面就有了一个定时器的异步任务,这时会取出这个任务放入执行栈执行,执行完出栈,打印出来2

  任务队列是由事件触发线程往里面加,由Event Loop取出外面。

定时器可能存在的问题

 1. 定时任务可能不会按时执行

 1 const test = () =>{
 2     let t = new Date();
 3     while( true ){
 4         if( +new Date()-t >= 500 ){
 5             break;
 6         }
 7     }
 8 };
 9 setTimeout(() => {
10     console.log( 2 );
11 }, 2000);
12 test();

  上面,test 方法是一个执行一个等待 5s 的任务的方法, setTimeout 等待 2s 打印出来 2 ,然后调用 test。预期的效果:执行代码,2s之后打印出来 2 。实际的效果:执行代码,5s 之火打印出来 2。根据定时器的执行原理,它会等同步任务执行完之后它才会执行,但是如果同步任务耗时很久的话,定时的任务就不会如期执行。这个在应用定时器时需要注意。

 2. 定时器嵌套 5 次之后最小间隔不能小于 4ms

   这个不同的浏览器有不同的实现。

  ( 规范对定时器的说明:https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers

定时器应用场景

1. 防抖

2.节流

  ( 防抖与节流的相关笔记:https://www.cnblogs.com/xiaoxuStudy/p/12653666.html

3.倒计时

  倒计时运用也是非常广泛的,比如说一个商品秒杀的页面,会提示秒杀时间还剩下多少。

4.动画 

  定时器可以做一些简单的动画。定时器做的动画存在的最大的问题是丢帧。

定时器的应用

for(var i=1; i<=10; i++){
    setTimeout(function(){
        console.log(i);
    }, 1000*i)
}

 输出:

说明:

  预期的输出结果是输出 1-10,每个数字之间间隔一秒输出。但是实际的输出结果是每隔 1s 输出一次 11。

  这是因为 js 没有块级作用域。setTimeout 是异步执行的,等同步的 for 循环执行完之后才会执行。等 for 循环执行完之后 i 的值已经是 11 了,所以执行 setTimeout 使用 i 的时候 i 是 11。

可以利用立即执行函数来实现相隔 1s 输出 1-10。( 相关笔记:https://www.cnblogs.com/xiaoxuStudy/p/12508530.html#one )

1 for(var i=1; i<=10; i++){
2     (function(i){
3         setTimeout(function(){
4             console.log(i);
5         }, 1000*i);
6     })(i)
7 }

输出:

也可以利用 es6 语法实现。将 var 替换为 let。

1 for(let i=1; i<=10; i++){
2     setTimeout(function(){
3         console.log(i);
4     }, 1000*i);
5 }

输出:

原文地址:https://www.cnblogs.com/xiaoxuStudy/p/12573798.html