JS 提供了一些原生方法来实现延时去执行某一段 代码 ,下面来简单介绍一下 setTiemout、 setInterval 、setImmediate、requestAnimationFrame。
JS 提供了一些原生方法来实现延时去执行某一段代码,下面来简单介绍一下
setTimeout : 设置一个定时器,在定时器到期后执行一次函数或代码段
- var timeoutId = window.setTimeout(func[, delay, param1, param2, ...]);
- var timeoutId = window.setTimeout(code[, delay]);
setInterval : 以固定的时间间隔重复调用一个函数或者代码段
- var intervalId = window.setInterval(func, delay[, param1, param2, ...]);
- var intervalId = window.setInterval(code, delay);
setImmediate: 在浏览器完全结束当前运行的操作之后立即执行指定的函数 (仅 IE10 和 Node 0.10 + 中有实现),类似 setTimeout(func, 0)
- var immediateId = setImmediate(func[, param1, param2, ...]);
- var immediateId = setImmediate(func);
requestAnimationFrame: 专门为实现高性能的帧动画而设计的 API,但是不能指定延迟时间,而是根据浏览器的刷新频率而定(帧)
- var requestId = window.requestAnimationFrame(func);
上面简单的介绍了四种 JS 的定时器,而本文将会主要介绍比较常用的两种:setTimeout 和 setInterval。
- // 下面代码执行之后会输出什么?
- var intervalId, timeoutId;
- timeoutId = setTimeout(function() {
- console.log(1);
- },
- 300);
- setTimeout(function() {
- clearTimeout(timeoutId);
- console.log(2);
- },
- 100);
- setTimeout('console.log("5")', 400);
- intervalId = setInterval(function() {
- console.log(4);
- clearInterval(intervalId);
- },
- 200);
- // 分别输出: 2、4、5
- // 执行在面的代码块会输出什么?
- setTimeout(function() {
- console.log('timeout');
- },
- 1000);
- setInterval(function() {
- console.log('interval')
- },
- 1000);
- // 输出一次 timeout,每隔1S输出一次 interval
- /*--------------------------------*/
- // 通过setTimeout模拟setInterval 和 setInterval有啥区别么?
- var callback = function() {
- if (times++>max) {
- clearTimeout(timeoutId);
- clearInterval(intervalId);
- }
- console.log('start', Date.now() - start);
- for (var i = 0; i < 990000000; i++) {}
- console.log('end', Date.now() - start);
- },
- delay = 100,
- times = 0,
- max = 5,
- start = Date.now(),
- intervalId,
- timeoutId;
- function imitateInterval(fn, delay) {
- timeoutId = setTimeout(function() {
- fn();
- if (times <= max) {
- imitateInterval(fn, delay);
- }
- },
- delay);
- }
- imitateInterval(callback, delay);
- intervalId = setInterval(callback, delay);
如果是 setTimeout 和 setInterval 的话,它俩仅仅在执行次数上有区别,setTimeout 一次、setIntervaln 次。
而通过 setTimeout 模拟的 setInterval 与 setInterval 的区别则在于:setTimeout 只有在回调完成之后才会去调用下一次定时器,而 setInterval 则不管回调函数的执行情况,当到达规定时间就会在事件队列中插入一个执行回调的事件,所以在选择定时器的方式时需要考虑 setInterval 的这种特性是否会对你的业务代码有什么影响?
- console.time('immediate');
- console.time('timeout');
- setImmediate(() = >{
- console.timeEnd('immediate');
- });
- setTimeout(() = >{
- console.timeEnd('timeout');
- },
- 0);
在 Node.JS v6.7.0 中测试发现 setTimeout 更早执行
下面代码运行后的结果是什么?
- // 题目一
- var t = true;
- setTimeout(function() {
- t = false;
- },
- 1000);
- while (t) {}
- alert('end');
- /*--------------------------------*/
- // 题目二
- for (var i = 0; i < 5; i++) {
- setTimeout(function() {
- console.log(i);
- },
- 0);
- }
- /*--------------------------------*/
- // 题目三
- var obj = {
- msg: 'obj',
- shout: function() {
- alert(this.msg);
- },
- waitAndShout: function() {
- setTimeout(function() {
- this.shout();
- },
- 0);
- }
- };
- obj.waitAndShout();
问题答案会在后面解答
在解释上面问题的答案之前我们先来了解一下定时器的工作原理,这里将用引用 How JavaScript Timers Work 中的例子来解释定时器的工作原理,该图为一个简单版的原理图。
上图中,左侧数字代表时间,单位毫秒;左侧文字代表某一个操作完成后,浏览器去询问当前队列中存在哪些正在等待执行的操作;蓝色方块表示正在执行的代码块;右侧文字代表在代码运行过程中,出现哪些异步事件。该图大致流程如下:
这里只是对定时器的原理做一个简单版的描述,实际的处理过程比这个复杂。
好啦,我们现在再来看看上面的面试题的答案。
第一题
alert 永远都不会执行,因为 JS 是单线程的,且定时器的回调将在等待当前正在执行的任务完成后才执行,而 while(t) {} 直接就进入了死循环一直占用线程,不给回调函数执行机会
第二题
代码会输出 5 5 5 5 5,理由同上,当 i = 0 时,生成一个定时器,将回调插入到事件队列中,等待当前队列中无任务执行时立即执行,而此时 for 循环正在执行,所以回调被搁置。当 for 循环执行完成后,队列中存在着 5 个回调函数,他们的都将执行 console.log(i) 的操作,因为当前 JS 代码上中并没有使用块级作用域,所以 i 的值在 for 循环结束后一直为 5,所以代码将输出 5 个 5
第三题
这个问题涉及到 this 的指向问题,由 setTimeout() 调用的代码运行在与所在函数完全分离的执行环境上. 这会导致这些代码中包含的 this 关键字会指向 window (或全局) 对象,window 对象中并不存在 shout 方法,所以就会报错,修改方案如下:
- var obj = {
- msg: 'obj',
- shout: function() {
- alert(this.msg);
- },
- waitAndShout: function() {
- var self = this; // 这里将this赋给一个变量
- setTimeout(function() {
- self.shout();
- },
- 0);
- }
- };
- obj.waitAndShout();
来源: http://www.open-open.com/lib/view/open1489990059722.html