**1 文件源代码(18 scanAttr.modern.js)**
~~~
function scanAttr(elem, vmodels, match) {
var scanNode = true
if (vmodels.length) {
var attributes = elem.attributes
var bindings = []
var fixAttrs = []
var uniq = {}
var msData = createMap()
for (var i = 0, attr; attr = attributes[i++]; ) {
if (attr.specified) {
if (match = attr.name.match(rmsAttr)) {
var type = match[1]
var param = match[2] || ""
var value = attr.value
var name = attr.name
if (uniq[name]) {
continue
}
uniq[name] = 1
if (events[type]) {
param = type
type = "on"
} else if (obsoleteAttrs[type]) {
if (type === "enabled") {
log("warning!ms-enabled或ms-attr-enabled已经被废弃")
type = "disabled"
value = "!(" + value + ")"
}
param = type
type = "attr"
name = "ms-" + type + "-" + param
fixAttrs.push([attr.name, name, value])
}
msData[name] = value
if (typeof bindingHandlers[type] === "function") {
var newValue = value.replace(roneTime, "")
var oneTime = value !== newValue
var binding = {
type: type,
param: param,
element: elem,
name: name,
value: newValue,
oneTime: oneTime,
priority: (priorityMap[type] || type.charCodeAt(0) * 10) + (Number(param.replace(/\D/g, "")) || 0)
}
if (type === "html" || type === "text") {
var token = getToken(value)
avalon.mix(binding, token)
binding.filters = binding.filters.replace(rhasHtml, function () {
binding.type = "html"
binding.group = 1
return ""
})
} else if (type === "duplex") {
var hasDuplex = name
} else if (name === "ms-if-loop") {
binding.priority += 100
}
bindings.push(binding)
if (type === "widget") {
elem.msData = elem.msData || msData
}
}
}
}
}
if (bindings.length) {
bindings.sort(bindingSorter)
fixAttrs.forEach(function (arr) {
log("warning!请改用" + arr[1] + "代替" + arr[0] + "!")
elem.removeAttribute(arr[0])
elem.setAttribute(arr[1], arr[2])
})
var control = elem.type
if (control && hasDuplex) {
if (msData["ms-attr-value"] && elem.type === "text") {
log("warning!" + control + "控件不能同时定义ms-attr-value与" + hasDuplex)
}
}
for (i = 0; binding = bindings[i]; i++) {
type = binding.type
if (rnoscanAttrBinding.test(type)) {
return executeBindings(bindings.slice(0, i + 1), vmodels)
} else if (scanNode) {
scanNode = !rnoscanNodeBinding.test(type)
}
}
executeBindings(bindings, vmodels)
}
}
if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent)) {
mergeTextNodes && mergeTextNodes(elem)
scanNodeList(elem, vmodels)
}
}
var rnoscanAttrBinding = /^if|widget|repeat$/
var rnoscanNodeBinding = /^each|with|html|include$/
~~~
**2 文件分析**
>[info] scanAttr()是avalon的标签属性扫描处理接口
elem:扫描标签入口
vmodels:保存vm信息对象
match:扫描的属性内容
`var scanNode = true`
>[info]扫描节点控制,
> 用在each with html include等扫描属性控制中,
> 在下文的绑定过程中出现
`if (vmodels.length) `
>[info]这里做参数检测,vmodel是vm层对象,如果存在vmodels对象,才继续扫描,
`var attributes = elem.attributes`
>[info]获取所有待扫描属性。
`var bindings = []`
>[info]保存属性扫描后需要处理的绑定属性内容
`var fixAttrs = []`
>[info]保存属性扫描后需要修改的废弃属性内容
~~~
var uniq = {}
var msData = createMap()
~~~
>[info]扫描辅助数组
`for (var i = 0, attr; attr = attributes[i++]; )`
>[info] 遍历标签的属性节点
`if (attr.specified)`
>[info] attr.specified 检测attr是否定义
这里用作attr安全检测
`if (match = attr.name.match(rmsAttr))`
>[info]匹配rmsAttr
> 在scan.js中定义 var rmsAttr = /ms-(\w+)-?(.*)/
~~~
var type = match[1]
var param = match[2] || ""
~~~
>[info]分析表达式可知 type对应第一个参数,param对应第二个参数
> 如 ms-attr-value,ms-duplex-radio,ms-attr-cx,
> type分别为attr,duplex,attr,而param分别为value,radio,cx。
~~~
var value = attr.value
var name = attr.name
~~~
>[info] value对应attr的值
>[info] name对应attr的名称
如ms-class="readonly: aaa",value是readonly:aaa,name是ms-class。
~~~
if (uniq[name]) {
continue
}
uniq[name] = 1
~~~
>[info] 这段代码用来IE8下的bug修改,见源代码注释。
>[info] 下面根据ms-x中的第一个参数确定type值,分别处理不同类型的属性。
~~~
if (events[type]) {
param = type
type = "on"
}
~~~
>[info] 1 事件属性信息
这里events在scan.js中定义
>[info] var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
>[info] 主要是一些事件操作
>[info] param修改为事件类型,type统一修改为on指令处理
~~~
else if (obsoleteAttrs[type]) {
if (type === "enabled") {
type = "disabled"
value = "!(" + value + ")"
}
param = type
type = "attr"
name = "ms-" + type + "-" + param
fixAttrs.push([attr.name, name, value])
}
~~~
>[info] 2 废弃属性扫描控制
这里的obsoleteAttrs在scan.js中定义
>[info] var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")
>[info] 废弃属性扫描控制,提醒使用新接口
>[info] param修改为属性对应类型,type统一修改为attr指令处理 合成attr的name
>[info] 最后保存到fixAttrs数组,提醒修正。
`msData[name] = value`
>[info] 缓存name与value对应关系到msData
`if (typeof bindingHandlers[type] === "function")`
>[info]3 注册绑定处理接口
这里使用bindingHandler数组,检测vmodel中注册的绑定处理。
>[info] 根据avalon.js全文可知bindingHandlers保存了各种指令处理函数。
>[info] 如bindingHandlers["if"],bindingHandlers.data,bindingHandlers.text,bindingHandlers.html,bindingHandlers.on等等
>[info] 有关bindingHandlers的分析见框架工具的另:bindHandlers绑定接口
`var newValue = value.replace(roneTime, "")`
>[info] ronTime在scan.js中定义,var roneTime = /^\s*::/
>[info] 这里使用replace将其替换为空值,保存省略掉roneTime参数的属性值到newValue。
`var oneTime = value !== newValue`
>[info] 通过比较value和newValue值,保存到oneTime。
~~~
var binding = {
type: type,
param: param,
element: elem,
name: name,
value: newValue,
oneTime: oneTime,
priority: (priorityMap[type] || type.charCodeAt(0) * 10) + (Number(param.replace(/\D/g, "")) || 0)
}
~~~
>[info] 保存所有信息到binding中,包含绑定所需参数
~~~
if (type === "html" || type === "text") {
var token = getToken(value)
avalon.mix(binding, token)
binding.filters = binding.filters.replace(rhasHtml, function () {
binding.type = "html"
binding.group = 1
return ""
})
}
~~~
>[info] 这里对ms-html,ms-text处理
`var token = getToken(value)`
>[info] 使用getToken()解析attr.value内容
getToken()在scanText.js文件中定义
分析见主:标签文本扫描。
`avalon.mix(binding, token)`
>[info] 调用avalon.mix合并binding和token
avalon.mix是avalon的一个重要工具函数,分析见avalon全局函数。
~~~
binding.filters = binding.filters.replace(rhasHtml, function () {
binding.type = "html"
binding.group = 1
return ""
})
~~~
>[info]修正binding.filters属性,
> rhasHtml在scanText.js文件中定义
~~~
var rhasHtml = /\|\s*html(?:\b|$)/,
r11a = /\|\|/g,
rlt = /</g,
rgt = />/g,
rstringLiteral = /(['"])(\\\1|.)+?\1/g,
rline = /\r?\n/g
~~~
`else if (type === "duplex")`
>[info] 4 ms-duplex-x属性处理
`var hasDuplex = name`
>[info] 保存duplex的name到hasDuplex中。
`else if (name === "ms-if-loop")`
>[info] 5 ms-if-loop属性优先级处理
`if (type === "widget")`
>[info] 6 ms-widget属性内容处理
`elem.msData = elem.msData || msData`
>[info] 保存缓存的msData属性值到elem的msData中。
>[info] 上面一段代码完成了普通属性的扫描处理。
下面开始处理绑定过程
`if (bindings.length)`
>[info] 检查是否有需要进行处理的绑定内容
`bindings.sort(bindingSorter)`
>[info] 根据优先级排序bindings数组
>[info]bindingSorter在scan.js中定义
~~~
function bindingSorter(a, b) {
return a.priority - b.priority
}
~~~
>[info]bindingSorter在scan.js中定义,根据优先级排序。
~~~
fixAttrs.forEach(function (arr) {
log("warning!请改用" + arr[1] + "代替" + arr[0] + "!")
elem.removeAttribute(arr[0])
elem.setAttribute(arr[1], arr[2])
})
~~~
>[info] 这段代码修改fixAttrs数组中保存的废弃属性
> 首先移除arr[0],然后设置属性arr[1]为arr[2]。
> 根据上面的obsoleteAttrs数组中扫描处理可知
> fixAttrs.push([attr.name, name, value])
> 移除废弃属性,添加合成属性的值为value
~~~
if (hasDuplex && msData["ms-attr-value"] && !elem.scopeName && elem.type === "text") {
log("warning!一个控件不能同时定义ms-attr-value与" + hasDuplex)
}
~~~
>[info] 这里是对应ms-duplex中ms-attr-value的冲突问题
>[info] 上面进行了2个修正处理后,开始正式的绑定处理
`for (i = 0; binding = bindings[i]; i++) {}`
>[info] 遍历bindings数组,对待绑定属性进行处理
`type = binding.type`
>[info] 获取待绑定属性类型
`if (rnoscanAttrBinding.test(type)) {}`
>[info] 检查绑定属性是否为if,widget,repeat中的一种
> rnoscanAttrBinding在下面定义
~~~
var rnoscanAttrBinding = /^if|widget|repeat$/
~~~
>[info]如果是if,widget,repeat三类属性,
直接执行executeBindings()完成核心绑定过程,并返回
>[info]下面检查scanNode状态??
`scanNode = !rnoscanNodeBinding.test(type)`
>[info] 根据下面的rnoscanNodeBinding定义var rnoscanNodeBinding = /^each|with|html|include$/
因此scanNode实际上是除属性类型each,with,html,include的状态。
>[info] 到此再次处理除if,widget,repeat属性和each,with,htmlk,include外的其他属性的处理。
`executeBindings(bindings, vmodels)`
>[info] 核心绑定处理过程流程
分析见主:Bindings绑定处理过程
~~~
if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent))
~~~
>[info] 检查是否需要节点扫描处理,不需要扫描的是each,with,html,include等节点
> rbind定义在06 configuration.js中
`var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g`
`mergeTextNodes && mergeTextNodes(elem)`
>[info] 合并子节点内容
`scanNodeList(elem, vmodels)`
>[info] 开始进入子节点内容扫描。
扫描除each,with,html,include外的其他属性下的子节点。