做者:殷荣桧@腾讯javascript
1、代码如何被解析成DOM对象css
2、节点的方法是如何添加到DOM上的html
3、简单模仿浏览器挂载DOM方法前端
4、看JSDom源码的疑问java
5、了解元素的实例化node
你也能够在个人Github查看这篇文章,欢迎Star.有什么问题能够在Github中评论。git
1、代码如何被解析成DOM对象github
就以以下的一行代码为例,开始探索代码解析的历程,先提出第一个疑问,这三行代码,是怎样解析成DOM对象的?web
// 代码以下
<div id="exampleId">
<span>hello world</span>
</div>
复制代码
DOM对象以下:正则表达式
截图中生成AST的连接,你能够去点击尝试修改HTML代码生成抽象语法树试试。这是浏览器拿到咱们所写的HTML文本代码第一步所要完成的事,由于文本的格式是不便于操做的,好比如今要去一个div节点的属性id的内容(exampleId),你说怎么取,估计你能想到用正则表达式,可是不能就靠正则表达式过日子啊,要取的属性各类各样,还有自定义的属性要取,这时候正则表达式表示也顶不住了。只有把文本的内容生成必定的数据结构才方便对其进行操做(如这边生成的AST抽象语法树)。这样经过相似document.html.body.div.attrs.id就能够成功取到。
若是你对浏览器将HTML生成抽象语法树的细节感兴趣,看chrome源代码几乎是不太可行的,你能够查看这样一个GitHub仓库:parse5,这个是用JS实现的,方便查看,你甚至还能够查看一个正则表达式版本的,也就是我前面提到的啥都用正则表达式来处理的,GitHub仓库:html-parse-stringify
2、节点的方法是如何添加到DOM上的
解决了第一个问题,产生了一个简单的抽象语法树,接下来问题又来了,平时咱们用的document.getElementByTagName在咱们这个抽象语法树上并无,咱们只有简单的HTML结点的属性nodeName,tagName之类的,并无这些方法,那么这些获取节点的方法又是从哪来的呢?
注:在chrome的控制台中直接输入document或使用console.log(document)是无法查看document属性的,
须要使用console.dir(document)来查看其属性。查看的属性以下图所示:
复制代码
打印出浏览器的docuemnt对象后,咱们在原型链上向下翻了好几层,终于找到了咱们想要找的getElementByTagName,是挂载在Document这样的一个原型链处,以下图所示。为何又来一个Document,和咱们以前的console.dir(document)中的document有什么不一样?
到了这边就须要搬出咱们的W3C标准来了,为何打印出来的document对象上有那么多的属性,有那么长的原型链?由于这些都是W3C标准规定的,就以一个上图中的属性字段URL为例,都是W3C规定的Docuemnt上须要有哪些属性,能够点击这里查看所定义的属性,查看这里。能够看出document是一个相似与Document的实例,实际上他们中间只是又隔了一层HTMLDocument。
那么原型链又是如何定义的呢,在W3C标准中能够看到是经过接口继承来实现的,你能够在标准中查到以下字样:
interface Document : Node
复制代码
这就说明Document是继承自Node接口的。这就是原型链的由来,咱们把原型链从顶端到低端都画出来,大概就是下面这样了。
从这里咱们就能够看出,一个Document是一个W3C定义好的接口,另外一个是HTMLDocument的实例化。
接下来咱们再一块儿看看,这条原型链上每一个节点的定义:
HTMLDocument接口的定义
现行标准:https://html.spec.whatwg.org/multipage/window-object.html#htmldocument
MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLDocument
Document接口的定义
现行标准:https://dom.spec.whatwg.org/#interface-document
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Document
Node接口的定义
现行标准:https://dom.spec.whatwg.org/#interface-node
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Node
EventTarget接口的定义
现行标准:https://dom.spec.whatwg.org/#interface-eventtarget
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget
复制代码
3、简单模仿浏览器挂载DOM方法
看了这些标准以后,你是否是想看看浏览器是如何将这些标准中定义的各类属性及方法挂载到咱们刚才经过parse5生成的简单的AST(抽象语法树)上的。仍是以前说的,看Chrome源代码不太现实,做为前端工程师最好的就是看看有没有JS实现版本的,恭喜,找到了一个JSDom,虽然和Chrome源代码实现机理不可能100%类似,当原理应该相差很少。接下来就一块儿看看getElementByTagName这个方法是怎么挂载到咱们的AST上去的。
在JSDom这里找到了whatwg接口的定义,和咱们看到的标准中的定义相差无几。这边是JSDom中给出的定义文件。接下来看看JSDom的实现:
能够看出其大概原理就是遵照w3c的标准将所须要的方法挂载到prototype上去了。若是在咱们生成的AST树上挂载,大概就是这样
ASTTree.prototype.getElementsByTagName = function(tagName) {
// 以下代码只是为说明大体含义,并不能正常运行
return this.ASTTree.html.body.childNodes.forEach((child) => {
child.tagName = tagName;
})
}
复制代码
这样你要根据TagName获取到对应的节点,就能够很方便的使用已经封装好的这个getElementsByTagName了,这样就基本有了咱们的DOM树的雏形了。
4、看JSDom源码的疑问
其实当你看JSDom代码和w3c标准的时候,可能会遇到一些疑惑,这里我把我本身遇到的几个疑惑耗时较长的两个个说明一下,避免你也踩坑。
第一个疑问是为何在chrome devtools中用console.dir(document)中有好多的事件,如onauxclick、 onblur、onabort这些在jsdom对于document实现的文档中都没有。
后来才发现,原来是经过使用Mixin来实现的,这些在标准中也都有定义。
// 具体在jsdom中的实现
mixin(DocumentImpl.prototype, GlobalEventHandlersImpl.prototype);
复制代码
// 标准中的定义
Document includes GlobalEventHandlers;
复制代码
第二个疑问就是,都说属性也是节点,好比id,name,style都应该有nodeType才是。可是为何没有nodeType呢?
仍是才疏学浅,只能到stackoverflow上去请教,获得了以下的答案:
我在浏览器的控制台中试了一下,的确如此,以下图所示,说明属性Attr也的确是一个节点,可是从查找到的信息综合来看,接下来的标准中会取消这一点,Attr将再也不是节点。
5、了解元素的实例化
以上只是大体了解了浏览器document实例化的造成过程,趁热打铁,把很类似的元素的实例化也了解一下。不懂元素的实例化不要紧,来看个例子就了解。打开devtools,在Element的Tab中随意选中一个节点,而后选择右侧的Properties的Tab,就能够看到这个元素实例的原型链。
每一个原型链节点均可以在w3c标准或者MDN中找到定义,实现的过程也和上面的AST上添加方法相似,这些在jsdom中均可以找到。
至此,咱们基本上搞清楚了咱们平时为何写一段文本的HTML代码,到了浏览器中就有个document对象可用,就能够有好多相似onclick,getElementBy....能够用。经过getElementById获取到某一个节点后又会有innerHTML,outerHTML等可使用。这些原来都是根据代码创建AST后在树上根据W3C标准添加上了各类方法就造成了你的document,和element等等。
最后,你若是在看文章,或者学习探索DOM的过程当中有什么疑问,均可以在github,或直接在下方评论。我有时间了就搞一搞,搞出名堂了我再补充到文章中。
参考文章:
How the browser renders HTML & CSS
An Introduction and Guide to the CSS Object Model (CSSOM)
Attributes and properties Provide location info for the attributes