定时器

作者: Cause_XL | 来源:发表于2017-07-07 18:48 被阅读0次

定时器

参考链接

setTimeout()

console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);

上面代码的输出结果就是1,3,2,因为setTimeout指定第二行语句推迟1000毫秒再执行。

为了便于javascript引擎优化代码,setTimeout方法一般总是采用函数名的形式,就像下面这样

function f(){
  console.log(2);
}

setTimeout(f,1000);

// 或者

setTimeout(function (){console.log(2)},1000);

除了前两个参数,setTimeout还允许添加更多的参数。它们将被传入推迟执行的函数。

setTimeout(function(a,b){
  console.log(a+b);
},1000,1,1);

上面代码中,setTimeout共有4个参数。最后那两个参数,将在1000毫秒之后回调函数执行时,作为回调函数的参数。

除了参数问题,setTimeout还有一个需要注意的地方:被setTimeout推迟执行的回调函数是在全局环境执行,这有可能不同于函数定义时的上下文环境。

var x = 1;

var o = {
    x: 2,
    y: function(){
    console.log(this.x);
  }
};

setTimeout(o.y,1000);
// 1

再看一个不容易发现错误的例子。

function User(login) {
  this.login = login;
  this.sayHi = function() {
    console.log(this.login);
  }
}
var user = new User('John');
setTimeout(user.sayHi, 1000);

上面代码只会显示undefined,因为等到user.sayHi执行时,它是在全局对象中执行,所以this.login取不到值。

为了防止出现这个问题,一种解决方法是将user.sayHi放在函数中执行。

setTimeout(function() {
  user.sayHi();
}, 1000);

上面代码中,user.sayHi是在函数作用域内执行,而不是在全局作用域内执行,所以能够显示正确的值。

另一种更通用的解决方法,则是采用闭包,将this与当前运行环境绑定。

document.getElementById('click-ok').onclick = function() {
  var self = this;
  setTimeout(function() { 
    self.value='OK';
  }, 100);
}

上面代码中,setTimeout指定的函数中的this,总是指向定义时所在的DOM节点。

第三种:箭头函数

function User(login) {
  this.login = login;
  this.sayHi = () => console.log(this.login);
}
var user = new User('John');
setTimeout(user.sayHi, 1000);

setInterval()

setInterval指定的是开始执行之间的间隔,因此实际上两次执行之间的间隔会小于setInterval指定的时间。
假定setInterval指定每100毫秒执行一次,每次执行需要5毫秒,那么第一次执行结束后95毫秒,第二次执行就会开始。
如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始

假设,某个onclick事件处理程序使用啦setInterval()来设置了一个200ms的重复定时器。如果事件处理程序花了300ms多一点的时间完成。
这个例子中的第一个定时器是在205ms处添加到队列中,但是要过300ms才能执行。在405ms又添加了一个副本。在一个间隔,605ms处,第一个定时器代码还在执行中,而且队列中已经有了一个定时器实例,结果是605ms的定时器代码不会添加到队列中。结果是在5ms处添加的定时器代码执行结束后,405处的代码立即执行。

setInterval(function() {
  console.log(2);
}, 1000);

(function() {
  sleeping(3000);
})();

上面的第一行语句要求每隔1000毫秒就输出一个2.但是,第二行语句需要3000毫秒才能完成,请问会发生什么结果?

结果就是等到第二行语句运行完成以后,立刻连续输出三个2,然后开始每隔1000毫秒输出一个2。

也就是说,setInterval具有累积效应,如果某个操作特别耗时,超过了setInterval的时间间隔,排在后面的操作会被累积起来,然后在很短的时间内连续触发,这可能或造成性能问题(比如集中发出Ajax请求)。

为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。上面代码用setTimeout,可以使用下面的这种方法:

var timer = setTimeout(function(){
    //do something
    timer = setTimeout(arguments.callee, interval);
}, interval)

arguments.callee 指向此参数的函数

上面实现了递归调用,这样做的好处是:在前一个定时器代码执行完成之前,不会向队列插入新的定时代码,确保不会有任何的缺失间隔。而且,它保证在下一次定时器代码执行之前,至少要等待指定的时间间隔。

根据这种思路,可以自己部署一个函数,实现间隔时间确定的setInterval的效果。

function interval(func, wait){
  var interv = function(w){
    return function(){
      setTimeout(interv, w);
      func.call(null);
    }
  }(wait);

  setTimeout(interv, wait);
}

interval(function(){
  console.log(2);
},1000);

上面代码部署了一个interval函数,用循环调用setTimeout模拟了setInterval。

setTimeout(f,0)

必须要等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才会执行setTimeout指定的任务。
setTimeout添加的事件,会在下一次Event Loop执行。

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

setTimeout(function() {
  console.log("Timeout");
}, 0);

function a(x) {
    console.log("a() 开始运行");
    b(x);
    console.log("a() 结束运行");
}

function b(y) {
    console.log("b() 开始运行");
    console.log("传入的值为" + y);
    console.log("b() 结束运行");
}

console.log("当前任务开始");
a(42);
console.log("当前任务结束");

// 当前任务开始
// a() 开始运行
// b() 开始运行
// 传入的值为42
// b() 结束运行
// a() 结束运行
// 当前任务结束
// Timeout

0毫秒实际上达不到的。根据HTML 5标准,setTimeOut推迟执行的时间,最少是4毫秒。如果小于这个值,会被自动增加到4。另一方面,浏览器内部使用32位带符号的整数,来储存推迟执行的时间。这意味着setTimeout最多只能推迟执行2147483647毫秒(24.8天),超过这个时间会发生溢出,导致回调函数将在当前任务队列结束后立即执行,即等同于setTimeout(f,0)的效果。

应用

setTimeout(f,0)有几个非常重要的用途。它的一大应用是,可以调整事件的发生顺序。比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,我们先让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)。

var input = document.getElementsByTagName('input[type=button]')[0];

input.onclick = function A() {
  setTimeout(function B() {
    input.value +=' input'; 
  }, 0)
};

document.body.onclick = function C() {
  input.value += ' body'
};

用户自定义的回调函数,通常在浏览器的默认动作之前触发。比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的的。

document.getElementById('input-box').onkeypress = function(event) {
  this.value = this.value.toUpperCase();
}

上面代码想在用户输入文本后,立即将字符转为大写。但是实际上,它只能将上一个字符转为大写,因为浏览器此时还没接收到文本,所以this.value取不到最新输入的那个字符。只有用setTimeout改写,上面的代码才能发挥作用。

document.getElementById('my-ok').onkeypress = function() {
  var self = this;
  setTimeout(function() {
    self.value = self.value.toUpperCase();
  }, 0);
}

上面代码将代码放入setTimeout之中,就能使得它在浏览器接收到文本之后触发。

由于setTimeout(f,0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到setTimeout(f,0)里面执行。

var div = document.getElementsByTagName('div')[0];

// 写法一
for(var i=0xA00000;i<0xFFFFFF;i++) {
  div.style.backgroundColor = '#'+i.toString(16);
}

// 写法二
var timer;
var i=0x100000;

function func() {
  timer = setTimeout(func, 0);
  div.style.backgroundColor = '#'+i.toString(16);
  if (i++ == 0xFFFFFF) clearInterval(timer);
}

timer = setTimeout(func, 0);

上面代码有两种写法,都是改变一个网页元素的背景色。写法一会造成浏览器“堵塞”,而写法二就能就不会,这就是setTimeout(f,0)的好处。

相关测试

下面各段代码输出什么?

for (var i = 0; i < 5; i++) {
  console.log(i);
}
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000 * i);
}
for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
for (var i = 0; i < 5; i++) {
  (function() {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), i * 1000);
}
setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);
  1. 直接输出 0 1 2 3 4

  2. 输出一个 5,然后每隔一秒再输出一个 5,一共 5 个 5

  3. 每隔一秒输出 0 1 2 3 4

  4. 每隔一秒输出 5 5 5 5 5;
    这样子的话,内部其实没有对 i 保持引用,其实会变成输出 5。

  5. 立马输出 0 到 4;
    给 setTimeout 传递了一个立即执行函数。setTimeout 可以接受函数或者字符串作为参数,那么这里立即执行函数是个啥呢,应该是个 undefined ,也就是说等价于:
    setTimeout(undefined, ...);
    而立即执行函数会立即执行,那么应该是立马输出的。

  6. 输出 2 3 5 4 1

相关文章

  • 2017.12.21学习总结

    下午学习了定时器,定时器分为高级定时器、通用定时器和基本定时器,我们主要研究通用定时器。 定时器中断实现步骤:...

  • javascript笔记6

    定时器-间歇性定时器 定时器-延时定时器 认识DOM 间歇性定时器var time = window.setInt...

  • 定时器弹框、定时器基本用法、定时器动画、时钟

    定时器弹框: 定时器基本用法: 定时器动画: 时钟:

  • 无标题文章

    iOS NSTimer使用详解-开启、关闭、移除 定时器定时器详解ios定时器关闭定时器NSTimer 1、要使用...

  • 定时器

    1.倒计定时器(setTimeout) clearTimeout清除定时器2.循环定时器(setInterval)...

  • 第十三节 JavaScript 定时器 单线程

    一、定时器 1. JS存在两种定时器 setTimeout() 延迟定时器 setInterval() ...

  • 定时器 - OC

    定时器的定义 创建一个定时器并启动这个定时器 停止定时器 后续了解:NSTimer invalidate不起作用h...

  • 定时器 类型转换 封闭函数

    定时器定时器在javascript中的作用1、制作动画2、异步操作3、函数缓冲与节流 定时器类型及语法 /*定时器...

  • STM32--------定时器

    STM32F103一共有11个定时器,其中: 2个高级定时器 4个普通定时器 2个基本定时器 2个看门狗定时器 1...

  • 定时器

    定时器弹窗 定时器基本用法 定时器动画 时钟 倒计时 变量的作用域

网友评论

      本文标题:定时器

      本文链接:https://www.haomeiwen.com/subject/icsrhxtx.html