• 浏览器


    浏览器输入url,浏览器主进程接管,开一个下载线程,然后进行 http请求;

    执行 dns 查询,建立 TLS 连接,向目标服务器发送请求,接收返回数据;

    (Https连接过程需要在正式收发数据前建立TLS连接,确保安全性。TLS建立在TCP之上,建立TLS连接前需要TCP4次握手。然后进行TLS连接。在连接中要完成秘钥交换,交换算法不同,握手过程细节也会不同。)

    响应内容可能为多种格式,这里记录为 html 格式的情况;

    响应数据经过一系列检查、判断后,最终会传给呈现引擎,这时候导航结束,页面加载开始;

    呈现引擎开始工作:(chrome webkit):

    1. Browser进程下载html文件并将文件发送给renderer进程
    2. renderer进程的GUI进程开始解析html文件来构建出DOM
    3. 当遇到外源css时,Browser进程下载该css文件并发送回来,GUI线程再解析该文件,在这同时,html的解析也同时进行,但不会渲染(还未形成渲染树)
    4. 当遇到内部css时,html的解析和css的解析同时进行
    5. 继续解析html文件,当遇到外源js时,Browser进程下载该js文件并发送回来,此时,js引擎线程解析并执行js,因为GUI线程和js引擎线程互斥,所以GUI线程被挂起,停止继续解析html。直到js引擎线程空闲,GUI线程继续解析html。
    6. 遇到内部js也是同理
    7. 解析完html文件,形成了完整的DOM树,也解析完了css,形成了完整的CSSOM树,两者结合形成了render树
    8. 根据render树来进行布局,若在布局的过程中发生了元素尺寸、位置、隐藏的变化或增加、删除元素时,则进行回流,修改
    9. 根据render树进行绘制,若在布局的过程中元素的外观发生变换,则进行重绘
    10. 将布局、绘制得到的各个简单图层的位图发送给Browser进程,由它来合并简单图层为复合图层,从而显示到页面上
    11. 以上步骤就是html文件解析全过程,完成之后,如若当页面有元素的尺寸、大小、隐藏有变化时,重新布局计算回流,并修改页面中所有受影响的部分,如若当页面有元素的外观发生变化时,重绘

    #、回流重绘

    怎么去理解这两个概念呢?从字面上理解,重绘,重新绘画,重新上色,较难产生联想的是回流。

    我们都知道,一个页面从加载到完成,首先是构建DOM树,然后根据DOM节点的几何属性形成render树(渲染树),当渲染树构建完成,页面就根据DOM树开始布局了,渲染树也根据设置的样式对应的渲染这些节点。

    在这个过程中,回流与DOM树,渲染树有关,重绘与渲染树有关,怎么去理解呢?

    比如我们增删DOM节点,修改一个元素的宽高,页面布局发生变化,DOM树结构发生变化,那么肯定要重新构建DOM树,而DOM树与渲染树是紧密相连的,DOM树构建完,渲染树也会随之对页面进行再次渲染,这个过程就叫回流

    当你给一个元素更换颜色,这样的行为是不会影响页面布局的,DOM树不会变化,但颜色变了,渲染树得重新渲染页面,这就是重汇

    你应该能感觉到,回流的代价要远大于重绘。且回流必然会造成重绘,但重绘不一定会造成回流。

    题外话

    1.由于display为none的元素在页面不需要渲染,渲染树构建不会包括这些节点;但visibility为hidden的元素会在渲染树中。因为display为none会脱离文档流,visibility为hidden虽然看不到,但类似与透明度为0,其实还在文档流中,还是有渲染的过程。

    2.尽量避免使用表格布局,当我们不为表格td添加固定宽度时,一列的td的宽度会以最宽td的宽作为渲染标准,假设前几行td在渲染时都渲染好了,结果下面某行的一个td特别宽,table为了统一宽,前几行的td会回流重新计算宽度,这是个很耗时的事情。

    #、减少回流

    1.DOM的增删行为

    比如你要删除某个节点,给某个父元素增加子元素,这类操作都会引起回流。如果要加多个子元素,最好使用documentfragment。

    2.几何属性的变化

    比如元素宽高变了,border变了,字体大小变了,这种直接会引起页面布局变化的操作也会引起回流。如果你要改变多个属性,最好将这些属性定义在一个class中,直接修改class名,这样只用引起一次回流。

    3.元素位置的变化

    修改一个元素的左右margin,padding之类的操作,所以在做元素位移的动画,不要更改margin之类的属性,使用定位脱离文档流后改变位置会更好。

    4.获取元素的偏移量属性

    例如获取一个元素的scrollTop、scrollLeft、scrollWidth、offsetTop、offsetLeft、offsetWidth、offsetHeight之类的属性,浏览器为了保证值的正确也会回流取得最新的值,所以如果你要多次操作,最取完做个缓存。

    5.页面初次渲染

    这样的回流无法避免

    6.浏览器窗口尺寸改变

    resize事件发生也会引起回流。

    #、线程和进程

    进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)

    线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

    #、浏览器 进程

    1. GUI渲染线程

      • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
      • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
      • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
    2. JS引擎线程

      • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
      • JS引擎线程负责解析Javascript脚本,运行代码。
      • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
      • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
    3. 事件触发线程

      • 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
      • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
      • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
      • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

    4. 定时触发器线程

      • 传说中的setIntervalsetTimeout所在线程
      • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
      • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
      • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
    5. 异步http请求线程

      • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
      • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

    #、load事件与DOMContentLoaded事件的先后

    渲染完毕后会触发load事件

    很简单,知道它们的定义就可以了:

    • 当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片。

    (譬如如果有async加载的脚本就不一定完成)

    • 当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了。

    (渲染完毕了)

    所以,顺序是:DOMContentLoaded -> load

    #、css加载是否会阻塞dom树渲染?

    css是由单独的下载线程异步下载的。

    css加载不会阻塞DOM树解析(异步加载时DOM照常构建)

    但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)

    #、普通图层和复合图层

    浏览器渲染的图层一般包含两大类:普通图层 以及   复合图层

    首先,普通文档流内可以理解为一个复合图层(这里称为默认复合层,里面不管添加多少元素,其实都是在同一个复合图层中)

    其次,absolute布局(fixed也一样),虽然可以脱离普通文档流,但它仍然属于默认复合层

    然后,可以通过硬件加速的方式,声明一个新的复合图层,它会单独分配资源
    (当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)

    将该元素变成一个复合图层,就是传说中的硬件加速技术

    • 最常用的方式:translate3dtranslateZ
    • opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
    • will-chang属性(这个比较偏僻),一般配合opacity与translate使用(而且经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层),
    • <video><iframe><canvas><webgl>等元素

    #、复合图层的作用

    一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能

    但是尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡

    使用硬件加速时,尽可能的使用index,防止浏览器默认给后续的元素创建复合层渲染

    具体的原理时这样的:
    **webkit CSS3中,如果这个元素添加了硬件加速,并且index层级比较低,
    那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),
    会默认变为复合层渲染,如果处理不当会极大的影响性能**

    简单点理解,其实可以认为是一个隐式合成的概念:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意

     #、JS的运行机制

    • JS分为同步任务和异步任务
    • 同步任务都在主线程上执行,形成一个执行栈
    • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
    • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

    所以:为什么有时候setTimeout推入的事件不能准时执行?因为可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码,
    所以自然有误差。

    • 主线程运行时会产生执行栈,

    栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕)

    • 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
    • 如此循环
    • 注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件

    #、定时器

    是由定时器线程控制

    因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。

    当使用setTimeoutsetInterval时,它需要定时器线程计时,计时完成后就会将特定的事件推入事件队列中。

    • 执行结果是:先beginhello!
    • 虽然代码的本意是0毫秒后就推入事件队列,但是W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
    • 就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(因为只有可执行栈内空了后才会主动读取事件队列)

    #、setTimeout 优于 setInterval:

    setInterval则是每次都精确的隔一段时间推入一个事件
    (但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)

    就会导致定时器代码连续运行好几次,而之间没有间隔。
    就算正常间隔执行,多个setInterval的代码执行时间可能会比预期小(因为代码执行需要一定时间)

    而且把浏览器最小化显示等操作时,setInterval并不是不执行程序,它会把setInterval的回调函数放在队列中,等浏览器窗口再次打开时,一瞬间全部执行

    鉴于这么多但问题,一般认为的最佳方案是:用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame

    #、requestAnimationFrame

    window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

    1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。

    2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

    停止:cancelAnimationFrame()接收一个参数 requestAnimationFrame默认返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

    #、定时器执行顺序,es6 会遇到有一些问题:

    执行结果为:

    因为Promise里有了一个一个新的概念:microtask

    JS中分为两种任务类型:macrotaskmicrotask,在ECMAScript中,microtask称为jobs,macrotask可称为task

    #、macrotask 和  microtask

    • macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

      • 每一个task会从头到尾将这个任务执行完毕,不会执行其它
      • 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染

    • microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务

      • 也就是说,在当前task任务后,下一个task之前,在渲染之前
      • 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
      • 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

    形成  macrotask  和  microtask  的场景

    • macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)
    • microtask:Promise,process.nextTick等

    线程:

    • macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
    • microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护

    注意,有一些浏览器执行结果不一样(因为它们可能把microtask当成macrotask来执行了),

    #、 js 运行机制总结

    • 执行一个宏任务(栈中没有就从事件队列中获取)
    • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
    • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
    • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
    • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

    #、负载均衡

    对于大型的项目,由于并发访问量很大,所以往往一台服务器是吃不消的,所以一般会有若干台服务器组成一个集群,然后配合反向代理实现负载均衡

    简单的说:

    用户发起的请求都指向调度服务器(反向代理服务器,譬如安装了nginx控制负载均衡),然后调度服务器根据实际的调度算法,分配不同的请求给对应集群中的服务器执行,然后调度器等待实际服务器的HTTP响应,并将它反馈给用户

    #、数据到达后端后的处理

    一般后台都是部署到容器中的,所以一般为:

    • 先是容器接受到请求(如tomcat容器)
    • 然后对应容器中的后台程序接收到请求(如java程序)
    • 然后就是后台会有自己的统一处理,处理完后响应响应结果

    概括下:

    • 一般有的后端是有统一的验证的,如安全拦截,跨域验证
    • 如果这一步不符合规则,就直接返回了相应的http报文(如拒绝请求等)
    • 然后当验证通过后,才会进入实际的后台代码,此时是程序接收到请求,然后执行(譬如查询数据库,大量计算等等)
    • 等程序执行完毕后,就会返回一个http响应包(一般这一步也会经过多层封装)
    • 然后就是将这个包从后端发送到前端,完成交互

    TLS 连接:https://www.jianshu.com/p/82efa80dc2f4

    浏览器工作原理:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

    浏览器工作原理:https://zhuanlan.zhihu.com/p/47407398

    回流重绘:https://www.cnblogs.com/echolun/p/10105223.html

    https://www.cnblogs.com/caiyy/p/10406934.html

    js单线程:https://www.cnblogs.com/wuqiuxue/p/8342121.html

    浏览器进程:https://segmentfault.com/a/1190000012925872#articleHeader20

    WebKit浏览器内核源码解析:https://www.cnblogs.com/xinxihua/p/14352940.html

    requestAnimationFrame:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

  • 相关阅读:
    隐藏NGINX服务器名称 和版本号
    salt-grains
    格式化输出文本的方法
    递归例子
    yield 生成器例子
    Python基础之函数
    Python基础之面向对象
    Python基础之模块2
    Python基础之字符编码
    Python基础之文件操作
  • 原文地址:https://www.cnblogs.com/guofan/p/16034197.html
Copyright © 2020-2023  润新知