MVVM試圖更加清晰的講用戶界面(UI)開發從應用程序的業務邏輯與行爲中心分離,由於,不少這樣的模式的實現都須要利用聲明式數據綁定來實現講View(視圖)工做從其餘層分離css
因此出現了一大堆自定義的聲明式的語法:html
如:Avalonnode
- 做用域綁定(ms-controller, ms-important)
- 模板綁定(ms-include)
- 數據填充(ms-text, ms-html)
- 類名切換(ms-class, ms-hover, ms-active)
- 事件綁定(ms-on,……)
- 顯示綁定(ms-visible)
- 插入綁定(ms-if)
- 雙工綁定(ms-duplex,原來的ms-model)
- 樣式綁定(ms-css)
- 數據綁定(ms-data)
- 布爾屬性綁定(ms-checked, ms-selected, ms-readonly, ms-disabled, ms-enabled)
- 字符串屬性綁定(ms-title, ms-src, ms-href……)
- 萬能屬性綁定(ms-attr)
- 萬能綁定(ms-bind)
- 數組循環綁定(ms-each)
- 對象循環綁定(ms-with)
等等………
顧名思義,自定義聲明語法,那麼遊覽器自己是不能識別的,那麼如何遊覽器能過識別自定義的HTML語法,它能讓你講行爲關係到HTML元素或者屬性上,甚至能讓你創造具備自定義行爲的新元素呢,咱們暫且講這個過程稱之爲「HTML編譯」吧。數組
咱們先看一段HTML代碼瀏覽器
<div id='box' ms-controller="box">
<div style=" background: #a9ea00;" ms-css-width="w" ms-css-height="h" ms-click="click"></div>
<p>{{ w }} x {{ h }}</p>
<p>W: <input type="text" ms-model="w" data-event="change"/></p>
<p>H: <input type="text" ms-model="h" /></p>
</div>
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.scan vmodels = 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無非就幹了一件事,掃描到指定的行爲,發送數據給處理函數