[TOC]
* * * * *
## 1 源代码文件
### 1-1 解析实现
~~~
src\strategy\lexer.js
~~~
### 1-2 解析生成
~~~
src\vdom\
~~~
## 2 流程分析
### 2-1 节点内容解析
~~~
src\strategy\lexer.js
function lexer(text, recursive) {
// 所有生成节点数组
var nodes = []
// recursive参数使用 true 解析子节点时调用
if (recursive && !rbind.test(text)) {
return nodes
}
// recursive参数使用 false 文本数组存储??1,??2,这里实现嵌套
if (!recursive) {
text = text.replace(rstring, dig)
}
// 反复迭代解析
do {
// 初始化解析状态outerHTML,node
var outerHTML = ''
var node = false
//创建文本虚拟节点 VText
var match = text.match(rtext)
if (match) {
outerHTML = match[0]
node = new VText(outerHTML.replace(rfill, fill))
}
//创建注释虚拟节点 VComment
if (!node) {
match = text.match(rcomment)
if (match) {
outerHTML = match[0]
node = new VComment(match[1].replace(rfill, fill))
// 移除紧挨着<!--ms-for-end:xxxx-->前的空白节点
// 注释节点空白清除
var nodeValue = node.nodeValue
if (rspBeforeForEnd.test(nodeValue)) {
var sp = nodes[nodes.length - 1]
if (sp && sp.type === '#text' && rsp.test(sp.nodeValue)) {
nodes.pop()
}
}
}
}
//创建闭标签虚拟节点
if (!node) {
// 闭合节点
match = text.match(rfullTag)
if (match) {
//贪婪匹配 outerHTML,可能匹配过多
outerHTML = match[0]
//节点类型
var type = match[1].toLowerCase()
outerHTML = clipOuterHTML(outerHTML, type)
//节点属性
match = outerHTML.match(rvoidTag)
var props = {}
if (match[2]) {
handleProps(match[2], props)
}
//节点innerHTML
var innerHTML = outerHTML.slice(match[0].length,
(type.length + 3) * -1)
// 节点组织
node = {
type: type,
props: props,
template: innerHTML.replace(rfill, fill).trim(),
children: []
}
node = modifyProps(node, innerHTML, nodes)
}
}
//创建自闭合标签虚拟节点
if (!node) {
match = text.match(rvoidTag)
if (match) {
//
outerHTML = match[0]
// 节点类型
type = match[1].toLowerCase()
// 节点属性
props = {}
if (match[2]) {
handleProps(match[2], props)
}
// 节点结点
node = {
type: type,
props: props,
template: '',
children: [],
isVoidTag: true
}
modifyProps(node, '', nodes)
}
}
if (node) {
// 创建节点成功 保存到数组
nodes.push(node)
text = text.slice(outerHTML.length)
if (node.type === '#comment' && rspAfterForStart.test(node.nodeValue)) {
node.signature = makeHashCode('for')
//移除紧挨着<!--ms-for:xxxx-->后的空白节点
text = text.replace(rleftSp, '')
}
} else {
// 创建节点失败 直接跳出
break
}
} while (1);
// 非递归的情况
if (!recursive) {
maps = {}
}
return nodes
}
~~~
> text:待解析节点内容
> recursive:是否属于递归解析,节点内解析子节点
* * * * *
> 准备返回节点数组
> 检查recursive,是否属于子节点解析
> 循环解析主体
> 1 虚拟文本节点匹配解析Vtext
> 2 虚拟注释节点匹配解析VComment
> 3 双标签生成虚拟节点VElement
> 4 单标签生成虚拟节点VElement
> 5 元素节点解析的modifyProps()递归调用lexer()解析子节点
### 2-2 虚拟节点创建
~~~
var match = text.match(rtext)
node = new VText(outerHTML.replace(rfill, fill))
~~~
> 虚拟文本节点
~~~
match = text.match(rcomment)
node = new VComment(match[1].replace(rfill, fill))
~~~
> 虚拟注释节点
~~~
match = text.match(rfullTag)
node = modifyProps(node, innerHTML, nodes)
match = text.match(rvoidTag)
modifyProps(node, '', nodes)
~~~
> 虚拟元素节点
### 2-3 handleProps(str, props)属性操作
~~~
var ramp = /&/g
var rnowhite = /\S+/g
var rquote = /"/g
var rnogutter = /\s*=\s*/g
function handleProps(str, props) {
str.replace(rnogutter, '=').replace(rnowhite, function (el) {
var arr = el.split('='), value = arr[1] || '',
name = arr[0].toLowerCase()
if (arr.length === 2) {
if (value.indexOf('??') === 0) {
value = value.replace(rfill, fill).
slice(1, -1).
replace(ramp, '&').
replace(rquote, '"')
}
}
props[name] = value
})
}
~~~
### 2-4 modifyProps(node, innerHTML, nodes)元素节点修正与递归解析
~~~
function modifyProps(node, innerHTML, nodes) {
// 节点类型
var type = node.type
if (node.props['ms-skip']) {
// 包含ms-skip属性的节点
node.skipContent = true
} else {
// 非包含ms-skip属性的节点
// 根据节点属性操作
switch (type) {
case 'style':
case 'script':
case 'noscript':
case 'template':
case 'textarea':
node.skipContent = true
if (type === 'textarea') {
node.props.type = 'textarea'
}
break
case 'input':
if (!node.props.type) {
node.props.type = 'text'
}
case 'xmp':
node.children.push(new VText(node.template))
break
case 'option':
node.children.push(new VText(trimHTML(node.template)))
break
default:
// 递归解析子节点
if (!node.isVoidTag) {
var childs = lexer(innerHTML, true)
node.children = childs
// 如果是table节点
if (type === 'table') {
addTbody(node.children)
}
}
break
}
// for类型表达式
var forExpr = node.props['ms-for']
if (forExpr) {
nodes.push({
type: '#comment',
nodeValue: 'ms-for:' + forExpr,
signature: makeHashCode('for')
})
delete node.props['ms-for']
nodes.push(node)
node = {
type: '#comment',
nodeValue: 'ms-for-end:'
}
}
}
return node
}
~~~
### 2-5 clipOuterHTML(matchText, type) 截取OuterHTML
~~~
var openStr = '(?:\\s+[^>=]*?(?:=[^>]+?)?)*>'
var tagCache = {}// 缓存所有匹配开标签闭标签的正则
var rchar = /./g
var regArgs = avalon.msie < 9 ? 'ig' : 'g'//IE6-8,标签名都是大写
function clipOuterHTML(matchText, type) {
var opens = []
var closes = []
// 开标签与闭标签的缓存
var ropen = tagCache[type + 'open'] ||
(tagCache[type + 'open'] = new RegExp('<' + type + openStr, regArgs))
var rclose = tagCache[type + 'close'] ||
(tagCache[type + 'close'] = new RegExp('<\/' + type + '>', regArgs))
/* jshint ignore:start */
//注意,页面有时很长,b的数值就很大,如
//000000000<000000011>000000041<000000066>000000096<000000107>
matchText.replace(ropen, function (_, b) {
//取得所有开标签的位置
opens.push(('0000000000' + b + '<').slice(-10))
return _.replace(rchar, '1')
}).replace(rclose, function (_, b) {
//取得所有闭标签的位置
closes.push(('0000000000' + b + '>').slice(-10))
})
/* jshint ignore:end */
/*<div>
<div>01</div>
<div>02</div>
</div>
<div>222</div>
<div>333</div>
会变成
000<
005<
012>
018<
025>
031>
037<
045>
051<
059>
再变成
<<><>>
<>
<>
最后获取正确的>的索引值,这里为<<><>>的最后一个字符,*/
var pos = opens.concat(closes).sort()
var gtlt = pos.join('').replace(rnumber, '')
var k = 0, last = 0
for (var i = 0, n = gtlt.length; i < n; i++) {
var c = gtlt.charAt(i)
if (c === '<') {
k += 1
} else {
k -= 1
}
if (k === 0) {
last = i
break
}
}
// (</>为三个字符)
var findex = parseFloat(pos[last]) + type.length + 3
//取得正确的outerHTML
return matchText.slice(0, findex)
}
~~~
### 2-7 addTbody(nodes) table修正
~~~
function addTbody(nodes) {
var tbody, needAddTbody = false, count = 0, start = 0, n = nodes.length
for (var i = 0; i < n; i++) {
var node = nodes[i]
if (!tbody) {
if (node.type === 'tr') {
tbody = {
type: 'tbody',
template: '',
children: [],
props: {}
}
tbody.children.push(node)
needAddTbody = true
if (start === 0)
start = i
nodes[i] = tbody
}
} else {
if (node.type !== 'tr' && node.type.charAt(0) !== "#") {
tbody = false
} else {
tbody.children.push(node)
count++
nodes[i] = 0
}
}
}
if (needAddTbody) {
for (i = start; i < n; i++) {
if (nodes[i] === 0) {
nodes.splice(i, 1)
i--
count--
if (count === 0) {
break
}
}
}
}
}
~~~
## 4 总结
### 4-1 意义
解析dom内容为虚拟dom
### 4-2 思路
递归调用lexer()解析节点
### 4-3 其他操作
[附:虚拟DOM](http://www.kancloud.cn/zmwtp/avalon2/137892)
- 概述
- 框架目录
- 组件目录(components\)
- 生成目录(dist\)
- 测试目录(karma\)
- 示例目录(perf\)
- 主体目录(src)
- 其他文件
- 框架流程
- 前:章节说明
- 主:模板扫描(avalon.scan())
- 主:VM创建(avalon.define())
- 主:同步刷新(avalon.batch())
- 附:节点解析(avalon.lexer())
- 附:虚拟DOM(avalon.vdomAdaptor())
- 附:渲染函数(avalon.render())
- 附:VM生成(avalon.masterFactory())
- 附:节点diff(avalon.diff())
- 主:界面事件(test)
- 框架工具
- 另:全局函数
- 另:全局正则
- 另:事件接口
- 另:组件接口
- 另:DOMApi
- 框架驱动
- D : 指令实现
- D:兼容处理
- 使用范例
- 基础原理
- js模块
- js对象
- js函数
- js数组
- js字符串
- dom接口
- 框架心得
- 心:总体思路