JavaScript线程机制与事件机制

一、进程与线程

1.进程

进程是指程序的一次执行,它占有一片独有的内存空间,可以通过windows任务管理器查看进程(如下图)。同一个时间里,同一个计算机系统中允许两个或两个以上的进程处于并行状态,这是多进程。比如电脑同时运行微信,QQ,以及各种浏览器等。浏览器运行是有些是单进程,如firefox和老版IE,有些是多进程,如chrome和新版IE

2.线程

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
线程是指CPU的基本调度单位,是程序执行的一个完整流程,是进程内的一个独立执行单元。多线程是指在一个进程内, 同时有多个线程运行。浏览器运行是多线程。比如用浏览器一边下载,一边听歌,一边看视频。另外我们需要知道JavaScript语言的一大特点就是单线程,为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

3.进程与线程

  • 应用程序必须运行在某个进程的某个线程上
  • 一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建
  • 一个进程中如果同时运行多个线程, 那这个程序是多线程运行的
  • 一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
  • 多个进程之间的数据是不能直接共享的

4.单线程与多线程的优缺点?

单线程的优点:顺序编程简单易懂

单线程的缺点:效率低

多线程的优点:能有效提升CPU的利用率

多线程的缺点:

  • 创建多线程开销
  • 线程间切换开销
  • 死锁与状态同步问题

二、浏览器内核

浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分的,一是渲染引擎,另一个是JS引擎。现在JS引擎比较独立,内核更加倾向于说渲染引擎。

1.不同的浏览器可能不太一样

  • Chrome, Safari: webkit
  • firefox: Gecko
  • IE: Trident
  • 360,搜狗等国内浏览器: Trident + webkit

2.内核由很多模块组成

  • html,css文档解析模块 : 负责页面文本的解析
  • dom/css模块 : 负责dom/css在内存中的相关处理
  • 布局和渲染模块 : 负责页面的布局和效果的绘制
  • 定时器模块 : 负责定时器的管理
  • 网络请求模块 : 负责服务器请求(常规/Ajax)
  • 事件响应模块 : 负责事件的管理

三、定时器引发的思考

1. 定时器真是定时执行的吗?

我们先来看个例子,试问定时器会保证200ms后执行吗?

 document.getElementById(‘btn‘).onclick = function () {
      var start = Date.now()
      console.log(‘启动定时器前...‘)
      setTimeout(function () {
        console.log(‘定时器执行了‘, Date.now() - start)
      }, 200)
      console.log(‘启动定时器后...‘)
      // 做一个长时间的工作
      for (var i = 0; i < 1000000000; i++) {
      }
    }


事实上,经过了625ms后定时器才执行。定时器并不能保证真正定时执行,一般会延迟一丁点,也有可能延迟很长时间(比如上面的例子)

2.定时器回调函数是在分线程执行的吗?

定时器回调函数在主线程执行的, 具体实现方式下文会介绍。

四、浏览器的事件循环(轮询)模型

1. 为什么JavaScript是单线程

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

2.Event Loop

JavaScript中所有任务可以分成两种,一种是同步任务,另一种是异步任务(如各种浏览器事件、定时器和Ajax等)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)


下面这个例子很好阐释事件循环:

    setTimeout(function () {
      console.log(‘timeout 2222‘)
      alert(‘22222222‘)
    }, 2000)
    setTimeout(function () {
      console.log(‘timeout 1111‘)
      alert(‘1111111‘)
    }, 1000)
    setTimeout(function () {
      console.log(‘timeout() 00000‘)
    }, 0)//当指定的值小于 4 毫秒,则增加到 4ms(4ms 是 HTML5 标准指定的,对于 2010 年及之前的浏览器则是 10ms)
    function fn() {
      console.log(‘fn()‘)
    }
    fn()
    console.log(‘alert()之前‘)
    alert(‘------‘) //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
    console.log(‘alert()之后‘)

有两点我们需要注意下:

  • 定时器零延迟(setTimeout(func, 0))并不是意味着回调函数立刻执行。至少4ms,才会执行回调函数。它取决于主线程当前是否空闲与“任务队列”里其前面正在等待的任务。
  • 只有在到达指定时间时,定时器就会将相应回调函数插入“任务队列”尾部

总结:异步任务(各种浏览器事件、定时器和Ajax等)都是先添加到“任务队列”(定时器则到达其指定参数时)。当 Stack 栈(JavaScript 主线程)为空时,就会读取 Queue 队列(任务队列)的第一个任务(队首),最后执行

五、H5 Web Workers(多线程)

1. Web Workers的作用

正如上面所提到,JavaScript是单线程。当一个页面加载一个复杂运算的 js 文件时,用户界面可能会短暂地“冻结”,不能再做其他操作。比如下面这个例子:

<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
  // 1 1 2 3 5 8    f(n) = f(n-1) + f(n-2)
  function fibonacci(n) {
    return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
  }
  var input = document.getElementById(‘number‘)
  document.getElementById(‘btn‘).onclick = function () {
    var number = input.value
    var result = fibonacci(number)
    alert(result)
  }
</script>


很显然遇到这种页面堵塞情况,很影响用户体验的,有没有啥办法可以改进这种情形?----Web Worker就应运而生了!

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。其原理图如下:

2. Web Workers的基本使用

主线程

  • 首先主线程采用new命令,调用Worker()构造函数,新建一个 Worker 线程
var worker = new Worker(‘work.js‘);
  • 然后主线程调用worker.postMessage()方法,向 Worker 发消息。
  • 接着,主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息。
  var input = document.getElementById(‘number‘)
  document.getElementById(‘btn‘).onclick = function () {
    var number = input.value
    //创建一个Worker对象
    var worker = new Worker(‘worker.js‘)
    // 绑定接收消息的监听
    worker.onmessage = function (event) {
      console.log(‘主线程接收分线程返回的数据: ‘+event.data)
      alert(event.data)
    }
    // 向分线程发送消息
    worker.postMessage(number)
    console.log(‘主线程向分线程发送数据: ‘+number)
  }
    console.log(this) // window

Worker 线程

  • Worker 线程内部需要有一个监听函数,监听message事件。
  • 通过 postMessage(data) 方法来向主线程发送数据。
//worker.js文件
function fibonacci(n) {
  return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
}
console.log(this)//[object DedicatedWorkerGlobalScope]
this.onmessage = function (event) {
  var number = event.data
  console.log(‘分线程接收到主线程发送的数据: ‘+number)
  //计算
  var result = fibonacci(number)
  postMessage(result)
  console.log(‘分线程向主线程返回数据: ‘+result)
  // alert(result)  alert是window的方法, 在分线程不能调用
  // 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
}

这样当分线程在计算时,用户界面还可以操作,而且更早拿到计算后数据,响应速度更快了。

3. Web Workers的缺点

  • 不能跨域加载JS
  • worker内代码不能访问DOM(更新UI)
  • 不是每个浏览器都支持这个新特性(本文例子只能在Firefox浏览器上运行,chrome不支持)

如果需要源代码,请猛戳Web Workers

如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!

参考文章

进程和线程

进程与线程的一个简单解释

关于JavaScript单线程的一些事

JavaScript 运行机制详解:再谈Event Loop

Web Worker 是什么鬼?

Web Worker 使用教程

原文地址:https://www.cnblogs.com/qianduanwriter/p/11829588.html

时间: 2024-10-10 20:08:31

JavaScript线程机制与事件机制的相关文章

锁机制,信号机制及事件机制

在多进程运行处理数据时,会出现争夺资源到时数据混乱的现象,为了避免数据混乱,这里就引入了锁机制: 锁机制:引入Lock模块,l = Lock()在子进程中,l.acquire()表示取到钥匙,锁上门进行数据处理;l.release()表示归还钥匙打开门,让下一个进程进行操作. 信号机制:能够将一把锁配置多把钥匙,能够让多个进程同时进行操作.导入Semaphore模块,sem = Semaphore(n)中的n是int型,表示可以同时进行操作的子程序数. 事件机制:导入Event模块,e.is_s

聊一聊Android的事件机制

侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和事件机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,事件机制还是比较简单的.从大的方面讲,不光是Android平台,各种平台的消息机制的原理基本上都是相近的,其中用到的主要概念大概有: 1)消息发送者: 2)消息队列: 3)消息处理循环. 示意图如下: 图中表达的基本意思是,消息发送者通过某种方式,将消息发送到某个消息队列里,同时还有一个消息处理循环,不断从消息队列里摘取消息,并进一步解析处理.

剖析Qt的事件机制原理

版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 QT源码解析(二)深入剖析QT元对象系统和信号槽机制 QT源码解析(三)深入剖析QT元对象系统和信号槽机制(续) QT源码解析(四)剖析Qt的事件机制原理 QT源码解析(五)QLibrary跨平台调用动态库的实现 QT源码解析(六)Qt信号槽机制与事件机制的联系 QT源码解析(七)Qt创建窗

JavaScript线程机制

浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JS引擎线程(用于处理JS).GUI渲染线程(用于页面渲染).浏览器事件触发线程(用于控制交互). 除此之外,有一些执行完就终止的线程,如Http请求线程. 1.        javascript引擎线程是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序. 2.        GUI渲染线程负责渲染浏览器界面,当界面需要重

Javascript事件机制兼容性解决方案

原文:Javascript事件机制兼容性解决方案 本文的解决方案可以用于Javascript native对象和宿主对象(dom元素),通过以下的方式来绑定和触发事件: 或者 var input = document.getElementsByTagName('input')[0]; var form = document.getElementsByTagName('form')[0]; Evt.on(input, 'click', function(evt){ console.log('inp

记一次途家面试中问到的JavaScript事件机制:Event Loop

前几天去途家面试,问到了事件机制,以及异步队列的问题.很遗憾,当时答错了.回来之后查了下资料,看到阮一峰老师博客的分析,感觉讲的非常浅显易懂,就分享过来了. 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,

JavaScript事件机制

<script type="text/javascript" src="http://runjs.cn/gist/2zmltkfa/all"></script> [前端培养-作业01]javascript事件机制 1.javascript事件模型 2.e.target与e.currentTarget是干什么的? 3.preventDefault与stopPropagation是干什么的 4.什么是dispatchEvent? 5.说一说事件代

javascript的事件机制

一.事件模型 IE 和 标准DOM的事件模型 IE系:冒泡方式 NETSCAPE系:捕获方式 标准DOM:先捕获再冒泡 冒泡,从触发点向外层.顶层扩散,最后到达document.window,遇到相同注册事件立即触发执行: 捕获则相反,从window.document向里收缩,一直到触发点,遇到相同注册事件立即触发执行: 有代码如下: <style type="text/css"> div { border: solid 1px red; } #s1 { padding:

JavaScript 详说事件机制之冒泡、捕获、传播、委托

DOM事件流(event  flow )存在三个阶段:事件捕获阶段.处于目标阶段.事件冒泡阶段. 事件捕获(dubbed  bubbling):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件. 事件冒泡(event  capturing):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点. 无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事