今天我们来阅读下weex源码的vdom. 阅读这一部分,你首先知道dom提供的接口是什么.源码所在位置是:
1.猛一看
猛一看这个文件里声明了这几个类,基本与html的DOM中的概念一致。其提供的方法(有些方法名都一样)也是后者的阉割。
- Document,类似于html中document的概念
- Node,类似于逻辑树中的一个节点,可以用来遍历和查找。
- Element,继承自Node,其直接对应dom树中的一个节点
- Comment,继承自Node,就是weex的注释类型。
vdom的作用可能也就是迎来送往的作用,原生更新了视图后通知vdom或者js更新vdom后通知原生的作用。
2.Document
2.1 初始化
在初始化的代码中,主要是设置了id,url,instanceMap,并且初始化了监听器,最后调用了createDocumentElement方法。
export function Document (id, url, handler) {
id = id ? id.toString() : ''
this.id = id
this.URL = url
instanceMap[id] = this
this.nodeMap = {}
this.listener = new Listener(id, handler || genCallTasks(id))
this.createDocumentElement()
}
这里的nodeMap就类似下图中的索引,而下图中的树则是这个dom树
2.2 createDocumentElement
Document.prototype.createDocumentElement = function () {
if (!this.documentElement) {
const el = new Element('document')
el.docId = this.id
el.ownerDocument = this
el.role = 'documentElement'
el.depth = 0
el.ref = '_documentElement'
this.nodeMap._documentElement = el
this.documentElement = el
el.appendChild = (node) => {
appendBody(this, node)
}
el.insertBefore = (node, before) => {
appendBody(this, node, before)
}
}
return this.documentElement
}
我们可以看到,document也是Element的一种。只是document的role被设为了documentElement。笔者对下面的代码极为佩服:
el.appendChild = (node) => {
appendBody(this, node)
}
el.insertBefore = (node, before) => {
appendBody(this, node, before)
}
这段代码重写了原有的element的方法。。
2.3 appendBody
在appendBody方法中,
- 首先判断是否document是否已经有真子元素,如果有则不能使用这个方法。
- 插入子元素
if (documentElement.pureChildren.length > 0 || node.parentNode) {
return
}
const children = documentElement.children
const beforeIndex = children.indexOf(before)
if (beforeIndex < 0) {
children.push(node)
}
else {
children.splice(beforeIndex, 0, node)
}
判断node的角色是否为body,如果是,则设置这个节点的docid,ownerDocument, parentNode
if (node.role === 'body') {
node.docId = doc.id
node.ownerDocument = doc
node.parentNode = documentElement
}
如果节点的角色不是body,则把这个节点的子节点的parentNode 指向这个节点,然后设置document的body体,并且把node和document连接在一起。
else {
node.children.forEach(child => {
child.parentNode = node
})
setBody(doc, node)
node.docId = doc.id
node.ownerDocument = doc
linkParent(node, documentElement)
delete doc.nodeMap[node.nodeId]
}
delete doc.nodeMap[node.nodeId]
用来删除原有索引的值,因为可以通过_root得到原来的值。
- 最后设置nodeType!=1的情况,目前就是comment元素。
node.parentNode = documentElement
doc.nodeMap[node.ref] = node
2.4 fireEvent
fireEvent 用以激活元素的事件,以及此事件所带来的dom变化。
if (!el) {
return
}
e = e || {}
e.type = type
e.target = el
e.timestamp = Date.now()
if (domChanges) {
updateElement(el, domChanges)
}
return el.fireEvent(type, e)
主要是用来生成事件的一些参数,比如时间戳,然后更新节点,最后嗲用el的fireEvent方法。可以看到这个el是Element类型,domChanges是一个对象。type的值官方说是只有div,list和scroller,但是在这个dom中并没有这个值得定义。
再读读代码,发现特别像早期的从窗口处理机制-监听器模式。当dom有变动时,监听器会调用这个事件的处理器。
const handler = this.event[type]
if (handler) {
return handler.call(this, e)
}
下节将继续阅读vdom代码,并绘制vdom的示意图