82. 钻钻牛角尖:为什么开发浏览器引擎这么难?

82. 钻钻牛角尖:为什么开发浏览器引擎这么难?

2016-12-28    26'35''

主播: 软件那些事儿🚉

9 0

介绍:
这一期的内容继续讲浏览器的工作原理,以及一些吐槽,或者这期的内容继续吐槽,顺便讲一点浏览器的工作原理。因为有些听众希望听到干货,不想听吐槽,对这些听众我想说的是,进错剧场了,我这个节目主要是吐槽,没有啥干货,并且,我始终认为,如果想学编程,希望通过睡前听不到半小时的音频,这是缘木求鱼。编程这个东西,是通过编译器以及无数次痛苦的调试才能掌握的技术,我不认为有什么灵丹妙药,可以让人通过每天半小时,或者每天7分钟能学会的。每天7分钟,让你成为编程高手,这就是扯淡啊!我觉得别说每天7分钟,我觉得0基础程序员的话,每天7小时,也得一年才能入门。[图片]说实在的,我倒是看到过不少音频节目是这样宣传的,比如说,每天5分钟,让你实现财务自由,但是,如果要想听每天5分钟,得先付出199元钱,然后才能财务自由。然后,很多人就付了199元,结果让人家主播财务自由了。大家可能觉得我吃不到葡萄说葡萄酸,讲真,你们猜对了!我真的想不懂,真的有人觉得每天听5分钟,就能财务自由,这是不太可能的。我觉得任何有价值的技术,包括忽悠这门技术,绝对不会是每天5分钟,听半年就学会的,一定需要不停的锻炼才可以掌握。编程也是这样,可能相对于其它的一些技巧,编程算不上特别复杂的技术,但是,大家也不要认为通过听电台就能学会编程。如果想学编程,听我这个电台是不合适的,所以,大家也不要总是留言说要干货,我这里没有干货,只有吐槽。不要幻想通过听广播学编程,学编程只有一条路,就是打开你的电脑,打开编译器,要么阅读别人的源代码,要么调试自己的项目。其它的途径,都是效果很差的。开始吐槽了,当浏览器拿到了HTML文档,需要解析,解析的最终结果是一棵二叉树,二叉树的每一个节点都是一个文档的结构。这棵树的名字叫解析树或者语法树。这两种叫法我都看到过,但是说的都是一回事儿,在数据结构的教材上,叫语法树的时候比较多。那什么是语法呢?这个语法和我们英语课上学的语法是不同的。我要开始讲编译原理的知识了。而且我没法用通俗易懂的语言讲编译原理的知识。我慢慢的发现,如果要讲清楚,或者说我自己思考清楚一件事情,唯一的方法就是从头讲起,比如说,我们很难让一个没有学过电路知识的人明白,一个电路是怎么工作的,如果真的要讲清楚,只有一个办法,就是从晶体管那个地方开始讲,否则,肯定是不清楚的。但是,如果从晶体管开始讲,那时间可就太长了,得讲好几天。还有一种办法,虽然讲不清楚,但是可以让听的人或者看的人,觉得自己清楚了,这种方法就是基本上所有的科普读物所采用的方法,类比的方法。这种方法其实只是好像清楚了,实际上根本没清楚。我可以举个例子,比如说,现在的手机都是32G存储或者128G存储,但是,手机里还有个存储器,也就是内存,是2G或者4G。一方面是一个32G或者128G的存储器,另一方面是一个2G或者4G的内存,如果想对一个没有搞过计算机的人讲清楚这两个东西,是非常困难的,甚至,可能就是徒劳的。因此,我听过一个节目,就是试图讲清楚这两个东西是不同的。最后,不得不用了类比的方法,这也是几乎所有的科普书籍所采用的方法,类比。他把手机内存比做超市里的货架,把外存比作超市的仓库,然后说这两个是不同的,为什么不同呢?讲不清楚么,最后,不懂的人觉得手机里有一个超市的货架,还有一个超市的仓库,至于到底有什么不同,也就不去深究了。毕竟,手机里有有两个东西,一个是货架,一个仓库,这代表着两种存储器。这算不算是个方法呢?说实在的,我觉得也算是个方法,因为我也很喜欢看科普的读物,对个外行来说,知道一点总比啥也不知道强。但是,类比实际上对理解真正的原理没太多的帮助。我得再狡辩一下,我对科普并没有任何批评的意思,因为面向大众的科普,比如说,我如果想了解黑洞,想了解微观可逆性,只能看面向我这种档次的人的科普,也就是大众科普。大众科普其实就是在非常浅显的面向大量的读者;如果读博士了,就是在很深的层次上面向很少的人,博士生的论文,肯定不会出现很业余的类比,人家都是直接上数学公式。所以,我打算以不用类比的方法开始讲编译原理的知识。即使这样可能会有点枯燥无味。因为我的观点是这样的,计算机这个东西,总共发展了就这么多年,不用类比的方法,我觉得大部分人还是能理解计算机的美丽的,尤其是程序员。就像我们看到一个美女,就知道她是美女。根本不用类比的方法,说她像西施或者像仙女,比如说,汤唯就是汤唯,高圆圆就是高圆圆,不用类比,我也觉得她们是美女。编译原理就是编译原理,不用类比,我觉得也可以领略编译原理的美。首先,讲词法分析器,这个玩意也叫词法扫描器。当我们拿到一个HTML文档的时候,尤其是可以把语法高亮显示的时候,我们一眼就可以看出,不同的颜色,代编不同的含义。比如说有HTML的各种标签,显示的颜色就和其他的文字的颜色不同。然后,我上一期节目提到的那几个引擎webkit开始进入语法分析阶段,在术语上把单词叫做token,也有翻译为令牌,记号等等,在这里我把这个token翻译为单词,因为HTML文件分解后的东西,的确非常像一个一个的单词。然后,用相对简单的规则,就把从网络上下载下来的文本文件,分解为一串一串的单词流,这样,为了下一步的词法分析做准备。很可惜,这一段话仍然是白话,对计算机来说,这段话就是废话,他根本不懂。为了让计算机能理解上面我说的这段话,需要用计算机才能理解的语言。比如说,正则表达式就是一个比较理想的选择。如果是程序员,我就默认大家知道啥是正则表达式,程序员经常用正则表达式匹配字符串等等,我也默认大家已经是个正则表达式高手了。尤其是大家可以在脑子里默念一下正则表达式的规则,两个正则表达式的并,两个正则表达式的连接以及一个正则表达式的克林闭包,这三个表达式在心里想几秒钟,其实用这三种正则表达式的运算,就可以把文件给分离的差不多了。尤其是能完全用正则表达式表达的语言,比如说C#就是,这种语言也被称为正则语言。但是,很可惜,我们这个音频里讲的是怎么处理HTML和CSS,恰好,HTML和JSON都不是正则语言,那怎么搞呢?我当然是搞不定的!但是,因为这个世界上有不少默默无闻的科学家,数学家,他们能搞定。既然讲到了数学家,我个人心里还是挺佩服数学家的,因此,我本科在理学院中混,算是学了4年数学吧,当然,这句话说的有点儿心虚,因为我逃了好多课,然后就找不到工作了,幸亏学数学的时候,老师未雨绸缪,知道天天让我们学微分几何,偏微分方程或者拓扑学,是找不到工作的,所以,我们系允许去计算机那里学点编程,以防止毕业以后因为找不到工作,给饿死了,所以我学了一些C++编程,然后我就当了程序员,来填饱肚子。因为我们所熟知的一些语言,是有递归的性质,比如数学运算也有递归的性质,尤其是我们小学三四年级所进行的加减乘除四则运算,有好几个括号的那种运算,实际上是可以通过递归的方法来算出来。可惜的是,我们三四年级的时候,根本不懂这个。在递归的语言中,我们没法用DFA来解释,因为,DFA只有有限个状态,是没办法追溯无限递归的。比如说,我们写网页的时候,可以在一个列表里包含另一个列表,在另一个列表里又包含另一个列表,理论上,只要足够闲的蛋疼,我们可以无穷的包含下去,这个时候,就是我前面所说的,使用正则表达式,正则语言还有DFA工具,都搞不定的状态。幸亏,有一些数学家,又搞出来了一个语言,他的名字叫上下文无关语言。英文叫做Context-Free Grammar。依靠这个工具,我们才可以愉快的上网看网页,当然了,绝大部分的网友根本不用关心上下文无关语言到底是个什么东西,这也是科学的神奇之处。我们不用懂空调的工作原理,只需要按几个按钮,就可以使用空调了。开源的伟大之处在于,并不是所有的东西都要从零开始,因为有了各种解析器了,毕竟创建一个解析器并不容易,尤其是能兼顾效率和精确的解析器更是难上加难。webkit使用了两种非常著名的解析器生成器,第一个叫Flex用来创建词法分析器,还有Bison用来创建解析器。他们也有其他的名字,比如叫Lex或者Yacc,不过,大家很少能用到这两个东西,除非真的去做浏览器或者做编译器。Flex处理的是包含编辑的正则表达式定义的文件,Bison处理的则是BNF格式的语法规则。接下来再说说HTML解析器,很可惜的是,常规的解析器都不能解析HTML,为什么呢?为什么呢?HTML和XML差不多样子,解析XML的解析器多如牛毛,为啥解析HTML的解析器就寥若晨星呢?那是因为XML是一种非常严格的语言,HTML是一种非常宽容的语言。简单来说,XML不允许存在一点错误,HTML则是差不多就行。HTML可以随便写写,比如说,HTML可以随便省略一些标记,也可以省略开始或者结束的标记,反正HTML简直就是最宽容的语言,写错了问题不大,只要是能显示出来,前端的同学也懒得去修改,即使知道这玩意有错误。这样的后果就是,HTML的解析器能容忍错误,不能说一个错误,浏览器给崩溃了。一个错误导致崩溃,大部分编程语言都是这样的,比如说闪退啥的,很可能是一个小错误。但是由于HTML处理的网页是各种各样水平的程序员写的,网页里面的错误肯定是五花八门,如果一个错误就让浏览器崩溃,那就不用上网了。所以,这也就是HTML的难点所在,得和错误共存。稍微总结一下开发一个浏览器引擎的难点,正是因为这些难点,所以才导致世界上可能有几百种浏览器,但是核心的引擎,就那么三四种:1. HTML语言宽容的本质。所以,我们程序员真的要感谢那么多人默默的付出,才让我们写的那些垃圾代码能显示出来,即使我们写的HTML像一坨屎,浏览器的引擎也默默的帮我们处理成我们想要的样子。2. 浏览器能处理很久远的网页,即使一些无效的HTML标签,他也知道什么时候该忽略掉。尤其是浏览器大战的年代,各家浏览器搞创新,每家都有一些独特的创新,可以显示在自己的浏览器上,那些创新尽量不让他们正确的显示在别人家的浏览器上。嗯,人类这个物种,有时候挺恶心的。3. 解析的过程需要不停的反复。为什么这么说呢,举例来说,如果在HTML中,有可以写入的选项,这个时候,因为用户要不停的写入内容,因此,这个浏览器要反复的解析,写入一点,解析一点。这也是非常棘手的一个问题。这三个难点是表面上就能看出来的,具体还有什么更多的难点,我想,也许只有开发引擎的程序员能体会了。现在已经是HTML5的年代了,HTML5规范则详细的描述了解析算法。HTML5的解析算法主要分为两个阶段,第一个阶段叫标记化阶段,第二个阶段则是构建树的阶段。如果大家有兴趣的话,可以google搜索关键字HTML 5 Parsing,就可以找到链接。或者,你可以输入HTML在百度搜索引擎里,你可以找到相关的培训班,然后交钱参加培训班以后,可以去问老师HTML5的解析算法是什么。这一点,不得不服百度呢,我一搜HTML,先给我搞了四个网站开发培训班的广告。这一点,百度的人性化特别好。如果你不想搜索的话,可以关注我的微信公众号——软件那些事儿——我把HTML 5 parsing的链接放在这里,还是挺有趣的。https://www.w3.org/TR/2011/WD-html5-20110113/parsing.html我简要的把这个链接的意思概括一下,不过呢,还是建议大家去读一下,还算有趣,单词量也不大。技术文章的单词量不大,这一点让我这个英语渣感觉有点幸福。竟然抱着一本字典,能读懂。第一个阶段叫标记化阶段,讲输入的内容,也就是HTML文件,解析成多个HTML的关键字,也就是标记。这些标记包括HTML的起始标记,也就是这个东西,结束标示,还有属性的名称,以及属性的值,这些都是在第一个阶段完成的。 第二个阶段叫构建树的过程,由第一个阶段生成的那些标记,传递给构造器,然后反复执行,直到第一个阶段生成的标记都完成了,这是个循环的过程。最后,生成一刻HTML解析树。这个流程呢,我把解析图放在这里,这个图也是在w3那个链接里的,如果有人去读一下原文,就不用看这个图了,这个图解释了整个过程。HTML的解析流程。[图片]HTML解析流程好吧,这一期就到这里,下一期继续讲浏览器的工作原理。