1、单线程

JavaScript语言的一大特点就是单线程,也就是说,同-一个时间只能做一件事。JavaScript只能执行一个任务,其他任务只能等待。这是因为JavaScript这门脚本语言诞生的使命所致,即JavaScript是为处理页面中用户的交互,以及操作DOM而诞生的。比如,对某个DOM元素进行添加和删除操作,不能同时进行,应该先进行添加,之后再删除。

JS引擎在执行任务时,是一个一个执行的,如果有多个任务,则后面的任务只能等待

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务 这样所导致的问题是,如果JavaScript执行的时间过长,就会造成页面的渲染不连贯,导致页面渲染加载有阻塞的感觉。 为了更好地理解,下面我们通过段代码来演示。

console.log(1);
setTimeout(function () {
    console.log(3);
}, 5000);
console.log(2);

执行上述代码,在控制台会看到程序先输出了1、2,等待5秒后输出3。由此可见,当调用setTimeout()方法后,该方法会立即执行完成,然后执行后面的代码,在控制台中输出2。而为setTimeout()传入的函数,它会在5秒后执行。像这样的操作就称为异步操作。这个异步执行的函数称为回调函数,它的调用时机是由定时器来决定的。

2、同步和异步

为了更好地利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。于是JavaScript出现了同步和异步的概念。

所谓同步,就是前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做法,烧水煮饭,等水开了之后,再去切菜,炒菜。

所谓异步,就是在做一件事件的同时,可以去处理其他的事情。还以做饭为例,异步做法是,在烧水煮饭的同时去切菜炒菜。 同步任务都是在主线程上执行的,会形成一个执行栈,而异步任务是通过回调函数实现的。一般来说,异步任务有3种类型,第1种是普通事件,如click、resize 等;第2种是资源加载,如load、error等;第3种是定时器,如setInterval()、setTimeout()。

3、执行机制

当定时器的时间设为0的时候,就会产生一个问题, 到底是为定时器传入的回调函数优先执行,还是setTimeout()后面的代码优先执行呢?示例代码如下。

console.log(1);
setTimeout (function () {
    console.log(3);
    }, 0);
for (var i = 0, str = ''; i< 900000; i++) {
    str += i;
    //利用字符串拼接运算拖慢执行时间
console.log(2);

上述代码执行后,输出顺序为1、2、3。显然,为定时器传入的回调函数是最后执行的。为了降低偶然性,第5 ~ 7行的代码拖慢了执行时间,但最终结果仍然是3最后输出。 在JavaScript中,同步任务是优先执行的,它们会被放入执行栈中执行,而异步任务(回调函数)则被放人任务队列中,如下图所示。

在上图中,一且执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务就会结束等待状态,进入执行栈,开始执行。因为JavaScript的主线程会不断地重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( Event Loop )。

  • js代码开始执行后,主线程执行栈中会把任务分为两类.
  • 一类是同步任务, 一类是异步任务; 主线程执行栈优先执行同步任务,
  • 异步任务会被放入特定的处理程序中,满足条件后,被放到消息(任务/事件)队列中,
  • 主线程执行栈中所有的同步任务执行完毕之后,通过事件循环去消息(任务/事件)队列中,
  • 挑选优先满足条件的程序,放入主线程执行栈中执行。事件循环,周而复始,一直执行。
  • 微任务和宏任务

    在ES3 以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。 ​ Tick会触发浏览器渲染,Promise不会触发,所以更加轻量级,多使用;

    macro)task,可以理解是每次执行栈执行的代码就是一个宏任务

  • 主线程上的执行栈中的代码
  • 每次从事件队列中获取一个事件回调并放到执行栈中执行
  • 比如去银行排队办业务,每个人的业务就相当于是一个宏任务
  • 总结起来,宏任务有
  • setTimeout
  • setInterval
  • 微任务(microtask)是宏任务中的一个部分,它的执行时机是在同步代码执行之后,下一个宏任务执行之前。总结起来,微任务有:
  • Promise.then
  • process.nextTick(Node.js环境)
  • 运行机制总结

    JS优先执行同步任务,然后执行微任务,最后执行宏任务。

    setTimeout(() => {
     console.log('1')
    //执行后回调一个宏事件console.log('1')
    },0)
    console.log('2');
    new Promise((resolve) =>{console.log('3');
    resolve()
    }).then(() => {console.log('4');}).then(()=>{
    console.log('5')})
    console.log('6')          打印结果 // 2 3 6 4 5 1
    复制代码
  • 这一次,彻底弄懂 JavaScript 执行机制
  •