线程和事件机制
JS线程和事件机制
进程和线程

进程:
- 程序的一次执行,它
占有一片独有的内存空间 - 可以通过windows任务管理器查看进程
线程:
- 是进程内的一个独立执行单元
- 是程序执行的一个完整流程
- 是CPU的最小的调度单元
进程和线程:
- 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程:主线程 –>进程启动后自动创建
- 一个进程中也可以同时运行多个线程:此时这个程序是多线程运行的
- 多个进程之间的数据是不能直接共享的 –>内存相互独立(隔离)
线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用
- JS是单线程运行的 , 但使用H5中的 Web Workers可以多线程运行
- 浏览器是多线程运行的
- 单进程和多进程的浏览器:
- 单进程
- firefox
- 旧ie
- 多进程
- chrome
- 新版ie
- 单进程
浏览器内核
支撑浏览器运行的最核心的程序
主流浏览器内核
- Chrome,Safari:[webkit]
- firefox:[Gecko]
- IE:[Trident]
- 360等国产双核浏览器:[Trident+webkit]
内核模块组成
- 主线程
- js引擎模块 : 负责js程序的编译与运行
- html,css文档解析模块 : 负责页面文本的解析(拆解)
- dom/css模块 : 负责dom/css在内存中的相关处理
- 布局和渲染模块 : 负责页面的布局和效果的绘制
- 分线程
- 定时器模块 : 负责定时器的管理
- 网络请求模块 : 负责服务器请求(常规/Ajax)
- 事件响应模块 : 负责事件的管理
定时器是定时执行的吗
1 | <body> |
- 定时器不能保证真正定时执行
- 一般会延迟一点(在可以接受的范围),也可能延迟很久(不能接受)
定时器回调函数是在分线程执行?
在主线程执行的, JS是单线程的
定时器是如何实现的?
事件循环模型
JS是单线程执行的
单线程执行的代码
setTimeout()的回调函数是在主线程执行的- 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
1 | // 如何证明JS执行是单线程的 |
流程结果:
- 先打印了[
fn()],然后马上就打印了[timeout() 00000] - 过了一秒后 打印 timeout 1111并弹窗,此处如果不将弹窗关闭,不会继续执行上方222
- 在将[timeout 1111]弹窗关闭后,
再等一秒执行此处
- 问:为何明明写的是2秒,却关闭上一个弹窗再过一秒就执行?
- 解:并不是关闭后再计算的,而是一起计算的,alert只是暂停了主线程执行
JS代码分类和执行的基本流程
代码分类:
- 初始化代码
- 回调代码(作为参数传递给另一个函数的函数代码。)
js引擎执行代码的基本流程
- 先执行初始化代码: 包含一些特别的代码 回调函数(异步执行)
- 设置定时器
- 绑定事件监听
- 发送ajax请求
- 后面在某个时刻才会执行回调代码
js单线程模式的原因
JavaScript 是一种单线程的语言,这意味着在任何给定的时间点,JavaScript 只能执行一个任务。这种设计选择主要是出于以下原因:
简单性:
- JavaScript 最初是为了在浏览器环境中运行而设计的。为了简化实现,JavaScript 采用了单线程模型,这样开发者就不需要担心多线程编程中的复杂问题,比如同步和死锁。
一致性:
- 单线程模型保证了脚本执行的一致性和可预测性。这使得开发者能够更容易地理解和调试他们的代码。
资源限制:
- 当JavaScript最初设计时,浏览器环境的资源相对有限。为了避免过多占用系统资源,单线程模型可以确保浏览器性能不受影响。
异步编程:
- 尽管JavaScript本身是单线程的,但它通过事件循环机制支持非阻塞I/O操作,这使得它能够高效地处理异步任务,如HTTP请求、DOM事件和定时器等。这使得JavaScript能够在不阻塞主线程的情况下处理后台任务。
DOM操作:
- 浏览器的DOM(文档对象模型)是共享资源,多个线程同时修改DOM可能会导致冲突。单线程模型可以避免这种情况发生。
尽管JavaScript是单线程的,但通过使用事件循环、回调函数、Promise、async/await等机制,它能够有效地处理异步操作。这些技术允许JavaScript在等待长时间运行的操作完成时继续执行其他任务,从而提高了整体的响应性和性能。
事件循环模型
概念
javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。
非阻塞:而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务(没有挂起直接执行相当于阻塞了主线程的执行),然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
单线程是必要的:也是javascript这门语言的基石,原因之一在其最初也是最主要的执行环境——浏览器中,需要进行各种各样的dom操作。 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。
当然,单线程在保证了执行顺序的同时也限制了javascript的效率,因此开发出了
web workers技术。这项技术号称可以让javaScript成为一门多线程语言。然而,使用web workers技术开的多线程有着诸多限制,例如:
所有新线程都受主线程的完全控制,不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。另外,这些子线程并没有执行I/O操作的权限,只能为主线程分担一些诸如计算等任务。所以严格来讲这些线程并没有完整的功能,也因此这项技术并非改变了javascript语言的单线程本质。可以预见,未来的javascript也会一直是一门单线程的语言。
提到javascript的另一个特点是“
非阻塞”,javascript引擎到底是如何实现的这一点呢?答案就是——event loop(事件循环)。
浏览器中js引擎事件循环机制
执行栈:
当我们调用一个方法的时候,js会生成一个与这个方法对应的执行环境(context),又叫
执行上下文。这个执行环境中存在着这个方法的私有作用域、上层作用域的指向、方法的参数,这个作用域中定义的变量以及这个作用域的this对象。 而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈(执行栈是管理这些执行上下文的一种机制。)。
事件队列(Task Queue):
JS引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务,当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为
事件队列。被放入事件队列不会立刻执行其回调,而是
等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

以上的事件循环过程是一个宏观的表述,实际上因为异步任务之间并不相同,因此他们的执行优先级也有区别。不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)
Web Workers
此处只做简略了解,详见Web Workers
- H5规范提供了js分线程的实现, 取名为: Web Workers
- 相关API
- Worker: 构造函数, 加载分线程执行的js文件
- Worker.prototype.onmessage: 用于接收另一个线程的回调函数
- Worker.prototype.postMessage: 向另一个线程发送消息
- 不足
- worker内代码不能操作DOM(更新UI)
- 不能跨域加载JS
- 不是每个浏览器都支持这个新特性
斐波那契数列
1 | <body> |
当我运行此行代码,传入计算数值为50左右(有的甚至更低),整个页面就会卡住好久的时间不能操作(计算结束后才会弹窗,但是未弹窗的这段时间用户并不能进行操作)
使用Web Workers
主线程:
1 | <body> |
分线程:
1 | //worker.js |

