MVVM试图更加清晰的讲用户界面(UI)开发从应用程序的业务逻辑与行为中心分离,由于,不少这样的模式的实现都须要利用声明式数据绑定来实现讲View(视图)工做从其余层分离css
因此出现了一大堆自定义的声明式的语法:html
如:Avalonnode
顾名思义,自定义声明语法,那么游览器自己是不能识别的,那么如何游览器能过识别自定义的HTML语法,它能让你讲行为关系到HTML元素或者属性上,甚至能让你创造具备自定义行为的新元素呢,咱们暂且讲这个过程称之为“HTML编译”吧。数组
咱们先看一段HTML代码浏览器
{{ w }} x {{ h }}
W:
H:
avalon.define("box", function(vm) {
vm.w = 100;
vm.h = 100;
vm.click = function() {
vm.w = parseFloat(vm.w) + 10;
vm.h = parseFloat(vm.h) + 10;
}
})
avalon.scan(document.getElementById('box'));
HTML结构中充斥了大量的ms开头的自定义标签,还有{}插值表达式。。等等ruby
声明1:
ms-controller="box"
avalon提供ms-controller, ms-important来指定VM在视图的做用范围。好比有两个VM,它们都有一个firstName属性,在DIV中,若是咱们用 ms-controller="VM1", 那么对于DIV里面的{{firstName}}就会解析成VM1的firstName中的值。app
声明2:
ms-css-width="w" ms-css-height="h"
用来处理样式框架
声明3:
ms-click="click"
avalon经过ms-on-click或ms-click进行事件绑定,并在IE对事件对象进行修复,并统一了全部浏览器对return false的处理dom
其实就是把部分的行为操做提高到了dom上了,而后有框架在后台给你处理好,经过加入各类自定义的属性可让任何的HTML元素都实现这样的行为函数
具体看源码的执行流程:
总的来讲就是匹配每一给节点上的属性,经过匹配分配到指定的bindingHandlers处理函数上,以后的处理本章不暂时不涉及
//扫描入口
avalon.scan = function(elem, vmodel)
//扫描子节点
function scanNodes(parent, vmodels, callback)
//开始扫描
function scanTag(elem, vmodels)
//扫描文本
function scanText(textNode, vmodels)
//扫描表达式
function scanExpr(str)
//扫描属性节点
function scanAttr(el, vmodels)
//抽取绑定
function executeBindings(bindings, vmodels)
//抽取文本绑定
function extractTextBindings(textNode)
看看命名就大概能猜出函数的做用了
1.入口函数 avalon.scan
avalon.scanavalon.scan = function(elem, vmodel) {
elem = elem || root
var vmodels = vmodel ? [].concat(vmodel) : []
scanTag(elem, vmodels)
}
默认从文本的根documentElement开始,若是传递了第一个elem,那么就是指定了扫描的做用域了,相似 jQuery( selector, [ context ] )
2. 执行扫描 scanTag
avalon.scanvmodels = vmodels || []
var a = elem.getAttribute(prefix + "skip")
var b = elem.getAttribute(prefix + "important")
var c = elem.getAttribute(prefix + "controller")
//这三个绑定优先处理,其中a > b > c
if (typeof a === "string") {
return
} else if (b) {
if (!VMODELS[b]) {
return
} else {
vmodels = [VMODELS[b]]
elem.removeAttribute(prefix + "important")
}
} else if (c) {
var newVmodel = VMODELS[c]
if (!newVmodel) {
return
}
vmodels = [newVmodel].concat(vmodels)
elem.removeAttribute(prefix + "controller")
}
scanAttr(elem, vmodels) //扫描特性节点
if (!stopScan[elem.tagName.toLowerCase()] && rbind.test(elem.innerHTML)) {
scanNodes(elem, vmodels)
}
依次要检测是当前元素上是否有ms-skip,ms-important,ms-controller属性,用于最开始的处理
若是ms-controller存在就取出vm视图模型对象
清除这个自定义属性
执行sacnAttr 属性扫描
3. 扫描属性节点 scanAttr
avalon.scanfunction scanAttr(el, vmodels) {
var bindings = []
for (var i = 0, attr; attr = el.attributes[i++]; ) { 1
if (attr.specified) { 2
var isBinding = false
if (attr.name.indexOf(prefix) !== -1) { 3
//若是是以指定前缀命名的
var type = attr.name.replace(prefix, "")
if (type.indexOf("-") > 0) { 4
var args = type.split("-")
type = args.shift()
}
isBinding = typeof bindingHandlers[type] === "function" 5
}
if (isBinding) {
bindings.push({ 6
type: type,
args: args || [],
element: el,
remove: true,
node: attr,
value: attr.nodeValue
})
}
}
}
executeBindings(bindings, vmodels)
}
attributes 属性返回包含被选节点属性的 NamedNodeMap。
若是在文档中设置了属性值,则 specified 属性返回 true.
是不是avalon的HTML指示 "ms-"开头
若是还指定了参数
能找到对应的处理函数
bindings 保存参数
4. 执行绑定 executeBindings
avalon.scanfunction executeBindings(bindings, vmodels) {
bindings.forEach(function(data) {
var flag = bindingHandlers[data.type](data, vmodels)
if (flag !== false && data.remove) { //移除数据绑定,防止被二次解析
data.element.removeAttribute(data.node.name)
}
})
}
找到对应的类型的bindingHandlers方法,传入数据与vm对象,实现处理
移除数据绑定,防止被二次解析
5. 扫描子节点 scanNodes
avalon.scanfunction scanNodes(parent, vmodels, callback) {
var nodes = aslice.call(parent.childNodes);
callback && callback();
for (var i = 0, node; node = nodes[i++]; ) {
if (node.nodeType === 1) {
scanTag(node, vmodels) //扫描元素节点
} else if (node.nodeType === 3) {
scanText(node, vmodels) //扫描文本节点
}
}
}
其实就循环处理子节点列表了,注意要过滤空文本类型
若是是元素节点就递归循环scanTag方法
若是是文本节点就scanText
6. 扫描文本 scanText
avalon.scanfunction scanText(textNode, vmodels) {
var bindings = extractTextBindings(textNode)
if (bindings.length) {
executeBindings(bindings, vmodels)
}
}
7.抽出文本绑定 extractTextBindings
avalon.scanfunction extractTextBindings(textNode) {
var bindings = [],
tokens = scanExpr(textNode.nodeValue)//分解表达式
if (tokens.length) {
while (tokens.length) { //将文本转换为文本节点,并替换原来的文本节点
var token = tokens.shift()
var node = DOC.createTextNode(token.value)
if (token.expr) { //若是分解的是表达式
var filters = token.filters
var binding = {
type: "text",
node: node,
args: [],
element: textNode.parentNode,
value: token.value,
filters: filters
}
if (filters && filters.indexOf("html") !== -1) {
avalon.Array.remove(filters, "html")
binding.type = "html"
binding.replaceNodes = [node]
}
bindings.push(binding) //收集带有插值表达式的文本
}
documentFragment.appendChild(node)
}
textNode.parentNode.replaceChild(documentFragment, textNode)
}
return bindings
}
文本解析是个比较复杂的东西,能够匹配不少种状况,因此须要加入不少解析的规则
scanExpr 就是扫描的表达式的匹配
documentFragment 先把这个结构让到文档碎片中,性能处理
8. 表达式匹配scanExpr
avalon.scanfunction scanExpr(str) {
var tokens = [],
value, start = 0,
stop
if (rexpr.test(str)) {
do {
var stop = str.indexOf(openTag, start)
if (stop === -1) {
break
}
value = str.slice(start, stop)
if (value) { // {{ 左边的文本
tokens.push({
value: value,
expr: false
})
}
start = stop + openTag.length
stop = str.indexOf(closeTag, start)
if (stop === -1) {
break
}
value = str.slice(start, stop)
if (value) { //{{ }} 之间的表达式
var leach = []
if (value.indexOf("|") > 0) { // 注意排除短路与
value = value.replace(rfilters, function(c, d, e) {
leach.push(d + (e || ""))
return c.charAt(0)
})
}
tokens.push({
value: value,
expr: true,
filters: leach.length ? leach : void 0
})
}
start = stop + closeTag.length;
} while (1);
value = str.slice(start);
if (value) { //}} 右边的文本
tokens.push({
value: value,
expr: false
})
}
}
return tokens
}
代码很长,可是处理的东西确很简单的
好比:
"{{ w }} x {{ h }}" 一个插值表达式,那么应该如何解析
分析这个表达式,解析能够分三块
1. {{ w }}
2 x
3 {{ h }}
左右两边都是vm视图全部关联的属性,中间x就是求值
那么解析的规则,分解3个部分,组成处理数据
tokens 就有3个组成对象
expr: true
filters: undefined
value: " w "
expr: false
value: " x "
expr: true
filters: undefined
value: " h "
解析后分解成绑定的数据
而后就是一次循环了, 遇到条件stopScan就终止了
因此总结scan无非就干了一件事,扫描到指定的行为,发送数据给处理函数