1

并行?并发?异步?

同步:synchronous: 指所有任务按出现的先后顺序依次执行 如果出现阻塞的任务,那么线程就会等待这个任务完成,接着执行下一个任务。

异步:asynchronous:不保证所有任务按出现的顺序执行

并发:concurrent:从宏观上,某个时间段里面多个程序都得到了运行,但不是说“同时运行”

并行:parallel:在多核心下,因进程和线程独立运行,且多个线程之间共享数据,程序可以同时运行。

定时器

常用的回调函数有:

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • requestAnimationFrame

https://zhuanlan.zhihu.com/p/...

setTimeout

作用:延迟指定的时间来调用函数或计算表达式。

语法:setTimeout(func /**函数,必选*/,code /**表达式,可选*/ ,milliseconds /**执行需等待的毫秒数,必选*/param1, param2, .../**传递给函数的参数,可选*/)

具体用法参加《Javascript异步编程(一)》

setInterval

作用:按照指定的周期(以毫秒计)来调用函数或计算表达式。

语法:setInterVal(func /**函数,必选*/,code /**表达式,可选*/ ,milliseconds /**每次执行将延迟的毫秒数,必选*/param1, param2, .../**传递给函数的参数,可选*/)

原理

 console.log('sync...',1);
  setInterval(()=>{
    console.log('sync...',2)
  },2000);
  console.log('sync...',3)

流程分析:

  1. 主程序调用栈
  2. 调用webAPI-setInterVal
  3. 定时器线程计数2s
  4. 每隔2s事件触发线程将回调放入任务队列
  5. 主线程通过Event Loop遍历任务队列执行回调 console.log('sync...',2)

注意

  • IE9及以下版本不兼容(setTimeout&setInterval)传递额外参数,可使用polyfill
  • setInterval,delay最小为10ms。意味着无法设置为0秒间隔,但可以尝试使用postMessage实现
  • 使用clearInterval(timerId)关闭指定定时器
  • 确保回调函数执行时长小于delay时间
  • 同setTimeout一样,setInterval回调中的this永远指向global
  • 不推荐使用setInterval(code,delay),有安全风险

为什么不建议使用setInterval

如果任务实际耗时超过delay,会出现同一时间触发多个回调。

有以下场景,每隔1s调用服务

// 时间间隔大于delay
let count = 5
let intervalTimer = setInterval(function () {
  if (count <= 0) {
    clearInterval(intervalTimer)
    return
  }
  /*模拟延时任务*/
  let timeoutTimer = setTimeout(function (count) {
    console.log(`the ${count} is running`)
    clearTimeout(timeoutTimer)
  }, Math.floor(Math.random() * (10000) + 1000),count)
  count--
}, 1000)

image_9.png
从上图可知,xhr响应无法按照顺序返回,这样就会导致无法正常处理结果

折中方案
function moreBetterInterval (count) {
  // 1s后调用
  setTimeout(function (countDown) {
    console.log(count +' is begin')
    let timeoutTimer = setTimeout(function (times) {
      console.log(`the ${times} is running`)
      clearTimeout(timeoutTimer)
      times--;
      if(times>0){
        moreBetterInterval(times)
      }
    }, Math.floor(Math.random() * (10000) + 1000),countDown)
  }, 1000,count)
}
moreBetterInterval(5)

image_10.png

可以保证递归之前已执行完回调,但无法保证按照一定的时间间隔。

实际应用场景

  • 短信倒计时
let countDown=60;
let timer=setInterval(function () {
  countDown--;
  if(countDown<0){
    clearInterval(timer);
    countDown=60
  }
},1000)
  • 显示当前时间
setInterval(function () {
    let d = new Date()
    $('#clock')[0].innerHTML = d.toLocaleTimeString()
}, 1000)

setImmediate

仅在Internet Explorer和Node.js下可用

作用:在循环事件任务完成后马上运行指定代码

语法:setImmediate(func /**函数,必选*/,code /**表达式,可选*/,[ param1,param2,...]/**传递给函数的参数,可选*/);

setImmediate与setTimeout(func,0)?

setTimeout(func,0)

谁先谁后,不一定~

需要考虑

  • 是在哪个阶段注册的,如果在定时器回调或者I/O回调里面,setImmediate肯定先执行。如果在最外层或者setImmediate回调里面,哪个先执行取决于当时机器状况。
  • 当前的Event Loop的任务队列的情况,如果在队尾,那也要先执行前面的事件,这样也无法保证立即执行

requestAnimationFrame

作用:接受一个动画执行函数作为参数,这个函数的作用是仅执行一帧动画的渲染,并根据条件判断是否结束,如果动画没有结束,则继续调用requestAnimationFrame并将自身作为参数传入

语法:requestAnimationFrame(func /**函数,必选*/)

细节:以60FPS(每帧16.7ms)为目标,浏览器内部会选择渲染的最佳时机

与setTimeout动画区别:

  • setTimeout(func,16.7):容易卡顿

原因有两个:

  • 实际执行时间晚于设定的延迟时间,出现卡顿
  • 与浏览器刷新率有关,不同设备的屏幕刷新频率可能会不同,而setTimeout只能设定固定的时间间隔,无法保证与刷新率同步,容易丢帧
  • requestAnimationFrame能节省CPU开销,当元素隐藏或不可见时,会停止渲染。而setTimeout仍在后台执行。

用途

  • 实现一帧的函数节流
  • 动画

注意

  • 使用cancelAnimationFrame(id)关闭渲染动画
  • IE10以下不兼容,可使用setTimeout进行polyfill 从而模拟帧率尽量适配刷新率

结尾

通过以上定时器,最显著的共同点是:回调。

初一看,回调没有问题呀,可以延迟计算。请想下以下情景:

  1. 嵌套回调
let msg = document.getElementById('msg')
$('#btn').click(function (evt) {
  msg.innerHTML += `${new Date()} processing btn click callback... <br>`
  setTimeout(function request () {
    msg.innerHTML += `${new Date()} processing setTimeout callback...<br>`
    $.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
      msg.innerHTML += `${new Date()} processing ajax callback...<br>`
    })
  }, 500)
}) 

这里我们用了三个函数嵌套,这种代码就被称为“回调地狱(callback hell)”,这样的代码难以编写,难以理解而且难以维护

  1. 控制反转
$.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
  msg.innerHTML += `${new Date()} processing ajax callback...<br>`
})

即自己程序的一部分的执行控制权交由某个第三方。在你不确定这个回调能否按照预期执行时,发生意外时很难定位问题。

为了优雅的的处理回调最大的问题:控制反转,有以下方式:

  • 回调分离 -->ES6 Promise
  • error-first风格-->Node.js中会将回调的第一个参数保留用作error
const fs=require('fs')
fs.readFile(__dirname,function(err,data) {
  if (err)  console.log(err)
  //...
})

但还是不优雅,并没有真正解决我们的控制反转问题,只是将我们之前担心的程序异常暴露了出来。

可能现在你希望有API或其他语言机制来解决这些问题。所幸,ES6会给你带来些干货~

Reference

HTML Timers
Timer resolution in browsers


王大山
28 声望2 粉丝