jQuery学习笔记 - Sizzle引擎

为什么要有Sizzle?

常见的选择器:

#test表示id为test的DOM节点
.aaron 表示class为aaron的DOM节点
input表示节点名为input的DOM节点
div > p表示div底下的p的DOM节点
div + p表示div的兄弟DOM节点p

其实,是通过浏览器提供的接口实现的:
获取id为test的DOM节点
document.getElementById(“test”)

获取节点名为input的DOM节点
document.getElementsByTagName(“input”)

获取属性name为checkbox的DOM节点
document.getElementsByName(“checkbox”)

高级的浏览器还提供:

document.getElementsByClassName

document.querySelector

document.querySelectorAll

由于低级浏览器并未提供这些高级点的接口,所以才有了Sizzle这个CSS选择器引擎。

“从右到左”解析CSS选择器

为什么排版引擎解析 CSS 选择器时一定要从右往左解析?

首先 先了解下浏览器的工作原理:

浏览器的主要功能

浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示您选择的网络资源。
这里所说的资源一般是指 HTML 文档,也可以是 PDF、图片或其他的类型。
资源的位置由用户使用 URI(统一资源标示符)指定。(就是你输入一个网址去请求的)

浏览器的高层结构

1、用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
2、浏览器引擎 - 在用户界面和呈现引擎之间传送指令。
3、呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
4、网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
5、用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
6、JavaScript 解释器。用于解析和执行 JavaScript 代码。
7、数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。


浏览器的主要组件

值得注意的是,和大多数浏览器不同,Chrome 浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程。

呈现引擎

作用:就是“呈现”嘛,把显示请求的内容显示在浏览器屏幕上~
Firefox 使用的是 Gecko,这是 Mozilla 公司“自制”的呈现引擎。而 Safari 和 Chrome 浏览器使用的都是 WebKit。

主流程


主流程示例

需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。

javascript 解析机制

解析过程分为编译执行 两个阶段。
编译就是预处理(预编译)。
在预编译期,javascript解释器会把javascript脚本代码转换成 字节码。【js代码 => 字节码】
在执行期,javascript解释器借助执行期环境把字节码生成机械码,按顺序执行代码完成程序设计的任务。【字节码 => 机械码】

  • 什么是解析?
    答:解析就是把文档转化成有意义的结构。也就是可让代码理解和使用的结构。

  • 解析后会得到什么?
    答:代表文档结构的节点树(解析树/语法树)

示例:
解析 2 + 3 - 1 这个表达式

  • 解析的过程
    • 词法分析(词法分析器)
      将 输入内容 分割成 大量标记 的过程(标记是语言中的词汇,即构成内容的单位。在人类语言中,它相当于语言字典中的单词。)
      词法分析器(有时也称为标记生成器),负责将输入内容分解成一个个有效标记

    • 语法分析(解析器)
      应用 语言的语法规则 的过程
      解析器负责根据语言的语法规则分析文档的结构,从而构建解析树。

解析是一个迭代的过程。通常,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。如果发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,然后继续请求下一个标记。

如果没有规则可以匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与所有内部存储的标记匹配的规则。如果找不到任何匹配规则,解析器就会引发一个异常。这意味着文档无效,包含语法错误。

预编译

javascript是一种解释型语言,而不是编译型语言。(解释型:在代码执行的时候才被解析器一行一行动态编译和执行,哎哟,也就是边编译边执行啦;编译型:执行前就完成编译)
编译需要有编译器咯,一般编译器可以包括下面组件:
符号表
词法分析器
语法分析器
语义检查器
中间代码生成器
代码优化器
代码生成器

看上面的图哈,对于一般编译步骤分为:词法分析、语法分成、语义检查、代码优化、生成字节码。But!对于javascript这类解释型语言来说,通过词法分析和语法分析,并建立语法树之后,就开始解释执行了。不是等完全生成字节码之后才调用虚拟机来执行这些编译好的字节码。

字符流(一段字符串,可能有空格、注释之类的) -> 记号流(一个一个的单词,去掉空格注释那些) -> 语法树(程序结构的树形表示) -> 正确的语法树 -> 中间代码 -> 优化的中间吗 -> 字节码

执行期

经过编译阶段的准备,javascript代码 在内存中 已经被构建为语法树了,然后javascript引擎 就会根据这个语法结构 边解释边执行了。

解释过程中,javascript引擎严格按 作用域机制来执行。

javascript语法采用词法作用域,就是说,js的变量、函数的作用域是在定义的时候决定的,不是执行的时候决定的。

当js解释器执行每个函数的时候,先创建一个执行环境;然后,在这个虚拟环境中创建一个调用对象;再然后,在这个对象里存储当前域中所有的局部变量、参数、嵌套函数、外部引用、父级引用列表upvalue等等语法分析结构。

在我们声明语句的时候(预编译期),就已经把变量、函数名(var 函数名 = function(){}的形式,函数名是指针)等存储到符号表中了。然后把函数名映射到实际的函数。
这个调用对象(指针)的生命周期和函数的生命周期是一致的,当函数调用完毕并且没有外部引用的情况下, 会自动被js引擎当做垃圾进行回收。


现在,继续回到问题:为什么排版引擎解析 CSS 选择器时一定要从右往左解析? 

假设,我们“从左往右”解析,例如「div div p em」,我们首先要找到所有的div,再去找它的儿子div、儿子的儿子p、儿子的儿子的儿子em,一步一步往里面找,如果一步没找到,就回到父亲那里重新出发,找下一个儿子(儿子也可能有儿子,不确定是到哪一步回溯的)。就是需要回溯若干次才能确定匹配与否,效率很低!!

假设,“从右往左”解析,先找到所有em,向上找它的父亲,父亲不匹配就不匹配,就找换个儿子em继续找。效率会高很多。

要知道DOM树的结构,一个元素可能有若干子元素,如果每一个都去判断一下显然性能太差。而一个子元素只有一个父元素,所以找起来非常方便!

说了这么多,就是为了引申出一个思想:
CSS选择器其实也就是一段字符串,我们需要分析出这个字符串背后对应的规则,在这里Sizzle用了简单的词法分析(句子->单词)。
所以在Sizzle中专门有一个tokenize处理器干这个事情。

未完。。。

参考自Aaron
How browsers work

posted @ 2016-04-16 22:23  Sameen  阅读(136)  评论(0编辑  收藏  举报