ANode 全名抽象节点,是 San 组件框架 template 解析的返回结果。本文档对 ANode 进行说明。
template 简述
插值语法
普通属性语法
双向绑定语法
指令语法
表达式
表达式类型
STRING
NUMBER
BOOL
ACCESSOR
INTERP
CALL
TEXT
BINARY
UNARY
TERTIARY
ANode 与相关类型结构
ANode
IndexedList
模板解析结果
文本
属性
双向绑定
复杂的插值
事件绑定
条件指令
循环指令
在 San 中,template 是一个符合 HTML 语法规则的字符串。在 template 中,数据绑定与事件的声明通过以下形式:
文本中通过 {{...}}
声明插值,插值内部支持表达式和过滤器的声明。
插值语法
{{ expr [[| filter-call1] | filter-call2...] }}
示例
<p>Hello {{name}}!</p>
属性内部可以出现插值语法。
示例
<span title="This is {{name}}">{{name}}</span>
template 解析阶段无法预知当前节点将会渲染成一个普通元素还是一个复杂组件,所以,将属性全部处理为是绑定表达式:
- 复杂形式的值,处理成字符串表达式。如
title="This is {{name}}"
- 只包含单一插值,处理成插值表达式。如
title="{{name}}"
San 认为 template 应该尽量保持 HTML 的语法简洁性,所以双向绑定方式在属性的值上做文章:属性值形式为 {= expression =}
的认为是双向绑定。
示例
<input type="text" value="{= name =}">
双向绑定仅支持普通变量和属性访问表达式。
以 san-
为前缀的属性,将被解析成指令。常见的指令有 for、if 等。
示例
<span san-if="isOnline">Hello!</span>
<span san-else>Offline</span>
<dl>
<dt>name - email</dt>
<dd san-for="p in persons" title="{{p.name}}">{{p.name}}({{dept}}) - {{p.email}}</dd>
</dl>
San 的 template 支持多种形式的表达式,表达式信息在 template 解析过程中会被解析并以 Object 格式保存在 ANode 中。下面是一个简单字符串表达式的信息形式:
exprInfo = {
"type": 1,
"value": "hello"
}
本章对保存在 ANode 中的表达式信息进行相应说明。在下面的描述中,用 exprInfo
代替表达式信息对象。
从源码中下面枚举类型的声明,可以看出 San 支持的表达式类型。
var ExprType = {
STRING: 1,
NUMBER: 2,
BOOL: 3,
ACCESSOR: 4,
INTERP: 5,
CALL: 6,
TEXT: 7,
BINARY: 8,
UNARY: 9,
TERTIARY: 10
};
exprInfo 中必须包含 type 属性,值为上面类型值之一。下面不再对 type 属性赘述。
字符串字面量
// value - 字符串的值
exprInfo = {
type: ExprType.STRING,
value: '你好'
}
数值字面量
// value - 数值的值
exprInfo = {
type: ExprType.NUMBER,
value: 123.456
}
布尔字面量
// value - 数值的值
exprInfo = {
type: ExprType.BOOL,
value: true
}
数据访问表达式,比如 a
/ a.b.c
/ a[index]
,代表对一个数据项的引用
// paths - 属性路径。数组,里面每一项是一个表达式对象
exprInfo = {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'user'},
{type: ExprType.STRING, value: 'phones'},
{
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'DefaultConfig'},
{type: ExprType.STRING, value: 'PHONE-INDEX'}
]
}
]
}
插值。解析器为了方便解析和求值,将插值看成一种表达式
// expr - 数据访问部分表达式信息,一个表达式对象
// filters - 过滤器部分信息。数组,其中每一项是一个 CALL 表达式对象
exprInfo = {
type: ExprType.INTERP,
expr: {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'user'},
{type: ExprType.STRING, value: 'phones'}
]
},
filters: [
{
type: ExprType.CALL,
name: 'comma',
args: [
{type: ExprType.NUMBER, literal: '3'}
]
}
]
}
调用表达式,表示对方法或过滤器的调用。调用表达式一般出现在插值的过滤器列表,或事件绑定信息中。
// name - 调用方法名。字符串
// args - 调用参数列表。数组,其中每一项是一个表达式对象
exprInfo = {
type: ExprType.CALL,
name: 'comma',
args: [
{type: ExprType.NUMBER, literal: '3'}
]
}
文本。文本是一段由静态字符串和插值表达式组成的复杂内容,通常用于 text 节点与属性绑定。
// segs - 文本组成片段。数组,其中每一项是一个 STRING 或 INTERP表达式对象
exprInfo = {
type: ExprType.TEXT,
segs: [
{type: ExprType.STRING, value: 'Hello '},
{
type: ExprType.INTERP,
expr: {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'whoAmI'}
]
},
filters: []
},
{type: ExprType.STRING, value: '!'}
]
}
二元表达式,支持多种计算和比较,包括 + | - | * | / | && | || | == | != | === | !== | > | >= | < | <=
// operator - 操作符。数值,值为操作符各个 char 的 ascii 之和。比如 == 操作符的 operator 为 61 + 61 = 122
// segs - 包含两个表达式对象的数组
exprInfo = {
type: ExprType.BINARY,
segs: [
{
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'commaLength'}
]
},
{
type: ExprType.NUMBER,
literal: "1"
}
],
operator: 43
}
一元表达式,其实现在就只支持 !
的逻辑否定。
exprInfo = {
type: ExprType.UNARY,
expr: {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'user'},
{type: ExprType.STRING, value: 'isLogin'}
]
}
}
三元表达式,其实就是 conditional ? yes-expr : no-expr
的条件表达式。
// segs - 包含3个表达式对象的数组,第一个是条件表达式,第二个是值为真时的表达式,第三个是值为假时的表达式
exprInfo = {
type: ExprType.TERTIARY,
segs: [
{
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'user'},
{type: ExprType.STRING, value: 'isLogin'}
]
},
{
type: ExprType.STRING,
value: 'yes'
},
{
type: ExprType.STRING,
value: 'no'
}
]
}
此处只说明解析完成返回结果中可能被访问的实例类型。解析过程中用到的 Walker 等类不做说明。
template 的 parse 直接返回一个 ANode 类的实例。实例上不包含任何方法,只有属性。
标识当前节点是否为文本节点
文本节点的文本内容。当 isText 为 true 时有效
文本节点的表达式信息对象。当 isText 为 true 时有效,一定是一个 TEXT 表达式
ANode 的结构与 HTML 一样,是一个树状结构。childs 是当前节点的子节点列表。文本节点该属性无效
节点的属性绑定信息。文本节点该属性无效
// 获取 title 属性绑定信息。该信息是一个表达式
aNode.props.get('title');
// 遍历所有绑定属性
aNode.props.each(function (bindItem) {
});
节点的事件绑定信息。文本节点该属性无效
// 获取 click 事件绑定信息。该信息是一个表达式
aNode.events.get('click');
// 遍历所有绑定属性
aNode.events.each(function (eventItem) {
});
节点的指令绑定信息。文本节点该属性无效
// 获取 if 指令信息。该信息是一个特定的指令信息对象
aNode.directives.get('if');
节点的标签名。文本节点该属性无效
IndexedList 是一个索引列表,添加到列表中的 item,能根据 item 的 name 属性进行索引。IndexedList 提供了一些常用的集合操作的方法。
var list = new IndexedList();
list.push({name: 'test', text: 'hello'});
// console log {name: 'test', text: 'hello'}
console.log(list.get('test'));
ANode 的 props、events、directives 属性因为需要较频繁的根据 name 访问,以及遍历操作,直接使用 Array 或 Object 都会存在弊端,故使用 IndexedList。
在列表中添加一个 item。
遍历整个索引列表
根据顺序下标获取 item
根据 name 获取 item
根据顺序下标移除 item
根据 name 移除 item
连接另外一个 IndexedList,返回一个新的 IndexedList
模板解析的返回结果是一个标签节点的 ANode 实例,实例中 childs
包含节点结构、props
包含属性绑定信息、events
包含事件绑定信息、directives
包含指令信息、tagName
为节点标签名。
本章节通过一些示例说明模板解析的 ANode 结果。其中表达式信息的详细说明请参考 表达式 章节,ANode 实例结构请参考 ANode 与相关类型结构 章节。
为方便表示,本章节所有示例,props
、events
、directives
信息全部表示为数组形式,实际上应该是 IndexedList 类型的实例。
文本节点作为 p 标签的子节点存在。
<p>Hello {{name}}!</p>
aNode = {
"directives": [],
"props": [],
"events": [],
"childs": [
{
"isText": true,
"text": "Hello {{name}}!",
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.STRING,
"value": "Hello "
},
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"filters": []
}
]
}
}
],
"tagName": "p"
}
属性信息是一个 绑定信息对象
,其中:
- name - 属性名
- expr - 表达式信息对象
下面例子的 title 属性绑定到一个 TEXT 类型的表达式中。
<span title="This is {{name}}">{{name}}</span>
aNode = {
"directives": [],
"props": [
{
"name": "title",
"expr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.STRING,
"value": "This is "
},
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"filters": []
}
]
},
"raw": "This is {{name}}"
}
],
"events": [],
"childs": [
{
"isText": true,
"text": "click here",
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"filters": []
}
]
}
}
],
"tagName": "span"
}
双向绑定的属性,绑定信息对象上包含 x 属性,值为 true。
<input type="text" value="{= name =}">
aNode = {
"directives": [],
"props": [
{
"name": "type",
"expr": {
"type": ExprType.STRING,
"value": "text"
},
"raw": "text"
},
{
"name": "value",
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"x": 1
}
],
"events": [],
"childs": [],
"tagName": "input"
}
<p title="result: {{(var1 - var2) / var3 + 'static text' | comma(commaLength + 1)}}"></p>
"directives": [],
"props": [
{
"name": "title",
"expr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.STRING,
"value": "result: "
},
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.BINARY,
"segs": [
{
"type": ExprType.BINARY,
"segs": [
{
"type": ExprType.BINARY,
"segs": [
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "var1"
}
]
},
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "var2"
}
]
}
],
"operator": 45
},
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "var3"
}
]
}
],
"operator": 47
},
{
"type": 1,
"value": "static text"
}
],
"operator": 43
},
"filters": [
{
"type": ExprType.CALL,
"name": "comma",
"args": [
{
"type": ExprType.BINARY,
"segs": [
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "commaLength"
}
]
},
{
"type": 2,
"value": 1
}
],
"operator": 43
}
]
}
]
}
]
},
"raw": "result: {{(var1 - var2) / var3 + 'static text' | comma(commaLength + 1)}}"
}
],
"events": [],
"childs": [],
"tagName": "p"
}
事件绑定信息与属性绑定信息类似,但是事件绑定信息对象的 expr 属性一定是一个 CALL 表达式的表示。
<button type="button" on-click="clicker($event)">click here</button>
aNode = {
"directives": [],
"props": [
{
"name": "type",
"expr": {
"type": ExprType.STRING,
"value": "button"
},
"raw": "button"
}
],
"events": [
{
"name": "click",
"expr": {
"type": ExprType.CALL,
"name": "clicker",
"args": [
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "$event"
}
]
}
]
}
}
],
"childs": [
{
"isText": true,
"text": "click here",
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{"type": ExprType.STRING, "value": "click here"}
]
}
}
],
"tagName": "button"
}
if 指令的值是一个表达式信息对象,else 指令的值永远等于 true。
<div>
<span san-if="isOnline">Hello!</span>
<span san-else>Offline</span>
</div>
aNode = {
"directives": [],
"props": [],
"events": [],
"childs": [
{
"directives": [
{
"value": {
"type": ExprType.ACCESSOR,
"paths": [
{type: ExprType.STRING, value: "isOnline"}
]
},
"name": "if"
}
],
"props": [],
"events": [],
"childs": [
{
"isText": true,
"text": "Hello!",
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{"type": ExprType.STRING, "value": "Hello!"}
]
}
}
],
"tagName": "span"
},
{
"directives": [
{
"value": true,
"name": "else"
}
],
"props": [],
"events": [],
"childs": [
{
"isText": true,
"text": "Offline",
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{"type": ExprType.STRING, "value": "Offline"}
]
}
}
],
"tagName": "span"
}
],
"tagName": "div"
}
循环指令对象的信息包括:
- item - 表达式对象,表示循环过程中数据项对应的变量
- index - 表达式对象,表示循环过程中数据索引对应的变量
- list - 表达式对象,表示要循环的数据
- name - 恒为 for
<ul>
<li san-for="p, index in persons">{{p.name}} - {{p.email}}</li>
</ul>
aNode = {
"directives": [],
"props": [],
"events": [],
"childs": [
{
"isText": true,
"text": "\n "
},
{
"directives": [
{
"item": {
type: ExprType.ACCESSOR,
paths: [
{"type": ExprType.STRING, "value": "p"}
]
},
"index": {
type: ExprType.ACCESSOR,
paths: [
{"type": ExprType.STRING, "value": "index"}
]
}
"list": {
type: ExprType.ACCESSOR,
paths: [
{"type": ExprType.STRING, "value": "persons"}
]
},
"name": "for"
}
],
"props": [],
"events": [],
"childs": [
{
"isText": true,
"text": "{{p.name}} - {{p.email}}",
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{"type": ExprType.STRING, "value": "p"},
{"type": ExprType.STRING, "value": "name"}
]
},
"filters": []
},
{
"type": ExprType.STRING,
"value": " - "
},
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{"type": ExprType.STRING, "value": "p"},
{"type": ExprType.STRING, "value": "email"}
]
},
"filters": []
}
]
}
}
],
"tagName": "li"
},
{
"isText": true,
"text": "\n"
}
],
"tagName": "ul"
}