nodejs是单线程执行的,同时它又是基于事件驱动的非阻塞IO编程模型。这就使得我们不用等待异步操作结果返回,就可以继续往下执行代码。当异步事件触发之后,就会通知主线程,主线程执行相应事件的回调。
本篇文章讲解node中JavaScript的代码的执行流程,下面是测试代码,如果你知道输出的结果,那么就不需要再看本篇文章,如果不知道输出结果,那么本片文章可帮助你了解:
console.log(1)
setTimeout(function () {
new Promise(function (resolve) {
console.log(2)
resolve()
})
.then(() => { console.log(3) })
})
setTimeout(function () {
console.log(4)
})
复杂的:
setTimeout(() => {
console.log('1')
new Promise((resolve) => { console.log('2'); resolve(); })
.then(() => { console.log('3') })
new Promise((resolve)=> { console.log('4'); resolve()})
.then(() => { console.log('5') })
setTimeout(() => {
console.log('6')
setTimeout(() => {
console.log('7')
new Promise((resolve) => { console.log('8'); resolve() })
.then( () => { console.log('9') })
new Promise((resolve) => { console.log('10'); resolve() })
.then(() => { console.log('11') })
})
setTimeout(() => { console.log('12') }, 0)
})
setTimeout(() => { console.log('13') }, 0)
})
setTimeout(() => { console.log('14') }, 0)
new Promise((resolve) => { console.log('15'); resolve() })
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })
.then(() => { console.log('18') })
node.js启动过程可以分为以下步骤:
Nodejs 将消息循环又细分为 6 个阶段(官方叫做 Phase), 每个阶段都会有一个类似于队列的结构, 存储着该阶段需要处理的回调函数.
Nodejs 为了防止某个 阶段 任务太多, 导致后续的 阶段 发生饥饿的现象, 所以消息循环的每一个迭代(iterate) 中, 每个 阶段 执行回调都有个最大数量. 如果超过数量的话也会强行结束当前 阶段而进入下一个 阶段. 这一条规则适用于消息循环中的每一个 阶段.
这是消息循环的第一个阶段, 用一个 for 循环处理所有 setTimeout 和 setInterval 的回调.
这些回调被保存在一个最小堆(min heap) 中. 这样引擎只需要每次判断头元素, 如果符合条件就拿出来执行, 直到遇到一个不符合条件或者队列空了, 才结束 Timer Phase.
Timer 阶段中判断某个回调是否符合条件的方法也很简单. 消息循环每次进入 Timer 的时候都会保存一下当时的系统时间,然后只要看上述最小堆中的回调函数设置的启动时间是否超过进入 Timer 时保存的时间, 如果超过就拿出来执行.
执行除了close callbacks、setTimeout()、setInterval()、setImmediate()回调之外几乎所有回调,比如说TCP连接发生错误、 fs.read, socket 等 IO 操作的回调函数, 同时也包括各种 error 的回调.
系统内部的一些调用。
这是整个消息循环中最重要的一个 阶段, 作用是等待异步请求和数据,因为它支撑了整个消息循环机制.
poll阶段有两个主要的功能:一是执行下限时间已经达到的timers的回调,一是处理poll队列里的事件。
注:Node的很多api都是基于事件订阅完成的,比如fs.readFile,这些回调应该都在poll阶段完成。
当事件循环进入poll阶段:
poll队列为空的时候,这里有两种情况。
如果代码没有被设定setImmediate()设定回调:
Poll阶段,当js层代码注册的事件回调都没有返回的时候,事件循环会暂时阻塞在poll阶段,解除阻塞的条件:
- 在poll阶段执行的时候,会传入一个timeout超时时间,该超时时间就是poll阶段的最大阻塞时间。
- timeout时间未到的时候,如果有事件返回,就执行该事件注册的回调函数。timeout超时时间到了,则退出poll阶段,执行下一个阶段。
这个 timeout 设置为多少合适呢? 答案就是 Timer Phase 中最近要执行的回调启动时间到现在的差值, 假设这个差值是 detal. 因为 Poll Phase 后面没有等待执行的回调了. 所以这里最多等待 delta 时长, 如果期间有事件唤醒了消息循环, 那么就继续下一个 Phase 的工作; 如果期间什么都没发生, 那么到了 timeout 后, 消息循环依然要进入后面的 Phase, 让下一个迭代的 Timer Phase 也能够得到执行.
Nodejs 就是通过 Poll Phase, 对 IO 事件的等待和内核异步事件的到达来驱动整个消息循环的.
这个阶段只处理 setImmediate 的回调函数.
那么为什么这里要有专门一个处理 setImmediate 的 阶段 呢? 简单来说, 是因为 Poll 阶段可能设置一些回调, 希望在 Poll 阶段 后运行. 所以在 Poll 阶段 后面增加了这个 Check 阶段.
专门处理一些 close 类型的回调. 比如 socket.on('close', ...). 用于资源清理.
1、node初始化
2、进入事件循环
2.1、进入Timer阶段
2.2、进入Pending I/O Callback阶段
这个阶段与JavaScript关系不大,略过
2.4、进入Poll阶段
首先检查是否存在尚未完成的回调,如果存在,分如下两种情况:
第一种情况:有可执行的回调
第二种情况:没有可执行的回调
2.5、进入check阶段
2.6、进入closing阶段
3、检查是否有活跃的handles(定时器、IO等事件句柄)
注意:
事件循环的每一个子阶段退出之前都会按顺序执行如下过程:
事件循环队列先保证所有的process.nextTick回调,然后将所有的Promise回调追加在后面,最终在每个阶段结束的时候一次性拿出来执行。
此外,process.nextTick和Promise回调的数量是受限制的,也就是说,如果一直往这个队列中加入回调,那么整个事件循环就会被卡住。
这两个方法的回调到底谁快?
如下面的例子:
setImmediate(() => console.log(2))
setTimeout(() => console.log(1))
使用nodejs多次执行后,发现输出结果有时是1 2,有时是2 1。
对于多次执行输出结果不同,需要了解事件循环的基础问题。
首先,Nodejs启动,初始化环境后加载我们的JS代码(index.js).发生了两件事(此时尚未进入消息循环环节):
setImmediate 向 Check 阶段 中添加了回调 console.log(2);setTimeout 向 Timer 阶段 中添加了回调 console.log(1)
这时候, 要初始化阶段完毕, 要进入 Nodejs 消息循环了。
为什么会有两种输出呢? 接下来一步很关键:
当执行到 Timer 阶段 时, 会发生两种可能. 因为每一轮迭代刚刚进入 Timer 阶段 时会取系统时间保存起来, 以 ms(毫秒) 为最小单位.
如果 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间, 则执行 Timer 阶段 中的该回调. 这种情况下先输出 1, 直到 Check 阶段 执行后,输出2.总的来说, 结果是 1 2.
如果运行比较快, Timer 阶段 中回调预设的时间可能刚好等于消息循环所保存的时间, 这种情况下, Timer 阶段 中的回调得不到执行, 则继续下一个 阶段. 直到 Check 阶段, 输出 2. 然后等下一轮迭代的 Timer 阶段, 这时的时间一定是满足 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间 , 所以 console.log(1) 得到执行, 输出 1. 总的来说, 结果就是 2 1.
所以, 输出不稳定的原因就取决于进入 Timer 阶段 的时间是否和执行 setTimeout 的时间在 1ms 内. 如果把代码改成如下, 则一定会得到稳定的输出:
require('fs').readFile('my-file-path.txt', () => {
setImmediate(() => console.log(2))
setTimeout(() => console.log(1))
});
这是因为消息循环在 Pneding I/O Phase 才向 Timer 和 Check 队列插入回调. 这时按照消息循环的执行顺序, Check 一定在 Timer 之前执行。
从性能角度讲, setTimeout 的处理是在 Timer Phase, 其中 min heap 保存了 timer 的回调, 因此每执行一个回调的同时都会涉及到堆调整. 而 setImmediate 仅仅是清空一个队列. 效率自然会高很多.
再从执行时机上讲. setTimeout(..., 0) 和 setImmediate 完全属于两个阶段.
下面以一段代码来说明nodejs运行JavaScript的机制。
如下面一段代码:
setTimeout(() => { // settimeout1
console.log('1')
new Promise((resolve) => { console.log('2'); resolve(); }) // Promise3
.then(() => { console.log('3') })
new Promise((resolve)=> { console.log('4'); resolve()}) // Promise4
.then(() => { console.log('5') })
setTimeout(() => { // settimeout3
console.log('6')
setTimeout(() => { // settimeout5
console.log('7')
new Promise((resolve) => { console.log('8'); resolve() }) // Promise5
.then( () => { console.log('9') })
new Promise((resolve) => { console.log('10'); resolve() }) // Promise6
.then(() => { console.log('11') })
})
setTimeout(() => { console.log('12') }, 0) // settimeout6
})
setTimeout(() => { console.log('13') }, 0) // settimeout4
})
setTimeout(() => { console.log('14') }, 0) // settimeout2
new Promise((resolve) => { console.log('15'); resolve() }) // Promise1
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() }) // Promise2
.then(() => { console.log('18') })
上面代码执行过程:
node初始化
执行JavaScript代码
执行微任务
进入第一次事件循环
进入Timer阶段
执行settimeout1回调:
进入 Poll 阶段
执行settimeout3回调
进入第二次事件循环
进入Timer阶段
执行settimeout5回调:
作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的
javascript是一门单线程语言,Event Loop是javascript的执行机制.牢牢把握两个基本点,以认真学习javascript为中心,早日实现成为前端高手的伟大梦想!
钩子机制也叫hook机制,或者你可以把它理解成一种匹配机制,就是我们在代码中设置一些钩子,然后程序执行时自动去匹配这些钩子;这样做的好处就是提高了程序的执行效率,减少了if else 的使用同事优化代码结构
在讲小程序的更新机制之前,我们需要先了解小程序的2种启动模式,分别为:冷启动和热启动。小程序不同的启动方式,对应的更新情况不不一样的。无论冷启动,还是热启动。小程序都不会马上更新的,如果我们需要强制更新,需要如何实现呢?
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其JWT的组成:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
本文介绍JavaScript运行机制,JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。
现在有一个需求,前端有一个按钮,点击以后会调用后端一个接口,这个接口会根据用户的筛选条件去hadoop上跑任务,将图片的base64转为img然后打包成zip,生成一个下载连接返回给前端,弹出下载框。hadoop上的这个任务耗时比较久
js代码执行之前,浏览器首先会默认的把所有带var和function的进行提前的声明或者定义:1.理解声明和定义、2.对于带var和function关键字的在预解释的时候操作不一样的、3.预解释只发生在当前的作用域下
脚本执行js引擎都做了什么呢?1.语法分析 2.预编译 3.解释执行。在执行代码前,还有两个步骤;语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误 ,查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined
以前对认证这方面的认识一直不太深刻,不清楚为什么需要token这种认证,为什么不简单使用session存储用户登录信息等。最近读了几篇大牛的博客才对认证机制方面有了进一步了解。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!