本文是对李兵老师的《浏览器工作原理与实践》课程的记录与整理。
课程地址:浏览器工作原理与实践
《浏览器工作原理与实践》分为几篇笔记,接上文
5、渲染流程
❝渲染流程总结
❞
- 渲染进程将 HTML 内容转换为能够读懂的「DOM 树」结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的「styleSheets」,计算出 DOM 节点的样式。
- 创建「布局树」,并计算元素的布局信息。
- 对布局树进行分层,并生成「分层树」。
- 为每个图层生成「绘制列表」,并将其提交到合成线程。
- 合成线程将图层分成「图块」,并在「光栅化线程池」中将图块转换成位图。
- 合成线程发送绘制图块命令「DrawQuad」给浏览器进程。
- 浏览器进程根据 DrawQuad 消息「生成页面」,并「显示」到显示器上。
主线程
构建 DOM 树
- 树结构
样式计算
css 结构-stylessheets
转换属性值标准化
- rgb
- px
计算 DOM 树每个节点具体样式
- CSS 继承和层叠规则
- useragent 浏览器默认样式
- 层叠继承
- CSS 继承和层叠规则
布局阶段(Layout Tree)
- 创建布局树(所有可见节点,不可见的会被忽略)
- 布局计算
分层 图层树(LayerTree)
渲染引擎还需要为特定的节点升成专用的图片并生成图层树
拥有层叠 上下文属性元素会被单独升为一层
- 定位属性(position)
- 透明属性
- CSS 滤镜
- z-index
需要剪裁(clip)被创建为图层
overflow:auto
图层绘制(Paint)
绘制指令
非主线程(合成线程)
栅格化操作
「合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图」
重排. 重绘. 合成
重排:「更改了元素几何属性」,需要更新整个渲染流水线,开销最大。
重绘:「更改元素绘制属性」,省去了布局和分层阶段,执行效率高一点
合成:渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做「合成」。非主线程操作
- transform
二、 javascript 执行机制
6. JavaScript 变量提升
变量提升(Hoisting)
「所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。」
变量声明:变量提升,并且默认值是 undefined
函数声明
- 函数声明,整个函数提升,并且优先级高于表达式定义
- 表达式声明:只有函数名提升,默认值 undefined,函数部分不提升
同名变量和函数规则
- 如果是同名的函数,JavaScript 编译阶段会选择最后声明的那个。
- 如果变量和函数同名,那么在编译阶段,变量的声明会被忽略
JavaScript 执行流程:先编译,再执行
编译阶段:变量和函数会放到变量环境中
执行上下文(Execution context)
「执行上下文是 JavaScript 执行一段代码时的运行环境」
可执行代码
执行阶段
JavaScript 引擎开始执行可执行代码,按照顺序一行一行执行
7. 调用栈
函数调用
运行一个函数,方式:函数名()
调用栈(执行上下文栈):管理函数调用的数据结构
调用栈是 JavaScript 引擎追踪函数执行的一个机制
- 每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码。
- 如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶。
- 当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈。
如何利用浏览器查看调用栈信息:Call Stack
栈溢出(stack overflow):调用栈是有大小的
8. 块级作用域
作用域
「作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。」
- 全局作用域
- 函数作用域:函数内部变量或者函数,函数执行结束后,内部定义的变量会销毁
- 块级作用域:ES6 新增
变量提升带来的问题
- 变量被突然覆盖
- 本应销毁的变量没有被销毁(for 循环中 i)
JavaScript 如何支持块级作用域
编译并创建执行上下文
- 函数内部通过 var 声明的变量,在编译阶段全都被存放到「变量环境」里面了。
- 通过 let 声明的变量,在编译阶段会被存放到「词法环境」(Lexical Environment)中。
继续执行代码,执行完毕,定义的变量就会从词法环境栈顶弹出
「总结:块级作用域是通过词法环境的栈结构实现,变量提升是通过变量环境实现。」
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());
- 根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量
- 作用域链:local->closure(foo)->global