浏览器背后的运行机制

  1. 主流浏览器: IE/Edge,Chrome,Safari,Opera,Firefox
  2. 浏览器内核
    a. chrome chromium
    b. Safari webkit
    c. ie trident
    d. firfox gecko
    e. opera presto
  3. 组成:
    a. HTML 解释器 :将 HTML 词法解析输出 DOM 树
    b. css 解释器 : 将 css 词法解析输出样式规则
    c. 图层布局计算模块 : 布局计算没的对象的位置及大小
    d. 视图绘制模块 : 将具体节点渲染形成图像
    e. js 引擎 : 编译执行 js 代码
  4. 浏览器渲染过程
    解析 HTML 文档的同时,解析 css 内容,当两个部分都同时解析完成,会生成一个渲染树(Render Tree),然后图层布局计算模块会从根节点开始进行遍历,
    计算每个元素的大小和位置,得到渲染树的布局渲染树(Layout of the Render Tree),最后视图会将布局渲染树渲染到计算机上
  5. 优化层次
    1. css 层面优化
      css 解析器在读取我们的代码时,执行顺序是从右往左
      1. 避免使用通配符
      2. 关注可以通过继承实现的属性,避免重复匹配重复定义
      3. 少用标签选择器,如果可以使用类选择器提代
      4. 减少嵌套,后代的开销是极大的,因此我们可以把深度降低(最高不超过 3 层)
    2. css 和 js 加载顺序
      1. css 阻塞: CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。 将 CSS 放在 head 标签里,启用 CDN 实现静态资源加载速度的优化
      2. js 阻塞: JS 引擎是独立于渲染引擎存在的。我们的 JS 代码在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎。JS 引擎对内联的 JS 代码会直接执行,对外部 JS 文件还要先获取到脚本、再进行执行。等 JS 引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续 CSSOM 和 DOM 的构建。 因此与其说是 JS 把 CSS 和 HTML 阻塞了,不如说是 JS 引擎抢走了渲染引擎的控制权。js 存在三种加载方式:
        1. 正常模式 这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。
             <script src="index.js"></script>
        
        1. async 模式 JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。
             <script async src="index.js"></script>
        
        1. defer 模式 JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。
            <script defer  src="index.js"></script>
        
    3. 减少 DOM 操作
      1. 回流: 当我们对 DOM 的修改引发了几何尺寸的变化(宽,高,隐藏)时,浏览器会重新计算元素的位置和大小,这样其他元素的位置和大小也会受到相应的影响,等所有的计算完成,浏览器才会重新绘制,这个过程称为回流(重拍)
      2. 重绘: 当我们对 DOM 的样式修改未引发其几何变化,浏览器只会改变其样式
      3. 重绘不一定导致回流,回流一定引发重绘
      4. js 优化操作 DOM:由于 js 引擎和渲染引擎时独立的两个部分,因此如果 js 操作渲染的逻辑,这样两边通讯会带来消耗,举个例子:
          for(let i = 0 ;i < 10000; i++> ) {
              document.getElementById('container').innerHTML+='<span>我是一个小测试</span>'
          }
      
      在这里,每次调用 DOM 接口会带来一定的消耗,这时我们可以缓存一些不必要的消耗;而且我们是每次遍历取拼接,这样会修改长度和高度,导致了回流
          let containerDom = document.getElementById('container')
          let count  = 10000;
          let content = ''
          for(let i = 0 ;i < count; i++> ) {
              content+='<span>我是一个小测试</span>'
          }
          containerDom.innerHTML = content
      
      1. DocumentFragment :表示一个没有父级文件的最小文档对象, 用于存储已排好版的或尚未打理好格式的 XML 片段;不是真实 DOM 树的一部分,它的变化不会引起 DOM 树的重新渲染的操作(reflow),且不会导致性能等问题
          let container = document.getElementById('container')
          // 创建一个DOM Fragment对象作为容器
          let content = document.createDocumentFragment()
          for(let count=0;count<10000;count++){
              // span此时可以通过DOM API去创建
              let oSpan = document.createElement("span")
              oSpan.innerHTML = '我是一个小测试'
              // 像操作真实DOM一样操作DOM Fragment对象
              content.appendChild(oSpan)
          }
          // 内容处理好了,最后再触发真实DOM的更改
          container.appendChild(content)
      
    4. Event Loop
      1. 概念: 微任务(micro-task)和宏任务(macro-task)
      2. 微任务(micro-task): Promise, process.nextTick, MutationObserver
      3. 宏任务(macro-task): setTimeOut, setInterval, I/O 操作,script 等
      4. 过程:
        1. 初始状态:调用栈空,micro 队列为空,macro 队列存在一个 script 标签任务(整体 js 逻辑)
        2. 全局上下文被推入调用栈,同步代码执行,在此过程会出现一些 micro 或 macro,他们会被推进各自的任务队列中,当同步代码执行完成,script 脚本会被踢出 macro 队列
        3. 执行 micro 队列中的任务
          总结:因此需要异步操作 DOM 的操作,应该包装成 micro
    5. 异步更新
      1. Vue 的 $nextTick()
      let callbacks = []
      let pending = false
      export function nextTick (cb?: Function, ctx?: Object) {
          let _resolve
          callbacks.push(() => {
              if (cb) {
                  try {
                      cb.call(ctx)
                  } catch (e) {
                      handleError(e, ctx, 'nextTick')
                  }
              } else if (_resolve) {
                  _resolve(ctx)
              }
          })
          // 检查上一个异步任务队列(即名为callbacks的任务数组)是否派发和执行完毕了。pending此处相当于一个锁
          if (!pending) {
              // 若上一个异步任务队列已经执行完毕,则将pending设定为true(把锁锁上)
              pending = true
              // 是否要求一定要派发为macro任务
              if (useMacroTask) {
                  macroTimerFunc()
              } else {
                  // 如果不说明一定要macro 你们就全都是micro
                  microTimerFunc()
              }
          }
          // $flow-disable-line
          if (!cb && typeof Promise !== 'undefined') {
              return new Promise(resolve => {
                  _resolve = resolve
              })
          }
      }
      
    6. Lighthouse 查看我们网页的一些体验得分,包含:页面性能、PWA、可访问性、最佳实践、SEO
    7. Perfomance API
      1. 返回参数
原文地址:https://www.cnblogs.com/tutao1995/p/14268181.html