从setTimeout谈JavaScript运行机制

前言

最近在看些JavaScript异步的东西,但是由于时间有限,才刚看了个头,不得不中途停止。为了方便日后查阅以备重拾,遂记录一点体会,如果能使得他人有所收获,那更是极好的。其实本文与异步并没有太大关系。
从setTimeout说起

众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执行完才能有机会执行,不像人一样,人是多线程的,所以你可以一边观看某岛国动作片,一边尽情挥洒汗水。JavaScript单线程机制也是迫不得已,假设有多个线程,同时修改某个dom元素,那么到底是听哪个线程的呢?

既然已经明确JavaScript是单线程的语言,于是我们想方设法要想出JavaScript的异步方案也就可以理解了。比如执行到某段代码,需求是1000ms后调用方法A,JavaScript没有sleep函数能挂起线程一秒啊?如何能够使得代码做到一边等待A方法执行,一边继续执行下面的代码,仿佛开了两个线程一般?机制的科学家们想出了setTimeout方法。

setTimeout方法想必大家都已经很熟悉了,那么setTimeout(function(){..}, a)真的是ams后执行对应的回调吗?

setTimeout(function() {
  console.log("hello world");
}, 1000);

while(true) {};

1s中之后,控制台并没有像预料中的一样输出字符串,而网页标签上的圈圈一直转啊转,掐指一算,可能陷入while(true){}的死循环中了,可是为什么呢?虽然会陷入死循环可是也得先输出字符串啊!这就要扯到JavaScript运行机制了。

JavaScript运行机制

一段JavaScript代码到底是如何执行的?阮一峰老师有篇不错的文章(JavaScript 运行机制详解:再谈Event Loop), 我就不再重复造轮子了;如果觉得太长不看的话,楼主简短地大白话描述下。一段js代码(里面可能包含一些setTimeout、鼠标点击、ajax等事 件),从上到下开始执行,遇到setTimeout、鼠标点击等事件,异步执行它们,此时并不会影响代码主体继续往下执行,一旦异步事件执行完,回调函数 返回,将它们按次序加到执行队列中,这时要注意了,如果主体代码没有执行完的话,是永远也不会触发callback的,这也就是上面的一段代码导致浏览器假死的原因(主体代码中的while(true){}还没执行完)。

  网上还有一篇流传甚广的文章(猛戳How JavaScript Timers Work),文章里有张很好的图,我把它盗过来了。

  文章里没有针对这幅图的代码,为了能更好的说明流程,我尝试着给出代码:

// some code

setTimeout(function() {
  console.log("hello");
}, 10);

// some code

document.getElementById("btn").click();

// some code

setInterval(function() {
  console.log("world");
}, 10);

// some code

我们开始执行代码。第一块代码大概执行了18ms,也就是JavaScript的主体代码,在执行过程中,先触发了一个setTimeout函数, 代码继续执行,只等10ms后响应setTimeout的回调,接着是一个鼠标点击事件,该事件有个回调(或许是alert一些东西),不能立即执行(单 线程),因为js主体代码还没执行完,所以这个回调被插入执行队列中,等待执行;接着setInterval函数被执行,我们知道,此后每隔10ms都会 有回调(尝试)插入队列中,运行到第10ms的时候,setTimeout函数的回调插入队列。js函数主体运行完后,大概是18ms这个点,我们发现队 列中有个click的callback,还有个setTimeout的callback,于是我们先运行前者,在运行的过程中,setInterval的 10ms响应时间也过了,同样回调被插入队列。click的回调运行完,运行setTimeout的回调,这时又10ms过去了,setInterval 又产生了回调,但是这个回调被抛弃了,之后发生的事大家都一目了然了。

  这里有一点我不太明白,就是关于interval回调的drop。按照How JavaScript Timers Work里的说法是,如果等待队列里已经有同一个interval函数的回调了,将不会有相同的回调插入等待队列。

 查到一篇前辈的文章Javascript定时器学习笔记,里面说“为了确保定时器代码插入到队列总的最小间隔为指定时间。当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才能将定时器代码添加到代码队列中”。但是我自己实践了下觉得可能并非如此:

var startTime = +new Date;
var count = 0;
var handle = setInterval(function() {
  console.log("hello world");
  count++;
  if(count === 1000) clearInterval(handle);
}, 10);

while(+new Date - startTime < 10 * 1000) {};

 按照上文的说法,由于while对线程的“阻塞”,使得相同的setInterval的回调不能加在等待队列中,但是实际在chrome和ff的 控制台都输出了1000个hello world的字符串,我也去原文博主的文章下留言询问了下,暂时还没答复我;也可能是我对setInterval的认识的姿势不对导致,如果有知道的朋友 还望不吝赐教,万分感激!

  总之,定时器仅仅是在未来的某个时刻将代码添加到代码队列中,执行时机是不能保证的。

setTimeout VS setInterval

  以前看到过这样的话,setInterval的功能都能用setTimeout去实现,想想也对,无穷尽地递归调用setTimeout不就是setInterval了吗?

setInterval(function() {   // some code
}, 10);

  根据前文描述,我们大概懂了以上setInterval回调函数的执行时间差<=10ms,因为可能会由于线程阻塞,使得一系列的回调全部在排队。用setTimeout实现的setInterval效果呢?

// 1
function func() {
  setTimeout(function() {
    // some code
    func();
  }, 10);
}

func();

// 2
setTimeout(function() {
  // some code
  setTimeout(arguments.callee, 1000);
}, 10);

 很显然两个回调之间的间隔是>10ms的,因为前面一个回调在队列中排队,如果没有等到,是不会执行下面的回调的,而>10ms是因 为回调中的代码也要执行时间。换句话说,setInterval的回调是并列的,前一个回调(有没有执行)并不会影响后一个回调(插入队列),而 setTimeout之间的回调是嵌套的,后一个回调是前一个回调的回调(有点绕口令的意思)

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索javascript
, 线程
, 阮一峰
, 队列
, 代码
, 回调方法不执行
, setinterval
, settimeout
, 不会执行回调函数
, javascript事件机制
, 代码执行
, 执行Javascript
, block回调机制
回调函数队列
javascript运行机制、javascript的运行机制、javascript 钩子机制、javascript 事件机制、javascript回收机制,以便于您获取更多的相关知识。

时间: 2024-11-16 03:46:02

从setTimeout谈JavaScript运行机制的相关文章

javascript运行机制之this详细介绍

 这篇文章主要介绍了javascript运行机制之this,需要的朋友可以参考下 this是面向对象语言中一个重要的关键字,理解并掌握该关键字的使用对于我们代码的健壮性及优美性至关重要.而javascript的this又有区别于Java.C#等纯面向对象的语言,这使得this更加扑朔迷离,让人迷惑.   this使用到的情况: 1. 纯函数 2. 对象方法调用 3. 使用new调用构造函数 4. 内部函数 5. 使用call / apply  6.事件绑定   1. 纯函数    代码如下: v

【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:"看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲一下就可以猜到那是指同步操作(自然不走event loop了):至于watcher啥的,显然只是实现上的特色,即使用同一个queue实现也未尝不可" [原帖: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 作者:阮一峰] 一年前,我写了

JavaScript 运行机制详解:再谈Event Loop

一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck in an event-loop>.这才尴尬地发现,自己的理解是错的.我决定重写这个题目,详细.完整.正确地描述JavaScript引擎的内部运行机制.下面就是我的重写. 进入正文之前,插播一条消息.我的新书<ECMAScript 6入门>出版了(版权页,内页1,内页2),铜版纸全彩印刷,非常

JavaScript运行机制之事件循环(Event Loop)详解_javascript技巧

一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线

javascript运行机制之this详细介绍_基础知识

this是面向对象语言中一个重要的关键字,理解并掌握该关键字的使用对于我们代码的健壮性及优美性至关重要.而javascript的this又有区别于Java.C#等纯面向对象的语言,这使得this更加扑朔迷离,让人迷惑. this使用到的情况:1. 纯函数2. 对象方法调用3. 使用new调用构造函数4. 内部函数5. 使用call / apply 6.事件绑定 1. 纯函数 复制代码 代码如下: var name = 'this is window';  //定义window的name属性  f

详解javascript new的运行机制_javascript技巧

和其他高级语言一样javascript 中也有new 运算符,我们知道new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过new 来产生对象? 本文将带你一起来探索 javascript 中 new 的奥秘... 一.认识new运算符 function Animal(name){ this.name = name; } Animal.color = "black"; Animal.prototype.say = f

mapreduce运行机制

谈mapreduce运行机制,可以从很多不同的角度来描述,比如说从mapreduce运行流程来讲解,也可以从计算模型的逻辑流程来进行讲解,也许有些深入理解了mapreduce运行机制还会从更好的角度来描述,但是将mapreduce运行机制有些东西是避免不了的,就是一个个参入的实例对象,一个就是计算模型的逻辑定义阶段,我这里讲解不从什么流程出发,就从这些一个个牵涉的对象,不管是物理实体还是逻辑实体. 首先讲讲物理实体,参入mapreduce作业执行涉及4个独立的实体: 客户端(client):编写

谈一谈JS消息机制和事件机制的理解_javascript技巧

消息/事件机制是几乎所有开发语言都有的机制,并不是deviceone的独创,在某些语言称之为消息(Event),有些地方称之为(Message). 其实原理是类似的,只不过有些实现的方式要复杂一点.我们deviceone统一就叫消息. 消息基础概念 还有一些初学者不太熟悉这个机制,我们先简单介绍一些基础概念,如果熟悉的人可以跳过这个部分. 一个/条消息可以理解为是一个数据结构,包含以下几个基本部分: 1.消息源:就是消息的来源,发出这个消息的对象 2.消息名:就是消息的唯一标示 3.消息数据:消

阿里巴巴技术文章分享 Javascript继承机制的实现_javascript技巧

Javascript作为一门脚本语言,在设计之初并没有考虑到面向对象的特性.即便到了当今这个遍布现代浏览器的年代,各种Javascript 框架/库如雨后春笋般地疯狂生长,Javascript中连个 class 关键字都没有.如果你要编写一个类,你还得借助于function,至于继承.重载什么的,就别奢望了. 可是,没有继承,日子怎么过啊?难道把所有的共有逻辑都拷贝一遍,实现最低级的代码复用? 答案当然是--NO,所以,我们要自己实现继承! 目标 最关键的目标当然是继承--子类自动拥有父类的所有