前端计划——JavaScript中关于setTimeout的那些事

栏目: JavaScript · 发布时间: 7年前

内容简介:前端计划——JavaScript中关于setTimeout的那些事

前言:setTimeout是JavaScript中常见的一个window对象方法,本文将介绍关于它的一些基础知识和易出错的地方。

1、基础知识

作用:setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

基本语法:

let timeoutId = window.setTimeout(func[, delay, param1, params2, ...]);
let timeoutId = scope.setTimeout(code[, delay]);
let timeoutId = window.setTimeout(function, milliseconds);
  • timeoutID 是该延时操作的数字ID, 此ID随后可以用来作为window.clearTimeout方法的参数。

  • func是你想要在delay毫秒之后执行的函数。

  • code 在第二种语法,是指你想要在delay毫秒之后执行的代码字符串,(使用该语法是不推荐的,

    不推荐的原因和eval()一样,即:1、安全性差(可被植入恶意代码)2、执行效率低(需要将字符串解析为代码再执行))

  • delay 是延迟的毫秒数(1秒=1000毫秒),函数的调用会在该延迟之后发生。如果省略该参数,delay取默认值0。

  • 需要注意的是,IE9 及更早的 IE 浏览器不支持第一种语法中向延迟函数传递额外参数的功能。

//以下是一个简单实例,3秒后弹窗提示
function myFunction(){
    setTimeout(function(){alert("Hello")},3000);
}

2、单线程与事件队列机制

先来看一些程序

//请判断以下代码输出结果
setTimeout(function(){
    alert("Hello World");
   },1000);
   while(true){};
//该函数会陷入死循环,1秒后并不会弹出提醒
//请写出以下代码输出结果
setTimeout(function (){
    console.log('a')
},0)
console.log('b')
//输出结果为b,a

有同学可能会认为:第一段代码在1秒后会弹窗提示Hello World。而第二段代码,把延迟毫秒数设为0,就会立即执行,先输出a,再输出b。显然,实际的结果不是这样。

为什么呢?因为:

JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。

我们先来介绍下浏览器渲染时的线程机制:

浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程。

  • JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。

  • GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。

  • 事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理( 当线程中没有执行任何同步代码的前提下才会执行异步代码 )。

    JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。

回归开始的问题:

  • 第一段代码始终会执行同步代码,陷入死循环,根本不会执行setTimeout内的函数,也就不会弹窗。

  • 第二段代码,经过查找资料,可以得知,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说—— 即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动,它仍然会放在事件队列的最后。 当同步代码执行完,输出b,然后再执行延迟函数,输出a。

3、this

this是JavaScript中一个重要的考察点,可以简单记为:this是指向函数执行时的当前对象,倘若没有明确的当前对象,它就是指向window的。

请看如下代码

//求函数执行结果
var a=1;
var obj={
    a:2,
    b:function(){
        setTimeout(function(){
            console.log(this.a);
        },2000);
        
    }
};
obj.b();
//函数输出为1

由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字会指向 window (或全局)对象,这和所期望的this的值是不一样的。也就是当执行时,this.a并不能读取obj对象中的a,而是会找到全局对象的a,故输出结果为1。

为了改变这种情况,有以下两种方法

//方法一
var a=1;
var obj={
    var me = this;
    a:2,
    b:function(){
        setTimeout(function(){
            console.log(me.a);
        },2000);
        
    }
};
obj.b();
//方法二
var a=1;
var obj={
    a:2,
    b:function(){
        setTimeout(function(){
            console.log(this.a);
        }.bind(this),2000);
 
    }
};
obj.b();

4、混合知识点考察

题目一:求输出结果

(function() {
    console.log(1); 
    setTimeout(function(){console.log(2)}, 1000); 
    setTimeout(function(){console.log(3)}, 0); 
    console.log(4);
})();
//输出结果是1,4,3,2

解释:参照第二部分的事件机制

题目二:对setTimeout和IIFE的考察

将 JavaScript 代码包含在一个函数块中有什么用途?为什么要这么做?

换句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。

IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。

for(var i = 0; i < 5; i++) {
 setTimeout(function() {
 console.log(i); 
 }, 1000);
}
//输出为5,5,5,5,5

使用IIFE

for(var i = 0; i < 5; i++) {
 (function(i) {
 setTimeout(function() {
 console.log(i); 
 }, 1000);
 })(i)
}
//输出为0,1,2,3,4

题目三:setTimeout的分片应用

如果 list 很大,下面的这段递归代码会造成堆栈溢出。如果在不改变递归模式的前提下修善这段代码?

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();
    if (item) {
    // process the list item...
    nextListItem();
    }
};

解决方案:加入定时器

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();
    if (item) {
    // process the list item...
    setTimeout(nextListItem(), 0);
    }
};

题目四:考察setTimeout和Promise系列

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

解释:立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

题目五:setTimeout与箭头函数的this指向

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Coming of Age in Second Life

Coming of Age in Second Life

Tom Boellstorff / Princeton University Press / 2008-04-21 / USD 29.95

The gap between the virtual and the physical, and its effect on the ideas of personhood and relationships, is the most interesting aspect of Boellstorff's analysis... Boellstorff's portrayal of a virt......一起来看看 《Coming of Age in Second Life》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具