浏览器运行原理
- 网页请求的过程
输入一个url/域名,会经过DNS解析成ip地址,然后去请求指定的服务器,服务器返回资源(大多是html)给域名进行展示
回流和重绘(面试重点)
回流
回流reflow: (也可以称之为重排)
- 第一次确定节点大小和位置称之为布局。
- 之后对节点的大小,位置修改重新计算称之为回流
回流举例: 修改的盒子的大小/宽高,移除等。
- 修改DOM结构(添加/删除节点)
- 改变布局(width/height/padding/font-size)
- 改变窗口大小(手动修改)
反面例子: 如果是修改了文字的颜色之类的是不需要进行重新布局,所以不会产生回流。
重绘
第一次渲染内容称之为绘制(paint)
之后重新渲染称之为重绘
回流一定会引起重绘,所以需要尽量避免回流,且非常消耗浏览器性能。
避免回流的方式:
- 1.修改样式尽量一次性修改
- 2.尽量避免频繁的操作DOM
- 3.尽量避免通过getComputedStyle获取尺寸、位置等信息
- 4.对某些元素使用position的absolute和fixed (会引起回流,但是开销相对较小)
defer/async
defer
- 1.加上defer之后,js文件的下载和执行,不会影响后面DOM Tree的构建
- 2.在defer代码中的DOM Tree已经构建完成
- 3.defer代码是在DOMContentLoaded事件发出之前执行
- 4.多个defer是可以保证代码的正确执行的
- 5.defer在某种程度上是可以提升性能的,推荐将defer的script放在header中
- 6.defer仅适用于外部脚本
async
慎用!!!
- 1.浏览器不会因async脚本而阻塞(同defer)
- 2.async不能保证执行顺序,他是独立下载,下载后立即执行,不会等待其他脚本
- 3.async不能保证在DOMContentLoaded之前或之后执行
渲染页面的详细流程
HTML -> (html Parser) -> DOM Tree -> (Attachment) -> Render Tree -> (painting) -> display
style sheets -> (css parser) -> style Rules -> (Attachment) ->同上
其中Dom Tree 会有 (DOM操作) 对DOM Tree进行操作
Render Tree 会有 (layout) 对Render Tree进行布局
JS的执行原理
1.初始化全局对象
1.1js引擎在执行代码之前,会在堆内存中创建一个全局对象:Global Object (简称GO)
- 该对象所有的作用域都可以访问得到
- 里面会包含Data、Array、String、Number、setTimeout、setInterval等等
- 里面还有一个window对象指向自己
2.执行上下文
js引擎内部有一个执行上下文(栈)(Execution Context Stack 简称ECS),它是用于执行代码的调用栈
执行上下文是需要去执行全局的代码块的:
全局代码快为了执行会创建一个Global Execution Context(GEC)
GEC会被放到ECS中
GEC会被放到ECS中包含两部分:
第一部分:在代码执行前,在parser转化成抽象语法树(AST)的过程中,会将全局的变量、函数等加入到GO中,但是并不会赋值。默认给定undefined。
这个过程也被称为变量的作用域提升(hoisting)
第二部分:在代码的执行过程中,对变量进行赋值,或者执行其他函数
3.认识VO对象(Variable Object)
每一个执行上下文都会关联一个VO(变量对象),变量和函数声明会被添加到这个VO对象中
tip:当全局代码被执行的时候,VO 即 GO。
全局代码执行过程(执行前)
可以看到创建GO的时候,会包含Data、Array等,之前提到过的, 全局自带的一些属性和方法。
自己定义的变量,例如name、foo(函数)、num1、num2等等,默认值都为undefined。
其中在函数被创建的时候也会在创建一个函数所指向的对象。
此时的foo只是被创建了,但是并没有被调用,函数的定义是最先被执行的,定义执行后,会创建对应的内存地址并将foo指向对应的内存地址。并且函数也会有自己的属性和方法(name、length等),还有自己的函数代码,[[scopes]]函数作用域。
全局代码被执行后(执行后)
在全局代码被执行后,会给GO中的变量进行赋值,且从上到下依次按顺序进行执行。
当函数被执行时
在执行过程中碰到了函数,会根据函数体创建一个函数执行上下文(Function Execution Context,简称FEC),并且被压入EC Stack中
又因为每一个执行上下文都会关联一个VO。进入函数执行上下文会创建一个AO(Activation Object);
这个AO对象会使用arguments做为初始化,并且初始化时传入的参数。
这个AO对象会作为执行上下文的VO来存放变量的初始化的值。
函数被执行后
4.作用域和作用域链
当进入到一个执行上下文时,执行上下文都会关联一个作用域链(Scope Chain)
- 作用域链是一个对象列表,用于变量标识符的求值。
- 当进入到一个执行上下文时,这个作用域链被创建,并且根据代码类型添加一系列对象
理解
这里仅作为我自己学完这些之后选择老师的一个案例进行自己的理解和描述。
![](../../images/img/js执行原理/anli.png)
在js代码执行之前,会现在堆中创建一个全局对象:Global Object(GO)
在解析的时候会把var/function声明的变量/函数存放到全局对象中
例如
1
2
3
4
5
6global object = {
bar(): 0xa00(函数对象的地址)
foo(): 0xb00(函数对象的地址)
message: undefined
obj: undefined
}全局的代码块为了执行会构建一个Global Execution Context(GEC),这个GEC会被存放到执行上下文栈(Execution Context Stack = ECS)中,前面提到过GEC被存放到ECS中会分为两部分,第一部分是将全局定义的变量、函数加入到GO中,但是不会赋值,这一过程已经完成。
第二部分就开始执行js代码,最开始的是将字符串”global message”赋值给message
然后继续执行,将对象{ name: “beichen” }赋值给obj,此时在堆内存中会在创建一个obj的对象,里面保存着{ name: “beichen” }这个对象。并将创建出来的obj对象的地址赋值给GO中的obj,此时GO中的obj变成了
obj: 0xm00(obj对象的地址)
tips:在赋值,也就是执行代码的过程是在ECS中完成的中,也就是在栈中完成的,在堆中保存数据
接着是bar和foo函数的,因为function声明的函数会进行提升,声明是最先声明的,所以这里会跳过这两个函数,当执行函数的时候才会进入到函数体中执行代码。
到了代码的第34行,开始执行foo(123)的函数,在ECS中会在创建一个新的执行上下文(EC),并且将这个新的EC添加到ECS中,每一个执行上下文都会关联一个VO。在我们的堆内存中会创建一个AO(activation Object),并且初始化argument,添加foo函数中需要定义的变量到AO中,默认值都为undefined。
1
2
3
4
5
6
7
8Activation Object = {
// 初始就会有arguments
arguments: (是有值的)
num: undefined(传入的参数)
message: undefined
name: undefined
age: undefined
}然后开始执行foo函数体中的内容。
1
2
3
4
5
6
7
8Activation Object = {
// 初始就会有arguments
num: 123
arguments: (是有值的)
message: "foo message"
name: undefined
age: undefined
}这里执行到bar()这里的时候,函数不会继续执行下去,而是去创建一个新的执行上下文。将新的EC添加到ECS中,同时关联一个VO,在堆内存中创建的AO,就是VO所对应的值。
1
2
3
4Activation Object = {
arguments: (有值)
message: undefined
}开始执行bar()函数体中的内容
第20行代码会在ECS中执行。
将bar所对应的AO中的message赋值为字符串bar。
当bar中的赋值执行完毕后,bar函数就已经完全执行完了,bar所对应的EC就会从ECS弹出。此时栈顶为foo函数对应的EC。
然后继续执行foo函数的剩下的函数体
1
2
3
4
5
6
7
8Activation Object = {
// 初始就会有arguments
num: 123
arguments: (是有值的)
message: "foo message"
name: "beichen"
age: 22
}此时foo函数中的内容也执行完毕,foo对应的EC从ECS中弹出,此时栈顶为GEC,也就是全局的执行上下文,顺着foo(123)继续向下执行。
此时全局执行上下文也执行完毕,执行上下文栈中为空。此时结束。
每一个执行上下文EC(Execution Context)都会包含一个VO(Variable Object),全局执行上下文的VO对应的就是GO(Global Object),而函数所创建的执行上下文的VO所对应的就是在堆内存中创建出来的AO(Activation Object),在EC中不仅包含VO,同时也会有作用域链,且作用域链是在执行上下文最开始被执行的时候就创建好的,EC中还会包含this