Skip to content

定时器与异步

定时器

控制在某个固定的时刻往进程中添加代码执行。

setTimeout执行之后返回一个正整数,是定时器的编号,可以用这个编号清除定时器,不同的定时器编号会累加

  • 在某段时间之后执行代码setTimeout,

第一个参数是需要执行的代码或者回调函数,必须,
第二个参数是延时,不写默认为 0,单位毫秒(1s = 1000ms)
第三个以后是需要执行的代码传入的参数

javascript
let foo = (x, y) => console.log(x + y)
setTimeout(foo, 1000, 2, 3) //返回一个正整数定时器编号
//1s之后打印出5

因为

javascript
window.setTimeout === setTimeout //true

这里不管在哪调用 setTimeout 的主体是 window

javascript
let o = {
    a: 1,
}

function foo() {
    setTimeout(function() {
        console.log(this)
    }, 1000)
}
foo.call(o) //这里指向window

因为是window调用的setTimeout所以不可以修改。定时器永远是浏览器调用

解决方法:

javascript
let o = {
    a: 1,
}

function foo() {
    setTimeout(
        function() {
            console.log(this)
        }.bind(o),
        1000
    ) //强行修改回调函数的this,返回的函数和原函数已经不是同一个了
}
foo() //这里指向o

或者:

javascript
let o = {
    a: 1,
}

function foo() {
    setTimeout(() => {
        //硬绑定到foo上下文的this中
        console.log(this)
    }, 1000)
}
foo.call(o) //这里指向o

清除定时器:clearInterval传入一个参数,就是定时器的序号。页面唯一。

javascript
let timer = setTimeout(function() {}, 5000)
clearInterval(timer) //上面那个定时器就删掉了。
  • 运行代码后每隔一段时间执行相应代码:setInterval,和setTimeout有相似性质

    第一个参数是需要执行的代码或者回调函数,必须,
    第二个参数是延时,不写默认为 0,单位毫秒(1s = 1000ms)
    第三个以后是需要执行的代码传入的参数

javascript
let timer = setInterval(
    function(a, b) {
        console.log(a + b)
    },
    1000,
    2,
    3
) //每隔1000ms打印依次5
clearInterval(timer) //通过定时器序号清除定时器

同步与异步

浏览器的 js 引擎,掌控了所有的 JS 代码运行。

JS 是单线程运行的,换句话说,同一时刻只能做一件事情。

javascript
for (let i = 0; i < 900000000; i++) {} //页面卡死一小会,无法操作

虽然 JS 执行和浏览器渲染虽然不是同一个线程,但是 JS 涉及页面元素操作,修改操作完成之前渲染会被锁死,JS 完成之后才会渲染。这就是单线程的性质。

  • 同步

    当读取到 JS 代码的时候:代码会从上往下执行,当前面的代码执行完成返回之前,后面的代码是不会执行的。其余操作也是不会执行的。

javascript
console.log(1)
console.log(2)
for (let i = 0; i < 900000000; i++) {} //页面卡死一小会,无法操作
console.log(3) //在上行代码执行完成之前是不会打印的
//1 2 等一会儿 3
  • 异步

    异步的含义是相对于同步的,某行代码的执行单独开辟线程处理,与主线程无关,返回结果传递到主线程

    但是 JS 运行是单线程的,所以不存在真正的异步

JS 执行的时候会依照一个顺序执行:

先执行完同步队列----->再执行异步队列

浏览器执行同步队列里的代码,

当遇到 setTimeout 或者 setInterval 或者点击事件等,会在必要的时刻将代码标识扔到异步队列中,异步代码会返回结果表示代码执行完成,实际代码没有执行。

同步执行完成会处于空闲状态。

处于空闲状态(执行间隙)才会不停扫描异步队列,将扫描到的标识为需要执行的异步代码执行

回调

浏览器 dom 事件:响应用户点击,图片加载完成,vue 组件的生命周期钩子 也是属于异步

节流和防抖

防抖 debounce

在事件被触发 n 秒后再执行回调函数,如果在这 n 秒内又被触发,则重新计时

节流 throttle

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

推荐使用Lodash函数库来使用节流和防抖

请求动画帧

requestAnimationFrame(foo(time))

电脑不卡的时候相当于setTimeout(foo,1000/60)

回调函数的参数 time 可以获取打开浏览器到现在经历的毫秒数

运动架

CSS3 当中可以通过 animation 做动画。JS 中的动画依赖运动框架。

已知起始位置和目标位置:传入运动时间,通过定时器设置完成动画效果。

需求:一个函数传入对象,时间,变换值,回调(动画执行完成后执行的操作)

javascript
function getStyle(ele) {
    return ele.currentStyle || getComputedStyle(ele)
}

//简单的解决兼容问题
window.requestAnimationFrame =
    window.requestAnimationFrame ||
    function(cb) {
        return setTimeout(cb, 1000 / 60)
    }
window.cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout

function animation(ele, data = {}, time = 500, cb) {
    /*
     * ele => 已获取的HTML标签对象
     * data => 需要改变的样式:目标数值 可以带单位
     * time => 改变样式所需要的总时间 单位是毫秒
     * 例如 : animation(box,{width:'1000px',height:'1000px'},5000)
     */
    var startValue = {}
    var changeValue = {}
    var startTime = new Date()
    var eleStart = getStyle(ele)
    for (var key in data) {
        startValue[key] = isNaN(Number.parseFloat(eleStart[key])) ?
            0 :
            Number.parseFloat(eleStart[key])
        changeValue[key] = Number.parseFloat(data[key]) - startValue[key]
    }
    run()

    function run() {
        var nowTime = new Date() - startTime
        var t1 = nowTime / time
        for (var key in changeValue) {
            var val = t1 * changeValue[key] + startValue[key]
            ele.style[key] = val + "px"
        }
        if (t1 >= 1) {
            cancelAnimationFrame(run)
            cb && cb()
        } else {
            requestAnimationFrame(run)
        }
    }
}

运动框架核心思想:

已用时间/总时间=已走路程/总路程
已知总时间和总路程还有帧数