进阶:setTimeout用法 & 任务队列异步函数节流 (10

2019-05-24  本文已影响0人  饥人谷1904_陈俊锋

饥人谷学习进阶第 10 天

定时器

JS提供定时执行代码的功能,主要由setTimeou()和setInterval()两个函数来完成。

setTimeout 和setInterval

setTimeout()


用来指定某个函数或某段代码,在多少毫秒之后执行(延时执行)。它返回一个整数,表示定时器的编号,之后可以用其来取消这个定时器

var timerId = setTimeout(func|code, delay)

setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数

console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);
// 输出: 1 3 2  因为setTimeout指定第二个语句推迟1000ms再执行

注意:推迟执行的代码必须以字符串的形式,放入setTimeout,因为引擎内部使用eval函数,将字符串转为代码。如果推迟执行的是函数,则可以直接将函数名,放入setTimeout。一方面eval函数有安全顾虑,另一方面为了便于JavaScript引擎优化代码,setTimeout方法一般总是采用函数名的形式

setTimeout(fn,1000);
// or 
setTimeout(function () {}, 1000)

setInterval()
用法同setTimeout,区别在于setInterval指定某个任务每隔一段时间就执行一次(间隔执行),也就是无限次定时执行

var i = 1;
var timer = setInterval (function() {
    console.log(i++);
}, 1000)
// 每隔1000ms就输出一个i,1 2 3 4 5 6 ...,直到用户执行clearInterval(timer)

clearTimeout(),clearInterval()
上面两个定时器函数都返回一个表示计时器编号的整数值,将该整数传入clearTimeout和clearInterval函数,可以取消对应的定时器

单线程模型

单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

运行机制

setTimeout和setInterval:将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等待下一轮Evant Loop时重新判断。这意味着,setTimeout指定的代码,必须等到本次执行的所有代码都执行完了,才会执行。

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f,0),那么不会立刻执行

setTimeout(f,0)将第二个参数设为0,作用是让f在现有的任务(脚本的同步任务和“任务队列”中已有的事件)一结束就立刻执行。也就是说,setTimeout(f,0)的作用是,尽可能早地执行指定的任务。

// 代码实例1:
var i=0;
for(var i=0; i<10; i++){
  setTimeout(function(){
      console.log(i)
  }, 1000)
}
// 输出:10次 10
// 代码实例2:
var t = true;  
setTimeout(function(){ 
  t = false; 
}, 1000);  

while(t){ }  
console.log('end')
// 输出: 陷入死循环,console.log('end')这行代码一直等待没办法执行

异步与回调

JS是一个单线程的语言,永远只有一个通道(主线程)在运行程序

JS中所谓的异步,应称为伪异步(会产生阻塞,并会相互干扰)

当主线程开始执行异步任务,实际就是执行对应的回调函数。所谓回调函数,就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

img
如图,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

模拟JS异步:

var foo = function(){
    console.log('foo begins');
    setTimeout(function(){
        console.log('foo finishes');
    },1000);
};
var bar = function(){
    console.log('bar executed');  
}
foo();
bar(); 
// 输出:
// foo begins
// bar executed
// foo finishes

上述程序模拟foo运行1秒而在中间的1秒运行中后面的bar也可以运行

JS异步方法存在的阻塞与干扰:

var foo = function(){
    console.log('foo begins');
    setTimeout(function(){
        console.log('foo finishes');
    },1000);
};
var bar = function(){

    while(){

    } 
}
// foo();
// bar();

1秒之后foo finishes没有被打印出来,因为bar方法进入死循环,js引擎卡死,导致foo方法也没有被运行完,本质上取决于JS单线程程序块按队列执行的特性

异步与回调:

function f1(callback){
    setTimeout(function(){
        //做某件事,可能很久
        console.log('别急,开始执行f1')
        for(var i=0;i< 100000;i++){

        }
        console.log('f1执行完了')

        callback()
    }, 0);

}
function f2(){
    console.log('执行f2');
}
function f3(){
    console.log('执行f3');
}
f1(f2) //当f1执行完之后再执行 f2
f3()

函数节流

执行频繁的事件等以最后一次为准(避免极短时间内很多重复执行)

var timer;
function fn () {
  if (timer) {
    clearTimeout(timer)
  }
  timer = setTimeout(function() {
    console.log('do something')
  }, 1000)
}
fn()
fn()
fn()
// do something

改进:

function throttle (fn, delay) {
  var timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn(arguments);
    }, delay)
  }
}

function fn() {
  console.log('do something');
}
function fn2 = throttle(fn, 1000);
fn2();

JS高级教程中的函数节流:

function throttle(method, context) {
  clearTimeout(method.tId);
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000)
}

参考

上一篇 下一篇

猜你喜欢

热点阅读