**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外的其他屬性下的子節點。