进程是系统资源分配一个独立单位,一个程序至少有一个进程。比方说:一个工厂表明一个 CPU, 一个车间就是一个进程,任一时刻,只能有一个进程在运行,其余进程处于非运行状态。javascript
线程是CPU调度和分派的基本单位,一个线程只能属于一个进程,一个进程能够有多个线程且至少有一个。比方说一个车间的工人,能够有多个工人一块儿工做。java
生活中经常能看到,某某电脑 CPU 的 4 核 4 线程,其意思是指,这款 CPU 同一时间最多只能运行 4 个线程,因此有些线程会处于工做状态,有的线程会处于中断,堵塞,睡眠状态。web
常常看到有不少任务同时在进行,一边工做,一边听歌,还一边下载电影。那是由于这些线程在以闪电般的速度不断的切换主要的几个线程,因此,人的体验上感受是不少不少任务在同时进行。promise
栈是一种数据结构,具备后进先出的特色,最开始进入栈结构的数据反而最后才能出来。 浏览器
队列也是一种数据结构,数据只能从一边进,一边出,先进去的天然就先出来。 bash
同步和异步关注的消息通讯机制,同步在函数调用时,若是调用者没有拿到响应结果,程序会继续等待,知道拿到结果为止。而异步会执行其后的代码,等到有响应结果后,才处理响应。网络
阻塞和非阻塞关注的是程序等待调用结果时的状态,阻塞的意思是,在调用结果返回响应前,线程会被挂起占用,程序没法继续往下走,而非阻塞的线程则不会挂起,后面的代码可以继续往下执行。数据结构
比方说:我去超市买包薯片,老板告诉我货架上没货了,立刻去库房拿,这过程当中,老板要我站着等他,直到他拿到货出来给我。这个过程就是阻塞。多线程
若是老板告诉我,能够先回去,他一会去库房拿,拿到了以后打电话给我。这个过程,就是非阻塞的,我不用等待,还能够干其余的事情。闭包
js 代码在执行代码时,JS 会给调用代码生成一个执行上下文对象,并将其压入执行上下文栈,首先进入栈底的是全局上下文,而后是函数的执行上下文(Execution Context),函数执行完以后,函数上下文从栈中弹出,直到退出浏览器,全局上下文才从栈底弹出。
用代码举个例子:
var globalName = "window";
var foo1 = function() {
console.log("foo1");
}
var foo2 = function() {
console.log("foo2");
foo1();
}
foo2();
复制代码
上面的图片大体可以描述执行上下文栈的实现逻辑,有关执行上下文的知识,你们能够翻看我以前的文章 - 《JavaScript 之执行上下文》
JavaScript 的一个很是有趣的特性是事件循环模型,与许多其余语言不一样,它永不阻塞。 处理 I/O 一般经过事件和回调来执行 -- MDN
浏览器主要任务是给用户是视觉和交互上的体验,若是页面使用过程当中,偶尔出现阻塞、挂起、无响应的体验必定是很是糟糕的。同时,若是采用多线程同步的模型,那么如何保证同一时间修改了 DOM, 究竟是哪一个线程先生效呢。
浏览器执行环境的核心思想在于任务调度方式的特别:
哪一个任务的优先级高,先来就先运行,直到执行完了才执行下一个,而且同一时刻只能执行一个代码片断,即所谓的单线程模型。
比方说,银行的柜台只开启了一个柜台,每一个人想要办理业务,就得先拿号排队,叫到了你的号码,你才能上去办理业务。不能多我的同时在一个柜台办理业务,否则就很容易出差错。
事件循环是 JS 处理各类事件的核心,因为多个线程同时操做 DOM, 形成不可控的问题,因此 JS 采用了单线程模型。另外,因为全部的事件同步执行,执行完一个才能执行下一个,会形成页面渲染的堵塞。JS 中存在异步事件,用户能够在点击页面的时候,请求网络响应的同事,还能够进行其余的点击操做,保证了页面不会由于网络请求,多种 IO 接口响应慢形成代码执行的堵塞和挂起。
事件循环的顺序是:
一个
事件出队,而后压入执行栈中执行。接下来咱们用代码来解释:
console.log("script start!");
function foo1() {
console.log("foo1");
}
foo1();
setTimeout(function () {
console.log("setTimeout!");
}, 1000);
function foo2() {
console.log("foo2");
}
foo2();
console.log("script end!");
打印:
// script start!
// foo1
// foo2
// script end!
// setTimeout!
复制代码
那咱们尝试把 setTimeout 的延迟时间改成 0,想要当即执行,看会不会当即执行:
console.log("script start!");
function foo1() {
console.log("foo1");
}
foo1();
setTimeout(function () {
console.log("setTimeout!");
}, 0);
function foo2() {
console.log("foo2");
}
foo2();
console.log("script end!");
打印:
// script start!
// foo1
// foo2
// script end!
// setTimeout!
复制代码
能够看出 setTimeout 属于异步事件,老是会在主线程的任务执行完后才开始执行。
顺便说一下事件循环几个原则:
- 一次只处理一个任务
- 一个任务从开始到完成,不会被其余任务所中断
这两个原则保证了浏览器任务单元的完整性,事件调用的有序性。
事件循环的实现原本应该由一个用于宏任务的队列和一个用于微任务的队列进行完成,这使得事件循环要根据任务类型来进行优先处理。
宏任务:
宏任务包括:
宏任务表明一个个离散、独立的工做单元,运行完任务后,浏览器能够进行其余的任务调度,如更新渲染或执行垃圾回收。宏任务须要屡次事件循环才能执行完。
微任务:
微任务包括:
微任务是更小的任务,微任务须要尽量地、经过异步方式执行,微任务更新浏览器的状态,但必须在浏览器执行其余任务以前执行。微任务使得咱们避免没必要要的 UI 重绘。微任务在一次事件循环中必须所有执行完。
宏任务和微任务的执行优先级原则是:
完成一个宏任务后,执行余下的微任务
同一次事件循环中,宏任务永远在微任务以前执行。
ok,知道了优先级原则后,咱们来看一段代码:
console.log(1);
setTimeout(function() {
console.log(2);
new Promise(resolve => {
console.log(3);
resolve(4);
console.log(5);
}).then(data => {
console.log(data);
});
}, 0);
new Promise(resolve => {
console.log(6);
resolve(7);
console.log(8);
}).then(data => {
console.log(data);
});
setTimeout(function() {
console.log(9);
}, 0);
console.log(10);
output:
第一次循环:
// 1
// 6
// 8
// 10
// 7
第二次循环:
// 2
// 3
// 5
// 4
第三次循环
// 9
复制代码
咱们一块儿来分析以上代码:
关于事件循环宏任务和微任务的执行过程:
- 首先两个类型的任务都是逐个执行
- 微任务会前下一个渲染或垃圾回收前所有执行完
- 一次事件循环中先只执行一个宏任务,在下一次事件循环前执行完全部的微任务,包括新建立的微任务。
尽管 HTML5 新标准加入了 web worker 的多线程技术,可是 web worker 只能用于计算,而且 JS 的多线程 worker 没法操做 DOM, 否则就没法控制页面是在被谁操做的了。
主线程传给子线程的数据是经过拷贝复制,一样子线程传给主线程的数据也是经过拷贝复制,而不是共享同一个内存空间。
以上说明,JS 不存在线程同步,因此仍是能够把 JS 看作单线程模型,把 web worker 当作 JS 的一种回调机制。
事件循环是 JS 和 Nodejs 事件调用机制的核心,保证了页面能够有序无阻塞的进行。
事件循环的主要逻辑是先执行调用栈,直到清空调用栈只剩下全局上下文。
而后 JS 检查宏任务队列,若是有任务则取出一个进行调用,进行页面渲染和垃圾回收。
同时将全部的微任务源派发的任务加入微任务事件队列,最后执行余下的全部微任务。微任务执行后完,进行页面渲染和垃圾回收后进行下一轮事件循环。
欢迎关注个人我的公众号“谢南波”,专一分享原创文章。