理解JavaScript的执行机制

一直没有深入了解过JavaScript的事件执行机制,直到看到了这篇文章:《这一次,彻底弄懂JavaScript执行机制》 才发觉熟悉JavaScript的执行机制非常重要。

毕竟在跟进项目中偶尔需要排查为什么会出现函数执行顺序不一样的情况。

感谢作者浅显易懂的文字让我获益匪浅,以下是自己对JavaScript执行机制的理解,全是流水账。

文章主要叙述:

1:单线程和异步任务

2: 异步任务的分类

3:setTimeout 和 setInterval 的执行方式

单线程和异步任务

JavaScript的特点就是单线程,也就是说,同一个时间只能做一件事。换句话说就是一行一行地按照顺序执行代码:

console.log(1);

let timeId = setTimeout(() => {
    console.log(2);
},0);
console.log(3);

运行上面的代码预想中的是1,2,3。但实际上打印出来的是:1,3,2

从上面的运行结果来看JavaScript等所有的同步任务执行完之后,再去执行异步任务

这是因为虽然JavaScript是单线程操作,但如果不做一些处理遇到类似setTimeout之类的异步操作就会导致阻塞。

这里有一个疑问了,在主线程执行同步任务的过程中,异步任务就真的任何事情都不做了吗?

实际上:

除了主线程之外,还存在一个"任务队列"(task queue)。

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务。

只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。然后通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

等主线程执行完同步任务后就会回过头去查找任务队列中有哪些事件,事件对应的异步任务进入执行栈,开始执行。执行完该任务后再去查找任务队列中可执行的异步任务,直到任务队列清空。

所以这里执行顺序是这样的:

1:执行console.log(1)

2:遇到timeId是异步任务,先放到任务队列

3:立即执行console.log(3)

4:同步任务执行完毕,去查找任务队列里面的事件,发现timeId有运行结果了,执行timeId。队列中没有其他的事件了,主线程运行完毕。

再来看段代码:

   let timeId = setTimeout(() => {
        function task() {
            console.log(‘会2秒之后运行吗‘);
        }
        task();
    },2000);

    let oldTime = new Date();
    function sleep(){
        let newTime = new Date();
        console.log(‘time:‘ + ( newTime.getSeconds() - oldTime.getSeconds()));
        if( newTime.getSeconds() - oldTime.getSeconds() < 5) {
            sleep();
        }
    }
    sleep();

运行上面代码,发现task并没有在2秒之内执行而是在5秒之后才执行。

这是因为虽然timeId暂时被挂起,并且在2秒后有了运行结果后在"任务队列"之中放置一个事件通知主线程timeId可以执行了。

但因为主线程中的同步任务sleep要5秒之后才运行完毕,导致执行栈5秒后才去任务队列中执行等待中的timeId函数

(好比约了朋友去玩,出门前要换衣服,要约滴滴打车。用手机约好车之后就去换衣服,但是换衣服实在太久了,车都来了衣服还没换好。司机打电话说我到了,你快过来坐车吧。

我跟司机说,衣服没换好,你先等等吧。过了半小时之后终于换好了衣服(司机等得要砍人了)终于可以去坐车了)

总得来说其实JavaScript只有一个主线程,运行过程中碰到异步任务就先挂起。而有了运行结果表示准备好了可以执行的异步任务就进入执行栈中在同步任务后面去排队。

异步任务的分类:

macro-task(宏任务):setTimeout、setInterval、setImmediate、I/O(ajax)、UI交互事件(onClick,onScroll...)
micro-task(微任务):Promise、process.nextTick、MutaionObserver

不同的API注册的异步任务会依次进入自身对应的队列中,然后等待Event Loop(事件循环)将它们依次放入主线程中执行

进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务

测试一下:

console.log(1);

let timeId = setTimeout(() => {
    console.log(2);
    let Promise02 = new Promise(resolve => {
        console.log(3);
        resolve();
    }).then(() => {
        console.log(4);
    });
},0);

let Promise01 = new Promise(resolve => {
    console.log(5);
    resolve();
}).then(() => {
    console.log(6);
});

console.log(7); 

打印出来是:1,5,7,6,2,3,4

所以执行顺序实际上是这样的:

1:执行宏任务(整体代码)

2:执行微任务(Promise01的then方法)

3:执行宏任务(timeId)

4:执行微任务(timeId里面Promise02的then方法)

setTimeout和setInterval的执行方式

setTimeout和setInterval都是异步可以延时执行的方法。

setTimeout(fn,ms)到了ms秒后fn会进入任务队列去等待执行,而setInterval(fn, ms)则是每到了ms秒都会有一个fn进入任务队列去排队等待执行,直到某个条件结束。

关于setTimeout(fn,ms)中的ms设置:

let timeId01 = setTimeout(() => {
    console.log(1);
},1);
let timeId02 = setTimeout(() => {
    console.log(2);
},0);

上面代码中timeId01的timer设置为1,timeId02的timer设置为0。按理说timeId02的执行顺序比timeId01要优先,但实际上打印结果是:1,2

这是因为一般来说timer最小只能设置4ms(嵌套层超过5,并且设置的timeout的时间少于4才有效)。但为了向实现看齐可最小设置1ms,0ms的时候会被设置为1ms

其中setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

原文地址:https://www.cnblogs.com/Travel/p/8320761.html

时间: 2024-08-15 01:43:25

理解JavaScript的执行机制的相关文章

深入理解JavaScript系列+ 深入理解javascript之执行上下文

http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html http://blog.csdn.net/hi_kevin/article/details/37761919    深入理解javascript之执行上下文(execution context)

javascript的执行机制—Event Loop

既然今天要谈的是javascript的事件循环机制,要理解事件循环,首先要知道事件循环是什么. 我们先从一个例子来看一下javascript的执行顺序. <script> setTimeout(function() { console.log('定时器开始了.'); },0) new Promise(function(resolve) { console.log('马上执行for循环了'); for (let i = 0; i < 10000; i++) { i == 99 &&

深入理解JavaScript事件循环机制

前言 众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心.可看HTML规范中的这段话: To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Ther

【深入理解javascript】执行上下文

参考原文:执行上下文 1.每一个执行上下文,工作分为三个阶段: 准备阶段–>执行阶段–>调用阶段 准备阶段:代码执行之前,设置数据,相当于初始化. 执行阶段:开始执行每一行代码. 调用阶段:可能没有.如果有函数调用,产生新的执行上下文.函数每被调用一次,都会产生一个新的执行上下文环境. 2.代码段 javascript在执行一个代码段之前,都会进行"准备工作"来生成执行上下文.这个"代码段"其实分三种情况--全局代码,函数体,eval代码. 全局代码是一

深入理解javascript中执行环境(作用域)与作用域链

相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行环境分为全局执行环境和局部执行环境,其中局部执行环境我们又可以称之为函数执行环境.那么究竟什么使执行环境呢?通俗的说,执行环境即为代码执行时所处的环境.我们下来看一看如下代码,再进一步分析之. 1 2 3 4 5 6 7 8 9 10 11 <script><br>var name="zhuzhenwei"; functio

深入理解Javascript之执行上下文(Execution Context)

在这篇文章中,将比较深入地阐述下执行上下文 - Javascript中最基础也是最重要的一个概念.相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的.什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一个函数时,运行函数体中的代码. Eva

理解Javascript之执行上下文(Execution Context)

1>什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一个函数时,运行函数体中的代码. Eval的代码 - 在Eval函数内运行的代码. javascript是一个单线程语言,这意味着在浏览器中同时只能做一件事情.当javascript解释器初始执行代码,它首先默认进入全局上下文.每次调用一个函数将会创建一个新的执行上下文. 每次新创建的一个执行上下文会被添加

深入理解javascript new的机制

我们在使用对象的时候,除了一些浏览器内置的单体对象可以直接使用外,都会new一个出来使用. 1.最简单的莫过于如下获取一个Object对象实例 var obj = new Object(); 说明:此时的new关键字干了最有用的一件事就是,继承了所有Object.prototype上的方法,这个可以去查看一下es5的参考资料中Object.prototype的方法列表.也就是说此时的obj对象可以使用所有继承而来的方法了! 2.然而是构造函数模式让我们对new有了一个深入的了解! functio

JavaScript可否多线程? 深入理解JavaScript定时机制

http://www.phpv.net/html/1700.html JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 setTimeout( function(){ alert(’你好!’); } , 0); setInterval( callbackFunction , 100); 认为setTimeout中的问候方法会立即被执行,因为这并不是凭空而说,而是Java