MVVM试图更加清晰的讲用户界面(UI)开发从应用程序的业务逻辑与行为中心分离,因为,很多这样的模式的实现都需要利用声明式数据绑定来实现讲View(视图)工作从其他层分离

所以出现了一大堆自定义的声明式的语法:

如:Avalon

  1. 作用域绑定(ms-controller, ms-important)
  2. 模板绑定(ms-include)
  3. 数据填充(ms-text, ms-html)
  4. 类名切换(ms-class, ms-hover, ms-active)
  5. 事件绑定(ms-on,……)
  6. 显示绑定(ms-visible)
  7. 插入绑定(ms-if)
  8. 双工绑定(ms-duplex,原来的ms-model)
  9. 样式绑定(ms-css)
  10. 数据绑定(ms-data)
  11. 布尔属性绑定(ms-checked, ms-selected, ms-readonly, ms-disabled, ms-enabled)
  12. 字符串属性绑定(ms-title, ms-src, ms-href……)
  13. 万能属性绑定(ms-attr)
  14. 万能绑定(ms-bind)
  15. 数组循环绑定(ms-each)
  16. 对象循环绑定(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开头的自定义标签,还有{}插值表达式。。等等

声明1:

ms-controller="box"

avalon提供ms-controller, ms-important来指定VM在视图的作用范围。比如有两个VM,它们都有一个firstName属性,在DIV中,如果我们用 ms-controller="VM1", 那么对于DIV里面的{{firstName}}就会解析成VM1的firstName中的值。

声明2:

ms-css-width="w" ms-css-height="h"

用来处理样式

声明3:

ms-click="click"

avalon通过ms-on-click或ms-click进行事件绑定,并在IE对事件对象进行修复,并统一了所有浏览器对return false的处理

其实就是把部分的行为操作提升到了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个组成对象

  1. expr: true
  2. filters: undefined
  3. value: " w "
  1. expr: false
  2. value: " x "
  1. expr: true
  2. filters: undefined
  3. value: " h "

解析后分解成绑定的数据

然后就是一次循环了, 遇到条件stopScan就终止了

所以总结scan无非就干了一件事,扫描到指定的行为,发送数据给处理函数

前端MVVM框架avalon揭秘 - HTML编译器的更多相关文章

  1. 前端MVVM框架avalon揭秘 - 双向绑定原理

    avalon大家可能不熟悉,但是Knockout估计或多或少听过用过,那么说说KO的几个概念 监控属性(Observables)和依赖跟踪(Dependency tracking) 声明式绑定(Dec ...

  2. 前端MVVM框架avalon - 模型转换1

    轻量级前端MVVM框架avalon - 模型转换(一) 接上一章 ViewModel modelFactory工厂是如何加工用户定义的VM? 附源码 洋洋洒洒100多行内部是魔幻般的实现 1: fun ...

  3. 轻量级前端MVVM框架avalon - 初步接触

    迷你简单易用的MVVM框架 avalon的介绍http://rubylouvre.github.io/mvvm/ 按照作者的介绍,在HTML中添加绑定,在JS中用avalon.define定义View ...

  4. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  5. 轻量级前端MVVM框架avalon源码分析-总结

    距avalon0.7版本发布有一段时间,由于之前的稳定性,就停止一段时间更新,期间研究了下Knockout源码,也尝试写了一个小型的mvvm的实现模型,仅仅只是仿造ko的核心实现,把无关的东西给剥离掉 ...

  6. 轻量级前端MVVM框架avalon - 执行流程1

    基本上确定了avalon的几个重要元素的关系: M,即model,一个普通的JS对象,可能是后台传过来的,也可能是直接从VM中拿到,即VM.$json.有关的这个$json的名字还在商讨 V,即Vie ...

  7. 轻量级前端MVVM框架avalon - 整体架构

    官网提供架构图 单看这个图呢,还木有说明,感觉有点蛋疼,作者的抽象度太高了,还好在前面已经大概分析过了执行流程 如图 左边是View视图,我们就理解html结构,换句话就是说用户能看到的界面,渲染页面 ...

  8. 轻量级前端MVVM框架avalon - ViewModel

    废话说了大几篇,我们开始来点干货了~ ViewModel的内部机制 在MVVM中,数据是核心.而jQuery则以DOM为核心. 而DOM只是HTML在JS的世界的抽象,是一个很易变的东西.因此如果业务 ...

  9. 轻量级前端MVVM框架avalon - 控制器

    引子: 最近工作挺忙,avalon只能断断续续的写下去了,大概看了下angular的源码,看到小一半就比较难坚持了,是块硬骨头,慢慢啃吧 不过angular的的文档中用词还是很优雅: HTML编译器 ...

随机推荐

  1. 搜索引擎Query Rewrite

    中心词抽取项目总结 B2B国际站Query重写.ppt 达观数据搜索引擎的Query自动纠错技术和架构详解 Natural Language Processing Simrank++ Query re ...

  2. Ajax跨域问题的两种解决方法

    浏览器不允许Ajax跨站请求,所以存在Ajax跨域问题,目前主要有两种办法解决. 1.在请求页面上使用Access-Control-Allow-Origin标头. 使用如下标头可以接受全部网站请求: ...

  3. HDU 4801 Pocket Cube

    题目链接 去年现场,虎哥1Y的,现在刷刷题,找找状态... 一共6种转法,把3个面放到顶部,左旋和右旋,感觉写的还不错....都写成常数了. #include <stdio.h> #inc ...

  4. centos 创建swap 交换分区

    阿里云的服务器是没有交换分区的,如 [www-data@iZbp1ivdq1ie5lmrhp13kjZ ~]$ free -m total used free shared buff/cache av ...

  5. 基于注解的Spring AOP入门、增强Advice实例

    这篇文章简单通过一个例子,介绍几种增强的基本配置,以方便spring框架初学者对aop的代码结构有个清楚的了解认识.首先,spring支持aop编程,支持aspectJ的语法格式来表示切入点,切面,增 ...

  6. Dertouzos (5750)

    Dertouzos 题意: 就是给一个n和一个d,问有多少个小于n的数的最大因子是d. 分析: 如果一个数是质数,又和d互质,它们的乘积在范围内的话显然是满足条件的, 如果这个质数和d不互质,那么如果 ...

  7. win32进程名查找进程PID

    1. #include <Psapi.h> #pragma comment(lib, "Psapi.lib") DWORD GetProcIDFromName(LPCT ...

  8. centos7开机自动联网设置

    /etc/sysconfig/network-scripts/目录下ifcfg-eth0这个 文件,把ONBOOT="no"改为yes

  9. 10分钟学会前端调试利器——FireBug

    概述 FireBug是一个用于网站前端开发的工具,它是FireFox浏览器的一个扩展插件.它可以用于调试JavaScript.查看DOM.分析CSS.监控网络流量以及进行Ajax交互等.它提供了几乎前 ...

  10. python 根据现有文件树创建文件树

    # -*- coding: utf-8 -*- import os, errno def fileName(path):#获取文件夹 str = '' for i in range(1,len(pat ...