Skip to content
快速导航

浏览器

浏览器解析HTML过程

DOMContentLoaded

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载

load

当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件

case1:无JS,无CSS

case2:无JS,有CSS

在这个情况的时候,CSS不会阻碍DOM的解析,但是会阻碍DOM的渲染

case3:有JS,有CSS

  • JS脚本会阻碍DOM解析
  • CSS会阻碍JS脚本执行,因此也会间接阻碍DOM解析
  • CSS必定会阻碍DOM渲染

async defer 区别

DOMContentLoaded事件不一定就是DOM解析完成的事件

  • async是异步执行,此处的DOMcontentLoaded事件触发只关注DOM是否被解析完,与async无关。异步下载完毕后就马上执行,因此可能会阻塞DOM解析,浏览器并不能保证多个async脚本按顺序执行。总的来说执行时机一定在load事件之前,可能在DOMcontentLoaded事件之前或者之后
  • defer是延迟执行,此处的DOMcontentLoaded事件触发是要defer脚本执行完毕后才会触发,defer的执行时机是在DOMcontentLoaded事件之前,换个一个角度说就是defer会等待真正的DOM解析完之后执行,所以它不会阻碍DOM解析,并且多个defer脚本执行可以按顺序执行
  • 区别就在于DOMContentLoaded事件的触发时间点

共同点是都存储在客户端,且都是同源的

  • 生命周期不同:cookie一般由服务器生成,可以设置失效时间,如果是在自己浏览器生成的话,默认是关闭后会失效;sessionStorage关闭标签页或者浏览器就会失效;localStorage除非被清除,否则一直是有效的
  • 作用域不同:cookielocalStorage在所有的同源窗口都是共享的,而sessionStorage不能共享
  • 大小不同:cookie大小一般不超过4ksessionStorage localStorage可以到5M
  • 是否与服务端通信:cookie数据始终在同源的请求中携带,再浏览器和服务器之间来回传递;而sessionStorage localStorage不会自动把数据发给浏览器,仅在本地保存
  • 存储位置不同:cookie存储在客户端,session存储在服务端,因此session也会更安全一些
  • 存储大小不同:cookie大小一般不超过4k,而session要大得多,但是session太多太大的话会造成服务器的压力
  • 有效期不同:cookie一般有效期较长,session有效期一般较短

session token 区别

  • 服务端是有记录状态:session记录了客户端和服务器的会话状态,而token是令牌,是一种访问资源的一种凭证,它可以使得服务器无状态化,不需要存储会话信息,它用计算来代替了储存
  • token的安全性更好一些,因为每个token还有签名,可以防止监听,而session需要依靠链路层来保证安全性了

重排和重绘

解释

  • 重排和重绘其实是发生在浏览器渲染路径上的两个节点。浏览器最关键的渲染路径就是DOMCSSOM生成渲染树(render tree),然后渲染树render tree通过布局(layout)来确定页面所有的内容的大小与位置,确定布局后,就进行paint操作,也就是把像素绘制到屏幕上

  • 其中重排就是当元素的位置和大小发生变动的时候,浏览器就要重新执行布局这个步骤,来重新确定页面所有内容的大小和位置,然后再重新绘制到屏幕上,所以重排一定会导致重绘

  • 其中重绘就是当元素的位置和大小没有发生变动,而仅仅是样式发生变动的时候,浏览器就会跳过布局(layout)步骤而直接执行绘制(paint)这个步骤,所以重绘不一定会导致重排

如何减少重排和重绘?

  • 集中式改变样式,不要频繁的进行样式的改变
  • 分离读和写操作,读操作的时候可以用变量进行缓存
  • 尽量少使用dispaly:none,可以使用visibility:hidden代替,dispaly:none会造成重排和重绘,visibility:hidden只会造成重绘。
  • 不要使用Table布局,因为一个小小的操作,可能就会造成整个表格的重排或者重绘
  • 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流

白屏与首屏

白屏

白屏时间是指浏览器开始显示内容的时间。一般认为是解析完<head>的时刻为白屏时间

计算:

<head> 标签前的<script> 标签内加入代码:

javascript
console.log(new Date().getTime() - performance.timing.navigationStart)

首屏

首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间

计算:

<body>下方的<script> 标签加入代码:

javascript
console.log(new Date().getTime() - performance.timing.navigationStart)

优化

  • 减少HTTP请求次数,减少HTTP请求大小
  • 合并压缩文件(gzip)
  • 采用svg图片或者字体图标
  • 尽量将 CSS 放文件头部,JS 文件放在底部,也可以是用defer加载JS
  • 采用服务端渲染
  • CDN
  • 资源缓存
  • 图片懒加载

客户端渲染(CSR)与服务端渲染(SSR)

客户端渲染(Client-side rendering)

从服务端获取HTML文件,服务端不会对文件进行任何的处理,客户端需要自行下载和执行javascript,生成DOM然后再渲染

优点

前后端分离,减少服务端压力,局部刷新

缺点

不利于SEO,首屏渲染慢

服务端渲染(Server-side rendering)

可能实际上很多网站是处于经济效益的考虑,主要是SEO和首屏

服务端返回渲染好的HTML文件,客户端不需要做任何操作,直接呈现就可以

优点

利于SEO,首屏渲染速度快

缺点

服务端压力大,前后端不分离不容易维护,前端页面更改,后端也要更改

浏览器有哪些进程?

  • 浏览器进程(Browser Process),这个是浏览器的主进程,主要负责包括地址栏、前进后退按钮、文件存取等
  • 渲染进程(Renderer Process),主要负责将 HTML, CSS, JavaScript 转换为用户可交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 就运行在渲染进程
  • 网络进程(NetWork Process),主要负责页面的网络资源加载
  • 插件进程(Plugin Process),主要负责网站使用的所有插件,每个插件一个进程,单独隔离出是为了防止插件挂了影响用户
  • GPU(GPU Process),主要负责 UI 渲染

js在V8中的执行过程

总流程

JS TO AST

AST Parser参考网站,有Token也有AST

词法分析

所谓词法分析就是将JS代码中的每个字符串,也就是将每个词解析出特定的意义,每个Token主要由一对对的typevalue组成。例如var name = 'songnian'生成的Token如下:

javascript
[
    // type表示属性/类型  value表示值
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "name"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "Identifier",
        "value": "songnian"
    },
    {
        "type": "Punctuator",
        "value": ";"
    }
]
语法分析

AST:Abstract Syntax Tree 即抽象语法树

语法分析就是将生成好的Token转化成AST树,如果源码符合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误”。最著名的是bable,它可以将ES6原码转化成AST,然后再将这个ES6 AST转成ES5 AST,最后利用ES5 AST转成js执行代码。例如var name = 'songnian'生成的AST如下:

javascript
{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "name"
          },
          "init": {
            "type": "Identifier",
            "name": "songnian"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "script"
}

生成字节码

字节码由解释器Ignition生成,它是介于AST和机器码之间的一种代码,它所占用的空间比机器码小很多

缓存机器码

在以前的v8引擎中,都是直接将AST转化成机器码然后给CPU执行(因为cpu只认识机器码),但是这样会带来一个问题,就是时间问题,因为每次执行都需要重新编译一次。基于此,Google团队提出了缓存机器码的解决方案,其实就是空间换时间,浏览器运行的时候存放在内存,浏览器关闭后存放在磁盘,但是这样又有一个问题,浏览器运行的时候机器码占用内存很大,非常考验电脑的性能,要是换一个内存小一点的电脑可能就无法支撑了

惰性编译

为了解决上一个问题,Google团队又提出了惰性编译:V8 启动的时候只编译和缓存全局作用域的代码,而函数作用域中的代码,会在调用的时候去编译,同样函数内部编译后的代码一样不会被缓存下来。一开始还行,但是后来又发现了一个问题,对于以前的jQuery这一些,他们的插件都是通函数去封装的,那么如果一个插件过大就会引起编译的时间很慢,这个惰性编译也就相当于把路给封死了

引入字节码

从全流程上来看,虽然多了一个生成字节码的环节,但是占用空间却能大大减小

编译器

热代码

热代码就是被重复执行多次的代码,在V8中会有专门的监控模块,来监控同一代码是否多次被调用,如果被多次调用,那么就会被标记为热代码

优化编译器TurboFan

接着热代码继续说,当存在热代码的时候,V8 会借着TurboFan 将为热代码的字节码转为机器码并缓存下来,这样一来,当再次调用热代码时,就不在需要将字节码转机器码,当然热代码相对来说还是少部分的,所以缓存也并不会占用太大内存,并且提升了执行效率,同样此处也是牺牲空间换时间

反优化

当热代码在某次执行的时候,突然某一个属性被修改了,则此时优化编译器TurboFan会执行反优化,其实就是将热代码退回到AST那一步,这个时候解释器Ignition会重新解释执行被修改的代码

Released under the MIT License.