浏览器工作原理
浏览器解析渲染页面
浏览器在解析过程中,如果遇到JS文件,HTML文档会挂起渲染过程,要等到js加载和解析执行完毕才会继续HTML的渲染过程。
原因是:因为JS有可能修改DOM结构。CSS的加载不影响js文件的加载,但却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。
从资源的字节流到DOM树(转载)
什么是字节流?
字节流是指传输过程中,传输数据的最基本单位是字节的流,一个不包含边界数据的连续流;字节流是由字节组成的,主要用在处理二进制数据。
字节流的具体过程
字节流经过解码过后就是字符流,然后经过词法分析器解析成词语,之后经过语法分析器构建成节点,最后组成一棵DOM树。
-
词法分析
- 在进行词法分析前,解释器首先检查网页内容的编码格式,找到合适的解码器,将字节流转换成特定格式的字符串,然后交给词法分析器进行分析。
- 每次此法分析器都会根据上次设置的内部状态和上次处理后的字符串来生成一个新的词语。 (ps:生成的词语还会进到xssAutitor来验证词语是否安全合法,非合法的词语不能通过)
-
词语到节点
- 得到词语(tokens)后,就可以形成DOM节点了。
- 这里说的节点不单单指HTMLElement节点,还包括TextNode/Attribute等等一系列节点,它们都继承自Node类。
-
组成DOM树
- 因为节点都可以看成有开始和结束标记,所以用栈的结构来辅助构建DOM树再合适不过了。
浏览器如何加载页面
- 输入服务器地址,与服务器进行连接
- 服务器返回静态资源(一般为index.html)
- 解析index.html (在生成render tree时,并不会立即进行绘制,中间还会有一个layout操作,也就是布局引擎,Layout的作用就是确定元素具体的展示位置和展示效果;)
- 解析时如果遇到css或者js文件,就像服务器请求并下载相应的css文件和js文件。
- 最后浏览器进行渲染,执行js代码
JavaScript引擎
为什么需要JavaScript引擎?
- 首先,我们需要知道JavaScript是一门高级编程语言,所有的高级编程语言都是需要转换成最终的机器指令来执行的;
- 而我们知道编写的JS代码可以由浏览器或者Node执行,其底层最终都是交给CPU执行;
- 但是CPU只认识自己的指令集,也就是机器语言,而JavaScript引擎主要功能就是帮助我们将JavaScript代码翻译CPU所能认识指令,最终被CPU执行;
JavaScript引擎有哪些?
- SpiderMonkey:第一款JavaScript引擎,由Brendan Eich开发(JavaScript作者);
- Chakra:用于IE浏览器,由微软开发;
- JavaScriptCore:Webkit中内置的JavaScript引擎,由苹果公司开发;
- V8:目前最为强大和流行的JavaScript引擎,由Google开发;
V8引擎(转载)
事件发生时,浏览器进程会发送事件类型及相应的坐标给渲染进程,渲染进程随后找到事件对象,交给js引擎处理,如果js代码中利用了桥接接口将该节点绑定了事件监听,那么就会触发该事件监听函数。
先了解一下官方对V8引擎的定义:
- V8引擎使用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等,可以独立运行,也可以嵌入到任何C++的应用程序中。。
- 所以说V8并不单单只是服务于JavaScript的,还可以用于WebAssembly(一种用于基于堆栈的虚拟机的二进制指令格式),并且可以运行在多个平台。
V8引擎的架构 (1)Parse模块:将JavaScript代码转换成AST(抽象语法树)。
-
该过程主要对JavaScript源代码进行词法分析和语法分析;
-
词法分析:对代码中的每一个词或符号进行解析,最终会生成很多tokens(一个数组,里面包含很多对象);
-
语法分析:在词法分析的基础上,拿到tokens中的一个个对象,根据它们不同的类型再进一步分析具体语法,最终生成AST;
-
以上即为简单的JS词法分析和语法分析过程介绍,如果想详细查看我们的JavaScript代码在通过Parse转换后的AST,可以使用AST Explorer工具: (2)Ignition模块:一个解释器,可以将AST转换成ByteCode(字节码)。
-
字节码(Byte-code):是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。
-
将JS代码转成AST是便于引擎对其进行操作,前面说到JS代码最终是转成机器码给CPU执行的,为什么还要先转换成字节码呢?
- 因为JS运行所处的环境是不一定的,可能是windows或Linux或iOS,不同的操作系统其CPU所能识别的机器指令也是不一样的。字节码是一种中间码,本身就有跨平台的特性,然后V8引擎再根据当前所处的环境将字节码编译成对应的机器指令给当前环境的CPU执行。 (3)TurboFan模块:一个编译器,可以将字节码编译为CPU认识的机器码。
-
在了解TurboFan模块之前可以先考虑一个问题,如果每执行一次代码,就要先将AST转成字节码然后再解析成机器指令,是不是有点损耗性能呢?强大的V8早就考虑到了,所以出现了TurboFan这么一个库;
-
TurboFan可以获取到Ignition收集的一些信息,如果一个函数在代码中被多次调用,那么就会被标记为热点函数,然后经过TurboFan转换成优化的机器码,再次执行该函数的时候就直接执行该机器码,提高代码的执行性能;
-
还存在一个
Deoptimization过程,其实就是机器码被还原成ByteCode,比如,在后续执行代码的过程中传入热点函数的参数类型发生了变化(如果给sum函数传入number类型的参数,那么就是做加法;如果给sum函数传入String类型的参数,那么就是做字符串拼接),可能之前优化的机器码就不能满足需求了,就会逆向转成字节码,字节码再编译成正确的机器码进行执行; -
从这里就可以发现,如果在编写代码时给函数传递固定类型的参数,是可以从一定程度上优化我们代码执行效率的,所以TypeScript编译出来的JavaScript代码的性能是比较好的;
V8引擎执行过程
-
①Blink内核将JS源码交给V8引擎;
-
②Stream获取到JS源码进行编码转换;
-
③Scanner进行词法分析,将代码转换成tokens;
-
④经过语法分析后,tokens会被转换成AST,中间会经过Parser和PreParser过程:
-
Parser:直接解析,将tokens转成AST树;
-
PreParser:预解析(为什么需要预解析?)
- 因为并不是所有的JavaScript代码,在一开始时就会执行的,如果一股脑对所有JavaScript代码进行解析,必然会影响性能,所以V8就实现了Lazy Parsing(延迟解析) 方案,对不必要的函数代码进行预解析,也就是先解析急需要执行的代码内容,对函数的全量解析会放到函数被调用时进行。
-
-
⑤生成AST后,会被Ignition转换成字节码,然后转成机器码,最后就是代码的执行过程了;