js的执行机制(简易理解懂)

转载别人的。js是一门单线程语言。

首先了解一下

js为什么是单线程的?为什么需要异步? 单线程又是如何实现异步的?

(1).为什么是单线程。

现在有2个进程,process1 process2,由于是多进程的JS,所以他们对同一个dom,同时进行操作

process1 删除了该dom,而process2 编辑了该dom,同时下达2个矛盾的命令,浏览器究竟该如何执行呢?

所以 js是单线程的。

(2).js为什么需要异步?

如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。 对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验

(3)js如何实现异步的呢?

是通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制

1  js事件循环

setTimeout(function(){
    console.log('定时器开始啦')
});
 
new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});
 
console.log('代码执行结束');

console的结果是:

// 马上执行for循环了。

// 代码执行结束

//执行then函数

//定时器开始了

 

js的事件循环

同步任务和异步任务

当我们打开网站,网页的渲染就是一大堆同步任务,比如页面骨架和页面元素的渲染。

加载音乐图片之类占资源大耗时久的任务,就是异步任务。

上图的解释说明:

  • 同步和异步任务分别进入不通过的‘执行场所’。 同步的进入主线程。异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程的任务执行完毕的时候,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程不断重复。也就是事件循环-Event Loop

怎么知道主线程的执行栈是否为空呢?  js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

let obj = [];
$.ajax({
    url:www.baidu.com,
    data:obj,
    success: () => {
        console.log('发送成功')
    }
})
console.log('代码执行结束');

上面是一段ajax请求

  1. ajax先进入Event Table,然后注册回调函数success。
  2. 执行console.log(‘代码执行结束’)
  3. ajax事件完成,回调函数success进入Event Queue、
  4. 主线程从Event Queue读取回调函数success并执行。

 

2 setTimeout

我们可以用它来实现异步延时执行。例如

setTimeout(() => {
    console.log('延迟3秒')
}, 3000)

但是有时候写的延迟3秒,实际5,6秒才执行函数,这有是何缘故?

先看一个例子

setTimeout(() => {
    task();
}, 3000)
console.log('执行console')

因为setTimeout是异步的,他的结果是   1. 执行console .2.task()

如果修改一下前面的代码

setTimeout(() => {
    task()
}, 3000)

sleep(1000000)

看起来差不多,但是却发现控制台task()需要的时间远远的超过3秒。为什么这么长时间呢?

这时候就要重新理解定义一下setTimeout。 先说下上述代码的执行顺序

  • task()进入Event Table并注册,计时开始。
  • 执行sleep函数,十分慢,计时仍在继续。
  • 3秒到了,计时事件timeout完成,task()进入Event Queue, 但是sleep还没有执行完。。
  • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

上述的流程走完,我们知道setTimeout这个函数,是经过指定的时间后,吧要执行的任务(比如task() )放到Event Queue中,又因为是单线程任务,要一个一个执行,如果前面的任务需要的时间太久,那么只能等着。导致真正的延迟时间远远大于3秒。

我们还经常遇到setTimeout(fn, 0)这样的代码, 0秒后执行是不是刻意立即执行呢?

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

举例说明:

// 例子1
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
}, 0)

//例子2
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
}, 3000)

代码的输出结果分别是:

//例子1
先执行这里
执行啦

//例子2
先执行这里
... 3s later
执行啦

关于setTimeout要补充的是,即使主线程为空,0毫秒实际上也是达不到的。根据html的标准,最低是4毫秒。

为什么是最低是4毫秒? 原因有两个

一是W3C标准规定setTimeout中最小的时间周期是4毫秒,凡是低于4ms的时间间隔都按照4ms来处理

二是述举例,我们可以看出js执行的时候,主线程碰到定时器的时候,是不会直接处理的,应该是先把定时器事件交给定时器线程去处理,这时主线程继续执行下面的代码,同时定时器线程开始计时处理,等到计时完毕,事件循环线程会把定时器要执行的操作放在事件队列末尾,等主线程空闲的时候再来执行事件队列里面的操作。

3 setInterval

与setTimeout类似,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue, 如果前面的任务耗时太久,那么同样失去等待。

应该使用setTimeout还是setInterval?

举例,使用setTimeout模拟setInterval.

vay say = function() {
    setTimeout(say, 1000)
    console.log('hello, world')
}
setTimeout(say, 1000)

这样js碰到定时器,会交给定时器线程处理,然后等计时完毕,定时器里面的操作添加到事件队列,等主线程空闲去执行。

主线程执行时又会遇到定时器,又开始执行上面的一系列操作。

这样做会在每一次定时器执行完毕才开始下一个定时器,其中的误差只是等待主线程空闲所需要等待的时间。

而setInterval是规定每隔固定的时间就往定时器线程中推入一个时间,这样做有一个问题,就是累积效应。

累积效应:就是如果定时器里面的代码执行所需的时间大于定时器的执行周期,就会出现累计效应。简单来说就是上一次定时器里面的操作还没执行完毕,下一次定时器事件又来了。

累积效应会导致有些事件丢失。为了保险起见,尽量使用setTimeout而不是使用setInterval。

 

4 Promise与process.nextTick(callback)

promise的学习可以参考阮一峰老师的Promise

process.nextTick(callback),在事件循环的下一次循环中调用callback回调函数

除了广义的同步任务和异步任务,我们队任务有更精细de 定义:

macro-task(宏任务):包括整体代码script  setTimeout setInterval

micro-tack(微任务):Promise, process.nextTick

不同类型的任务会进入对应的Event Queue,比如setTimeout setInterval会进入相同的Event Queue。

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

举例说明:

setTimeout(function() {
    console.log('setTimeout')
})

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

console.log('console')

这段代码作为宏任务,进入主线程。

先遇到setTimeout,将其回调函数注册后分发到宏任务Event Queue。

接下来遇到promise, new Promise立即执行,then函数分发到微任务Event Queue。

遇到console.log(),立即执行。

好啦,整体代码script作为第一个宏任务执行结束。看看有哪些微任务? then在微任务Event Queue里面,执行。

OK,第一轮事件循环结束了,开始第二轮循环。从宏任务Event Queue开始,然后是setTimeout对应的回调函数,立即执行。

事件循环,宏任务,微任务的关系如图所示:

 

接下来是一段复杂的代码,测试是否真的掌握了js的执行机制。

console.log('1');
setTimeout(function() {    
    console.log('2');    
    process.nextTick(function() {            
        console.log('3');    
    })    
    new Promise(function(resolve) {        
        console.log('4');        
        resolve();    
    }).then(function() {        
        console.log('5')    
    })
})
process.nextTick(function() {    
    console.log('6');
})
new Promise(function(resolve) {    
    console.log('7');    
    resolve();
}).then(function() {    
    console.log('8')
})
setTimeout(function() {    
    console.log('9');    
    process.nextTick(function() {       
        console.log('10');    
    })
    new Promise(function(resolve) {        
        console.log('11');       
        resolve();    
    }).then(function() {        
        console.log('12')    
    })
})

第一轮事件循环流程。

整体script作为第一个宏任务进入主线程。遇到console.log。 输出1。

遇到setTimeout,,将其回调函数分发到宏任务Event Queue中,暂且记为setTimeout1。

遇到process.nextTrick(),将其回调函数分发到微任务Event Queue。 暂记process1。

遇到promise。newPromise直接指向。输出7。then函数被分发到微任务Event Queue。暂记then1。

又遇到了setTimeout,回调函数分发到宏任务Event Queue中,暂记setTimeout2。

宏任务微任务
setTimeout1process1
setTimeout2then1

执行微任务 

process1  ,输出6。

then1,输出8。

至此,第一轮循环正式结束。 这一轮的结果是 1,7,6,8。

第二轮从setTimeout1开始。过程同上。输出2,4,3,5。

第三轮从setTimeout2开始。过程同上。输出9,11,10,12,。

所以完成的输出是 1 7 6 8 2 4 3 5 9 11 10 12

(请注意。node坏境下的事件监听依赖与前端环境不完全相同,输出顺序可能有误差。)

 

总结

1.js的异步。

js是一门单线程语言,不管是什么新框架新语法糖实现的所谓异步。其实都是用同步的方法去模拟的。

2.事件循环 Event loop

事件循环是js实现异步的一种方法,也是js的执行机制。

3.js的执行和运行。

执行和运行有很大区别,js在不同的环境下,比如node,浏览器,ringo等等,执行方式不同。而运行大多是指js解析引擎,是统一的。

 

转载别人的,不知道原作者是谁,但是讲的很清晰,理解容易多了,感谢。

自己手动敲了一遍,补充加了一些别的内容。清晰了很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值