单线程javascript
进程是操做系统分配资源和调度任务的基本单位,线程是创建在进程上的一次程序运行单位,一个进程上能够有多个线程。java
一个进程中只有一个线程,程序顺序执行,前面的执行完成后才会执行后面的程序。node
有点儿像点菜同样, 顾客来了。服务员接单,服务员接完单后立刻告诉厨房去作菜吧,此时服务员不会等待菜作好而是立刻会被释放出来继续服务下一个客户。 一直都是一个服务员在工做,等菜作好了,服务员在回去取菜给客户吃。ajax
单线程特色是节约内存,而且不须要再切换执行上下文,并且单线程不须要考虑锁的问题。数据库
以下图api
异步非阻塞promise
同步和异步关注的是消息通知机制,指代的是被调用方浏览器
同步就是发出调用后,没有获得结果前,该调用不返回,一旦调用返回,就获得返回值bash
当一个异步过程调用发出后,调用者不会马上获得结果,而是调用发出后,被调用者经过状态、通知或者回调函数处理这个调用。服务器
阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)的状态,针对的是调用者
阻塞调用是指调用结果返回以前,当前线程会被挂起。调用线程只有在获得结果后才会返回
非阻塞调用指在不能马上获得结果前,该调用不会阻塞当前线程
a> 同步阻塞
调用者:我喜欢你
被调用者:我也喜欢你
b> 异步阻塞
调用者: 我喜欢你
被调用者:我和我妈商量下。回头回复你
调用者: 不挂断电话,我等你回复
c> 异步非阻塞
调用者: 我喜欢你
被调用者: 我和我妈商量下。回头回复你
调用者: 挂断电话,不等你了。联系了另一个妹子
d> 同步非阻塞
调用者:给 A 打电话 我喜欢你
被调用者(A):正准备回复
调用者:不挂断电话 A 转身又给 B 打电话 我喜欢你
复制代码
I/O 操做
访问服务器的静态资源
读取数据,读取文件
node 在处理高并发, I/O密集场景有明显优点。高并发是指在同一时间并发访问服务器,I/O密集知道是文件操做、网络操做、数据库。相对的有 CPU 密集,CPU 密集值的是逻辑处理运算、压缩、解压、加密、解密等。 但是菜作好了,如何告诉服务员呢? ==回调==
事件驱动
渲染引擎内部是多线程的,包括 UI 线程和 JS 线程。注意 UI 线程和 JS 线程是互斥的,由于 JS 运行结果会影响到 UI 线程的结果。 UI 更新会被保存在任务队列中等 JS 线程空闲时候当即被执行。
JS 在最初为何被设计成了单线程,而不是多线程呢?若是多个线程同时操做 DOM 那岂不是会很混乱?
其余线程
浏览器中的 Event Loop
来个经典的图
栈内存 / 堆内存
javascript 中的变量分为基本类型和引用类型。基本类型是保存在栈内存中,引用类型则指的是保存在堆内存中的对象
任务队列
宏观任务(MacroTask):
setTimeout
setInterval
setImmediate(只兼容IE)
MessageChannel
requestAnimationFrame
I/O
UI rendering
复制代码
微观任务(MicroTask):
process.nextTick
Promise
Object.observe(已废弃)
MutationObserver
复制代码
好比: 函数的执行栈,做用域的释放顺序。
放进去的顺序: 全局做用域 <= one <= two <= three
函数 three 没有执行完,函数 one 是不会被释放的。函数销毁的顺序则是 three => two => one => 全局
function one () {
let a = 1;
two();
function two() {
console.log(a);
let b = 2;
function three () {
debugger;
console.log(b);
}
three();
}
}
one();
复制代码
断点调试下以下:进入的顺序和出去的顺序是相反的。
例子-1:
// 栈中的代码执行完毕后,会调用队列中的代码,此过程不挺的循环
// 当 1000 毫秒到达的时候 setTimeout 才会被放到任务队列里去
console.log(1);
setTimeout(() => {
console.log(2);
}, 1000)
setTimeout(() => {
console.log(3);
}, 500)
// 1 3 2
复制代码
例子-2:
console.log(1);
setTimeout(() => {
console.log(2);
}, 1000)
while(true) {}
setTimeout(() => {
console.log(3);
}, 500)
复制代码
此时不会输出 2 和 3。是由于 while 是个死循环。当时间到达时,要看栈中是否已经执行完了,若是没有执行完,就不会调用队列中的内容
例子-3:
console.log('global')
for (var i = 1;i <= 5;i ++) {
setTimeout(function() {
console.log('setTimeout1:', i)
},i*1000)
console.log(i)
}
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('then1')
})
setTimeout(function () {
console.log('timeout2')
new Promise(function (resolve) {
console.log('timeout2_promise')
resolve()
}).then(function () {
console.log('timeout2_then')
})
}, 1000)
// 输出结果
// global
// 1
// 2
// 3
// 4
// 5
// promise1
// then1
// setTimeout1: 6
// timeout2
// timeout2_pormise
// timeout2_then
// setTimeout1: 6
// setTimeout1: 6
// setTimeout1: 6
// setTimeout1: 6
// setTimeout1: 6
复制代码
先执行主线程中的任务输出 global 和 for 循环中的 i。setTimeout 属于宏观任务,时间到了会放到宏任务队列中,setTimeout1 会根据 i * 1000 依次放入到宏任务队列中。Promise 构造函数中的执行器属于同步任务,会先输出 promise1, 调用 resolve 后改变了 Promise 状态,调用 then 方法会将任务放入微任务队列中。此时 setTimeout2 时间到会被放入宏任务队列中。timeout2_promise 也会根据promise1的执行过程进入到微任务队列。
每次 Event Loop 触发执行的过程是:
A> 执行主线程中的任务,调用栈为空
B> 取出==全部== micro-task 任务队列 => 执行
C> 取出==一个== macro-task 任务 => 执行
D> 取出==全部== micro-task 任务队列 => 执行
E> 重复 C 和 D