乐趣区

关于javascript:笔记JS异步编程单线程定时器

同步与异步

同步

调用之后失去后果,再执行其余工作

const test = () => {let t = +new Date();
  while (true) {if (+new Date() - t >= 2000) {break;}
  }
};
console.log(1);
test();
console.log(2);
console.log(3);
// 输入:1 
// 执行 test()占用 2s,2s 后输入 2 3

异步

调用之后不论后果,继续执行其余工作

console.log(1);
setTimeout(() => {console.log(2);
}, 2000);
console.log(3);
// 输入:1 3
// 定时器 2 秒后输入 2

过程与线程

材料:过程与线程的简略解释

过程

程序运行的实例
同一程序能够产生多个过程
一个过程蕴含一个或多个线程

线程

操作系统可能进行运算调度的最小单位
一次只能执行一个工作
有本人的调用栈、寄存器环境
同一过程的线程共享过程资源

查看过程的罕用 linux 命令:

查看过程的状态:`ps (process status)`
查看动静的过程变动:`top (table of processes)`

JS 单线程

浏览器的过程

启动浏览器后,会产生多个过程

材料:
古代浏览器的多过程架构
浏览器的过程和线程

渲染过程

渲染过程包含:

  • GUI 线程 渲染布局,解析 HTML、CSS,构建 DOM 树
  • JS 引擎线程

    • 解析执行 JS(Chrome V8 就是 JS 引擎,跑在 JS 引擎线程,JS 引擎线程只有一个。因为解释 JS 的引擎是单线程的,所以称 JS 为单线程)
    • 与 GUI 线程互斥(因为 JS 能够操作 DOM,如果和 GUI 线程同时操作 DOM,就会出问题)
  • 定时器触发线程

    • setTimeout
    • setInterval
  • 事件触发线程

    • 将满足条件的事件放入工作队列
  • 异步 HTTP 申请线程

    • XHR 所在线程 (解决 ajax 申请,申请实现后,如果有回调就告诉事件触发线程往工作队列增加事件)

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

异步场景:

  1. 定时器
  2. 网络申请
  3. 事件绑定
  4. Promise

定时器

定时器工作流程

  1. 调用 WebAPI,如 setTimeout
  2. 定时器线程计数
  3. 事件触发线程将定时器事件放入工作队列
  4. 主线程通过 Event Loop 遍历工作队列

代码剖析:

console.log(1);
setTimeout(() => {console.log(2);
}, 2000);
console.log(3);
执行过程:console.log(1) 同步工作入栈执行,执行完出栈,打印 1
调用 setTimeout,定时 2s
console.log(3) 同第一步,打印 3
执行栈空,查看工作队列,不到 2s 还没有工作
2s 到,事件触发线程将工作增加进工作队列
循环查看就会发现有工作,将工作放入执行栈执行,打印 2 

定时器可能存在的问题

  1. 定时工作可能不会按时执行

    // 因为同步工作执行了 5s,所以 5s 后才打印 2
    const test = () => {let t = +new Date();
      while (true) {if (+new Date() - t >= 5000) {break;}
      }
    };
    setTimeout(() => {console.log(2);
    }, 2000);
    test();
  2. 定时器嵌套 5 次后最小距离不能低于 4ms(不同浏览器实现不同)

定时器利用场景

  • 防抖
  • 节流
  • 倒计时
  • 动画(存在丢帧的状况)学习 requestAnimationFrame

setTimeout 代码执行剖析

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

setTimeout 等同步的 for 循环执行后才执行,此时 i 曾经变成 11,所以后果是每隔 1s 打印一个 11,打印 10 个 11

  1. 能够利用闭包造成作用域,保留 i 的值,能够实现打印每隔 1s 打印 1 -10

      for (var i = 1; i <= 10; i++) {(function (x) {setTimeout(() => {console.log(x)
          }, 1000 * x)
        })(i)
      }
  2. 因为 var 没有块级作用域,所以换成 ES6 的 let 也能够实现

    for (let i = 1; i <= 10; i++) {setTimeout(function() {console.log(i);
      }, 1000 * i);
    }
退出移动版