🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
**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外的其他属性下的子节点。