浅谈js运行机制(线程)

2018-01-18 11:26:53

浅谈js运行机制(线程)

1.前言

  • 从开始接触js时,我们便知道js是单线程的。单线程,异步,同步,互调,阻塞等。在实际写js的时候,我们都会用到ajax,不管是原生的实现,还是借助jQuery等工具库实现,我们都知道,ajax可以实现局部刷新,并且在请求处理时,任然可以响应用户的操作,比如点击事件。不是说js是单线程吗?这些都是怎么实现的?

  • 在阅读《深入理解Bootrap的源码》一书,在分析轮播组件(carousel.js)的源码时,作者对一句代码操作的注释引起了我的兴趣。

setTimeout(function(){
  that.$element.trigger('slid'); 
},0);//触发slid事件,这里使用setTimeout是为了确保UI刷新线程不被阻塞。12345

后面我会一一解答这些疑惑。

2.浏览器线程

  • js运作在浏览器中,是单线程的,即js代码始终在一个线程上执行,这个线程称为js引擎线程。

  • 浏览器是多线程的,除了js引擎线程,它还有: 

    1. UI渲染线程

    2. 浏览器事件触发线程

    3. http请求线程

    4. EventLoop轮询的处理线程

    5. ……..

这些线程的作用:

  • UI线程用于渲染页面

  • js线程用于执行js任务

  • 浏览器事件触发线程用于控制交互,响应用户

  • http线程用于处理请求,ajax是委托给浏览器新开一个http线程

  • EventLoop处理线程用于轮询消息队列

浏览器中的js任务:

执行JavaScript代码 
对用户的输入(包含鼠标点击、键盘输入等等)做出反应 
处理异步的网络请求

3.js单线程

  • 单线程的含义是js只能在一个线程上运行,也就说,js同时只能执行一个js任务,其它的任务则会排队等待执行。

  • js是单线程的,并不代表js引擎线程只有一个。js引擎有多个线程,一个主线程,其它的后台配合主线程。

  • 多线程之间会共享运行资源,浏览器端的js会操作dom,多个线程必然会带来同步的问题,所有js核心选择了单线程来避免处理这个麻烦。js可以操作dom,影响渲染,所以js引擎线程和UI线程是互斥的。这也就解释了js执行时会阻塞页面的渲染。

4.消息队列(任务队列)

JavaScript运行时,除了一个运行线程,引擎还提供一个消息队列,里面是各种需要当前程序处理的消息。新的消息进入队列的时候,会自动排在队列的尾端。

  • 消息和回调函数相互联系;

  单线程意味着js任务需要排队,如果前一个任务出现大量的耗时操作,后面的任务得不到执行,任务的积累会导致页面的“假死”。这也是js编程一直在强调需要回避的“坑”。

5.js任务

任务分为2种:

  • 同步任务

  • 异步任务

它们的区别是: 本段中的线程指的是js引擎主线程

  • 同步任务:在主线程排队支持的任务,前一个任务执行完毕后,执行后一个任务,形成一个执行栈,线程执行时在内存形成的空间为栈,进程形成堆结构,这是内存的结构。执行栈可以实现函数的层层调用。注意不要理解成同步代码进入栈中,按栈的出栈顺序来执行。

  • 异步任务会被主线程挂起,不会进入主线程,而是进入消息队列,而且必须指定回调函数,只有消息队列通知主线程,并且执行栈为空时,该消息对应的任务才会进入执行栈获得执行的机会。

主线程执行的说明: 【js的运行机制】

(1)所有同步任务都在主线程上执行,形成一个执行栈。 
(2)主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。 
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 
(4)主线程不断重复上面的第三步。

执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。

6.事件和回调函数

  • 消息队列队列(或者叫任务队列)是一个事件的队列,IO响应时,会往队列中添加一个消息,此时说明相关的异步代码到了执行的时机,可以进入主线程的执行栈了。

  • 主线程读取消息队列,可以读取到对应的事件。

  • 消息队列可以响应IO事件,还有用户产生的事件(比如点击鼠标,页面滚动),只要指定了回调函数,就会进入消息队列,等待EventLoop轮询线程处理,是否可以进入主线程的执行栈。

  • 消息和回调函数相互联系的含义:主线程读到消息,就会执行相应的回调函数;进入消息队列的消息,必须对应相应的回调函数,否则这个消息会被丢弃不会进入消息队列。

  • 消息队列是一个先进先出的队列结构,这就决定了它的执行顺序,先产生的消息会被主线程先读取,会不会执行则会先检查一下执行时间,因为存在setTimeout等定时函数,这类事件产生的消息进入到消息队列,被执行的时机取决与它在队列中的位置和执行时间有关。【上文中使用setTimeout能够避免阻塞UI线程就是这个原因】。

7.EventLoop

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

简单说,浏览器的两个线程:一个负责程序本身的运行,称为”主线程”;另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为”Event Loop线程”(可以译为”消息线程”)。

由于js是运行在单线程上的,所有浏览器单独开启一个线程来处理事件消息的轮询,避免阻塞js的执行。

异步代码的执行逻辑: 
  每当遇到I/O的时候,主线程就让EventLoop线程去通知相应的I/O程序,然后接着往后运行,所以不存在等待时间。等到I/O程序完成操作,EventLoop线程把消息添加到消息队列,主线程就调用事先设定的回调函数,完成整个任务。

  • JavaIO中包括了网络IO,我们通常把http请求归类为网络IO.

  • js的ajax是new XMLHttpRequest()对象实现的,浏览器会新开一个线程来处理http请求,这就是ajax能够实现局部刷新的同时,还能响应用户交互的原因。

这也说明了在处理IO时,浏览器通常会新开启IO线程,这个属于我的推测,并没有查到对应的资料,因为IO涉及的广泛,这话也没错。

8.定时器

  前面也提到了定时器,定时器是会在进入消息队列,这也就和异步代码的执行逻辑一样了。它在”消息队列”的尾部添加一个消息,因此要等到同步任务和”消息队列”现有的任务都处理完,才会得到执行的机会,还要看定时器设置的时间是否到了才会执行。

9.关于异步代码的说明.

  从上文我们可以得知:使用定时器和事件可以实现异步,这是2种最为明显的实现异步的原理。对于异步的实现,阮一峰的博客说的非常清晰。

10.文尾的话

   这篇博文是站在了巨人的肩膀上,查询了很多博客,和书,特别感谢阮一峰大神的作品,让我对js的执行机制有了完整清晰的认识。


  • 2019-12-04 10:48:18

    vue 项目资源文件 static 和 assets 不说区别直接使用?

    assets中资源会webpack构建压缩到你代码中,而static文件直接引用。 static 中长存放类包、插件等第三方的文件,assets里放属资源文件比如自己资源图片、css文件、js文件。 引入资源的方式static文件夹可以使用~/static/方式引入, assets文件夹可以使用 ~@/assets 方式引入

  • 2019-12-05 17:01:36

    Vue 结合 Axios 接口超时统一处理

    当网路慢的时候。又或者公司服务器不在内地的时候,接口数据请求不回来超时报错的情况相信大家肯定遇到过的,这里我把我公司项目请求超时的处理方法分享下,希望看过后有帮助。

  • 2019-12-05 17:13:40

    JS模板工具lodash.template的简单用法

    lodash是从underscore分支的一个项目,之前我写了一篇JS模板工具underscore.template的简单用法,lodash跟underscore很相似,这也简单介绍一下lodash的template方法。 先把underscore的文章中用过的代码贴过来,把underscore的js文件换成lodash的js,其他一字不改,然后我们试试:

  • 2019-12-06 10:47:29

    date-fns日期工具的使用方法详解

    isToday() 判断传入日期是否为今天 isYesterday() 判断传入日期是否为昨天 isTomorrow() 判断传入日期是否为 format() 日期格式化 addDays() 获得当前日期之后的日期 addHours() 获得当前时间n小时之后的时间点 addMinutes() 获得当前时间n分钟之后的时间 addMonths() 获得当前月之后n个月的月份 subDays() 获得当前时间之前n天的时间 subHours() 获得当前时间之前n小时的时间 subMinutes() 获得当前时间之前n分钟的时间 subMonths() 获得当前时间之前n个月的时间 differenceInYears() 获得两个时间相差的年份 differenceInWeeks() 获得两个时间相差的周数 differenceInDays() 获得两个时间相差的天数 differenceInHours() 获得两个时间相差的小时数 differenceInMinutes() 获得两个时间相差的分钟数

  • 2019-12-06 10:49:39

    npm 查看源 换源

    npm,cnpm,查看源,切换源,npm config set registry https://registry.npmjs.org

  • 2019-12-06 11:01:31

    npm发布包流程详解 有demo

    npm发布包步骤,以及踩过的坑(见红颜色标准): 1.注册npm账号,并完成Email认证(否则最后一步提交会报Email错误) 2.npm添加用户或登陆:npm adduser 或 npm login

  • 2019-12-06 13:16:18

    vue mixins组件复用的几种方式

    最近在做项目的时候,研究了mixins,此功能有妙处。用的时候有这样一个场景,页面的风格不同,但是执行的方法,和需要的数据非常的相似。我们是否要写两种组件呢?还是保留一个并且然后另个一并兼容另一个呢? 不管以上那种方式都不是很合理,因为组件写成2个,不仅麻烦而且维护麻烦;第二种虽然做了兼容但是页面逻辑造成混乱,必然不清晰;有没有好的方法,有那就是用vue的混合插件mixins。混合在Vue是为了提出相似的数据和功能,使代码易懂,简单、清晰。

  • 2019-12-06 13:26:30

    vue的mixins混入合并规则

    混入minxins:分发vue组件中可复用功能的灵活方式。混入对象可以包含任意组件选项。组件使用混入对象时,所有混入对象的选项将混入该组件本身的选项。