单线程和事件轮询机制
进程和线程
js
进程:车间
程序的一次执行, 它占有一片独有的内存空间
线程:工人
CPU的基本调度单位, 是程序执行的一个完整流程
进程和线程:
* 一个进程中一般至少有一个运行的线程: 主线程。
* 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的。
* 一个进程内的数据可以供其中的多个线程直接共享。
* 多个进程之间的数据是不能直接共享的。
进程之间的通信:socket文件
进程是计算机内最小的单位
JS 单线程运行
js
1. 如何证明JavaScript是单线程执行?
设置了定时器,定时器的回调函数会等到主线程空闲且时间到执行;
如果主线程没有空闲下来,即使定时器的时间到了,回调函数也不会执行(等到主线程空闲)。
2. 为什么JavaScript选择单线程?
多线程会有线程调度以及线程开启关闭的开销
JavaScript主要在浏览器端操作DOM完成特效,如果不是单线程,不好解决页面渲染的同步问题。
同步任务和异步任务
js
同步任务:
按照顺序,一步一步地执行,执行完上一个任务再执行下一个任务
异步任务:
需要满足条件且主线程空闲才可以执行,在等待异步任务满足条件的过程中,同步任务继续执行,异步任务会在同步任务完成后执行
异步任务都是回调函数的形式, 回调函数不一定都是异步任务
JS中的异步任务有哪些:
1. 定时器的回调函数
2. DOM事件的回调函数
3. Ajax的回调函数 前后端交互
4. Promise的回调涵数
....
--------------------------------
// 创建定时器,同步任务
// 定时器的回调函数是异步任务,需要满足条件且主线程空闲才会执行
setTimeout(function() {
console.log('定时器执行');
});
// 同步任务
console.log(100);
console.log(200);
事件轮询机制
事件轮询(Event Loop)是 JS 实现异步的具体解决方案,同步代码直接执行,异步函数或代码块先放在异步队列中,待同步函数执行完毕,轮询执行异步队列的函数。
js
1、执行栈(调用栈)
主线程里就是一个执行栈,所有的任务(执行上下文对象)都要放入执行栈执行
2、异步任务管理模块
判断异步任务是否满足了执行条件,分为:
定时器管理模块
DOM事件管理模块
Ajax管理模块
...
如果满足了异步任务管理模块,会将异步任务放入回调队列,等待执行
3. 回调队列
队列是一种数据存储结构,特点是先进先出,后进后出
回调队列存放等待执行的异步任务
4. 事件轮询模块
时刻监听主线程(执行栈)是否空闲,一旦空闲,从回调队列中取出异步任务,放入主线程执行
js
执行栈(调用栈):要执行的代码进入执行栈
回调队列: 计时器过期或者事件触发亦或者接收到ajax响应,现在回调被推送到回调队列。但是回调不会立即执行,这就是事件轮询开始的地方。
管理模块: DOM事件管理、定时器管理、ajax请求管理
事件轮询: 事件轮询的工作是监听执行堆栈,并确定执行堆栈是否为空。如果执行堆栈是空的,它将检查回调队列,看看是否有任何挂起的回调等待执行。
js
JS主线程(执行栈)是车间主任
浏览器管理模块是 负责异步任务的管理,在计时和监听用户触发事件,当触发或计时器到点时,会把回调送到回调队列
事件轮询一直在询问主线程是否空闲,一旦空闲,就把执行队列的任务退给主线程工作。
一道经典题目:
js
//问题描述:请写出最终的输出值,并解释原因
var value1 = 0, value2 = 0, value3 = 0;
for (var i = 1; i <= 3; i++) {
var i2 = i;
(function () {
var i3 = i;
// 每次循环都开启一个定时器 但是定时器的回调函数在循环结束后才会执行!!!!!
setTimeout(function () {
value1 += i;
value2 += i2;
value3 += i3;
}, 1);
})();
}
// for 循环结束之后 全局变量i的值是4, 全局变量i2的值是3
setTimeout(function () {
console.log(value1); // 12
console.log(value2); // 9
console.log(value3); // 6
}, 100);
// i 是全局变量,循环结束了i的值为4,然后开始执行回调函数创建了3个回调函数,对应4+4+4
// i2 也是全局变量,但是循环结束时,i2的值为3,执行回调函数,创建了3个回调函数对应3+3+3
// i3 是局部变量,循环结束时,创建了三个回调函数,每个回调函数中的I3,是上层回调函数作用域的值分别是1+2+3
JS 实现多线程(了解)
js
Worker 构造函数
Worker.prototype.postMessage() 向分线程发送数据
Worker.prototype.onmessage 监听分线程的消息
创建子线程
new Worker('[路径]');
利用Worker可以实现多线程运算符
通过实例化一个Worker,创建一个子线程
子线程里面不允许操作DOM,也没有window
worker适合场景:
把耗时的计算放在分线程,不会影响主线程的其他工作
如果耗时的计算在主线程,导致页面卡顿(甚至崩溃)
worker的缺点:
① 无法操作DOM
② 无法跨域
③ 兼容性(不是所有的浏览器都可以使用)
Worker 构造函数
Worker.prototype.postMessage() 向分线程发送数据
Worker.prototype.onmessage 监听分线程的消息
js
// 获取相关元素
var input01 = document.querySelector('#input01');
var input02 = document.querySelector('#input02');
var btn = document.querySelector('#btn');
var resBox = document.querySelector('#res');
// 创建子线程 子线程的代码会执行
var worker = new Worker('./child.js');
// 点击计算按钮 向子线程发送数据
btn.onclick = function() {
// 定义向子线程发送的数据 ‘+’ String 自动转换为 Number
var nums = [+input01.value, +input02.value]
// 向子线发送数据
worker.postMessage(nums);
};
// 监听子线程发过来数据
worker.onmessage = function(event) {
resBox.innerHTML = event.data;
}
子线程
js
// 监听主线程发过来的数据
onmessage = function(event) {
//console.log('接收到主线程数据了:', event);
// 根据接收到的数据进行计算
var res = event.data[0] + event.data[1];
// 将数据发送到主线程
postMessage(res);
};