笔记之浏览器工作原理与实践(2)

2,028 阅读7分钟

本文是对李兵老师的《浏览器工作原理与实践》课程的记录与整理。

课程地址:浏览器工作原理与实践

《浏览器工作原理与实践》分为几篇笔记,接上文

笔记之浏览器工作原理与实践(1)

5、渲染流程

完整渲染流程
完整渲染流程

渲染流程总结

  1. 渲染进程将 HTML 内容转换为能够读懂的DOM 树结构。
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的styleSheets,计算出 DOM 节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树
  5. 为每个图层生成绘制列表,并将其提交到合成线程。
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令DrawQuad给浏览器进程。
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
  • 主线程

    1. 构建 DOM 树

      • 树结构
    2. 样式计算

      1. css 结构-stylessheets

      2. 转换属性值标准化

        标准化属性值
        标准化属性值
        • rgb
        • px
      3. 计算 DOM 树每个节点具体样式

        • CSS 继承和层叠规则
          • useragent 浏览器默认样式
          • 层叠继承
    3. 布局阶段(Layout Tree)

      • 创建布局树(所有可见节点,不可见的会被忽略)
      • 布局计算
    4. 分层 图层树(LayerTree)

      渲染引擎还需要为特定的节点升成专用的图片并生成图层树

      1. 拥有层叠 上下文属性元素会被单独升为一层

        1. 定位属性(position)
        2. 透明属性
        3. CSS 滤镜
        4. z-index
      2. 需要剪裁(clip)被创建为图层

        overflow:auto

    5. 图层绘制(Paint)

      绘制指令

  • 非主线程(合成线程)

    图层被划分
    图层被划分

    栅格化操作

    合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图

    GPU栅格化
    GPU栅格化
  • 重排. 重绘. 合成

    • 重排:更改了元素几何属性,需要更新整个渲染流水线,开销最大。

      重排
      重排
    • 重绘:更改元素绘制属性,省去了布局和分层阶段,执行效率高一点

      重绘
      重绘
    • 合成:渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。非主线程操作

      • transform
      合成
      合成

      二、 javascript 执行机制

6. JavaScript 变量提升

变量提升(Hoisting)

所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。

函数声明和赋值
函数声明和赋值
  1. 变量声明:变量提升,并且默认值是 undefined

  2. 函数声明

    • 函数声明,整个函数提升,并且优先级高于表达式定义
    • 表达式声明:只有函数名提升,默认值 undefined,函数部分不提升
  3. 同名变量和函数规则

    1. 如果是同名的函数,JavaScript 编译阶段会选择最后声明的那个。
    2. 如果变量和函数同名,那么在编译阶段,变量的声明会被忽略

JavaScript 执行流程:先编译,再执行

  1. 编译阶段:变量和函数会放到变量环境中

    执行流程图
    执行流程图
    • 执行上下文(Execution context)

      执行上下文是 JavaScript 执行一段代码时的运行环境

    • 可执行代码

  2. 执行阶段

    JavaScript 引擎开始执行可执行代码,按照顺序一行一行执行

7. 调用栈

  1. 函数调用

    运行一个函数,方式:函数名()

  2. 调用栈(执行上下文栈):管理函数调用的数据结构

    执行函数的调用栈
    执行函数的调用栈
  • 调用栈是 JavaScript 引擎追踪函数执行的一个机制

    • 每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码。
    • 如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶。
    • 当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈。
  • 如何利用浏览器查看调用栈信息:Call Stack

  • 栈溢出(stack overflow):调用栈是有大小的

8. 块级作用域

作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

  • 全局作用域
  • 函数作用域:函数内部变量或者函数,函数执行结束后,内部定义的变量会销毁
  • 块级作用域:ES6 新增

变量提升带来的问题

  • 变量被突然覆盖
  • 本应销毁的变量没有被销毁(for 循环中 i)

JavaScript 如何支持块级作用域

  1. 编译并创建执行上下文

    • 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了。
    • 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
  2. 继续执行代码,执行完毕,定义的变量就会从词法环境栈顶弹出

    变量查找过程
    变量查找过程

总结:块级作用域是通过词法环境的栈结构实现,变量提升是通过变量环境实现。

9. 作用域链与闭包

作用域链

作用域链的调用栈图
作用域链的调用栈图
  • 作用域查找变量的链条称为作用域链
  • 作用域链是通过词法作用域(静态作用域)来确定的,词法作用域反映了代码的结构
  • 基于调用栈,不是基于函数定义的位置

词法作用域

词法作用域就是指作用域是由代码函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

词法作用域
词法作用域

词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系

块级作用域中的变量查找

块级作用域查找变量
块级作用域查找变量

查找过程为 1、2、3、4、 5

  • 单个执行上下文顺序:词法环境->变量环境
  • 块级作用域执行完毕后,定义的变量会从词法环境栈顶弹出

闭包

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包

举栗子

function foo() {
  var myName = " 极客时间 ";
  let test1 = 1;
  const test2 = 2;
  var innerBar = {
    getName: function() {
      console.log(test1);
      return myName;
    },
    setName: function(newName) {
      myName = newName;
    }
  };
  return innerBar;
}
var bar = foo();
bar.setName(" 极客邦 ");
bar.getName();
console.log(bar.getName());
执行到return bar调用栈
执行到return bar调用栈
闭包产生
闭包产生
执行bar函数时
执行bar函数时
  • 根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量
  • 作用域链:local->closure(foo)->global