单线程模型和事件循环

定时器

首先要先讲解下定时器,js中的定时器有两种: setTimeout() 和 setInterval()

setTimeout()

setTimeout(code,millisec)

参数 解释
code 必需。要调用的函数后要执行的 JavaScript 代码串。
millisec 必需。在执行代码前需等待的毫秒数。

return 定时器 id,可以通过 clearTimeout(id) 取消定时器任务

注意:code可以为function或者string

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


var id = setTimeout(function(){
    console.log('console.log("10秒后打印")')
},10*1000)
// clearTimeout(id)

setInterval()

setInterval(code,millisec)

参数 解释
code 必需。要调用的函数后要执行的 JavaScript 代码串。
millisec 必需。在执行代码前需等待的毫秒数。

return 定时器 id,可以通过 clearInterval(id) 取消定时器任务

注意:code可以为function或者string

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


var id = setInterval(function(){
    console.log('console.log("10秒后打印")')
},10*1000)
// clearInterval(id)

使用setTimeout()实现setInterval()

function mySetInterval(fun,millisec){
    setTimeout(function(){
        fun();
        mySetInterval(fun,millisec)
    },millisec);
}

单线程模型

JavaScript是单线程的,同一时刻只能执行特定的任务。

进程和线程都是操作系统的概念。进程是应用程序的执行实例,每一个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成;进程在运行过程中能够申请创建和使用系统资源(如独立的内存区域等),这些资源也会随着进程的终止而被销毁。而线程则是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制。在系统创建进程之后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程是由系统进程所创建的,同时用户也可以自主创建其它线程,这一系列的线程都会并发地运行于同一个进程中。

但是我们经常看到如下代码

foo.onclick = function(e) {
    //...
};
bar.onclick = function(e) {
    //...
};

思考:当foo和bar被点击的时候,为什么会执行 function 里的代码?

从一道面试题说起

var t = true;

setTimeout(function (){
    t = false;
},1000);

while (t){}

alert('end');

输出是什么那?不少同学会不假思索的说1s后alert('end')。其实不然,执行这行代码会一直死循环。原因是js是单线程模型,setTimeout()是通过事件循环实现的。

事件循环

js中使用事件循环机制,实现并发(注意不是并行)。

js的事件循环是通过队列实现的,让我先了解下什么是队列

队列

栈是FIFO(先进先出)的数据结构

js实现
names = [];
names.push("Cynthia");
names.push("Jennifer");

names.shift();

names.push("foo");
names.push("bar");

names.shift();

把所有的push和shift放在一起并不影响结果

js中的应用

事件循环机制

栈是FILO(先进后出)的数据结构

js实现
names = [];
names.push("Cynthia");
names.push("Jennifer");

names.pop();

names.push("foo");
names.push("bar");

names.pop();

push和pop调换顺序会影响结果

js中的应用

函数调用栈

function a(){
    b();
}
function b(){
    c();
}
function c(){
    //抛出异常
    throw new Error("throw this")
}
c();

定时器是准确的吗?

执行下面代码

var start = new Date;
setInterval(function(){
    var end = new Date;
    console.log('Time elapsed:', end - start, 'ms');
    start = end;
}, 500);

打印出来的 Time elapsed 会发现不是精确的500ms,可能是499ms,也可能是501ms。

这好像看不出来js的定时器的不准确的规律。我们可以将代码改进一下,发现其规律

var start = new Date;
setInterval(function(){
    var end = new Date;
    console.log('Time elapsed:', end - start, 'ms');

}, 500);

这次我们打印的是从开始执行到当前时间的总时间差,我们可以发现定时器其实总是延后触发的。和事件循环这一机制匹配

让我们再执行下一段代码

var start = new Date;
var count = 0;
setInterval(function(){
    var end = new Date;
    console.log('Time elapsed:', end - start, 'ms');
    start = end;
    count++;
    if(count === 2){
        for(var i = 0; i < 1990000000; i++);
    }
}, 500);

这次第三次打印的 Time elapsed 有了显著变化

再换种写法试试

var start = new Date;
setInterval(function(){
    var end = new Date;
    console.log('Time elapsed:', end - start, 'ms');
    start = end;
}, 500);
for(var i = 0; i < 1990000000; i++);

这次是第一次打印的 Time elapsed 有了显著变化

我们自己实现的 mySetInterval() 和系统提供的 setInterval()行为一致吗?

  • id会不断改变,不利于取消
  • 事件处理函数内用时过长会影响下一次定时器触发时间

大家可以自行运行下面两段代码对比其中差异

var start = new Date;
setInterval(function(){
    var end = new Date;
    console.log('Time elapsed:', end - start, 'ms');
    start = end;
    for(var i = 0; i < 10000*10000; i++);
},1000)
var start = new Date;
mySetInterval(function(){
    var end = new Date;
    console.log('Time elapsed:', end - start, 'ms');
    start = end;
    for(var i = 0; i < 10000*10000; i++);
},1000)

results matching ""

    No results matching ""