ANode 参考
template 简述
在 San 中,template 是一个符合 HTML 语法规则的字符串。在 template 中,数据绑定与事件的声明通过以下形式:
插值语法
文本中通过 {{...}}
声明插值,插值内部支持表达式和过滤器的声明。
插值语法
{{ expr [[| filter-call1] | filter-call2...] }}
示例
<p>Hello {{name}}!</p>
普通属性语法
属性内部可以出现插值语法。
示例
<span title="This is {{name}}">{{name}}</span>
属性声明根据不同形式,处理成不同的绑定表达式:
- 复杂形式的值,处理成TEXT表达式。如
title="This is {{name}}"
- 只包含单一插值,并且无 filter 时,插值内部的表达式会被抽取出来。如
title="{{name}}"
双向绑定语法
San 认为 template 应该尽量保持 HTML 的语法简洁性,所以双向绑定方式在属性的值上做文章:属性值形式为 {= expression =}
的认为是双向绑定。
示例
<input type="text" value="{= name =}">
双向绑定仅支持普通变量和属性访问表达式。
指令语法
以 s-
为前缀的属性,将被解析成指令。常见的指令有 for、if 等。
示例
<span s-if="isOnline">Hello!</span>
<span s-else>Offline</span>
<dl>
<dt>name - email</dt>
<dd s-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,
ARRAY: 11,
OBJECT: 12
};
exprInfo 中必须包含 type 属性,值为上面类型值之一。下面不再对 type 属性赘述。
STRING
字符串字面量
// value - 字符串的值
exprInfo = {
type: ExprType.STRING,
value: '你好'
}
NUMBER
数值字面量
// value - 数值的值
exprInfo = {
type: ExprType.NUMBER,
value: 123.456
}
BOOL
布尔字面量
// value - 数值的值
exprInfo = {
type: ExprType.BOOL,
value: true
}
ACCESSOR
数据访问表达式,比如 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'}
]
}
]
}
INTERP
插值。解析器为了方便解析和求值,将插值看成一种表达式
// 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: {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'comma'}
]
},
args: [
{type: ExprType.NUMBER, literal: '3'}
]
}
]
}
CALL
调用表达式,表示对方法或过滤器的调用。调用表达式一般出现在插值的过滤器列表,或事件绑定信息中。
// name - 调用方法名。字符串
// args - 调用参数列表。数组,其中每一项是一个表达式对象
exprInfo = {
type: ExprType.CALL,
name: {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'comma'}
]
},
args: [
{type: ExprType.NUMBER, literal: '3'}
]
}
TEXT
文本。文本是一段由静态字符串和插值表达式组成的复杂内容,通常用于 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: '!'}
]
}
BINARY
二元表达式,支持多种计算和比较,包括 + | - | * | / | && | || | == | != | === | !== | > | >= | < | <=
// 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
}
UNARY
一元表达式,支持:
!
逻辑否定-
取负+
转换成数值
// operator - 操作符。数值,值为操作符 char 的 ascii。
exprInfo = {
type: ExprType.UNARY,
expr: {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'user'},
{type: ExprType.STRING, value: 'isLogin'}
]
},
operator: 33
}
TERTIARY
三元表达式,其实就是 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'
}
]
}
ARRAY LITERAL
数组字面量,支持数组展开。
// [name, 'text', ...ext, true]
// items - 数组项列表。expr 为数组项表达式,spread 代表是否为展开项
exprInfo = {
type: ExprType.ARRAY,
items: [
{
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
}
},
{
"expr": {
"type": ExprType.STRING,
"value": "text"
}
},
{
"spread": true,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "ext"
}
]
}
},
{
"expr": {
"type": ExprType.BOOL,
"value": true
}
}
]
}
OBJECT LITERAL
对象字面量,支持对象展开。
// {name: realName, email, ...ext}
// items - 对象项列表。name 为项 name 表达式, expr 为项 value 表达式,spread 代表是否为展开项
exprInfo = {
"type": ExprType.OBJECT,
"items": [
{
"name": {
"type": ExprType.STRING,
"value": "name"
},
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "realName"
}
]
}
},
{
"name": {
"type": ExprType.STRING,
"value": "email"
},
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "email"
}
]
}
},
{
"spread": true,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "ext"
}
]
}
}
]
}
PARENTHESIZED
括号表达式不会生成独立的表达式对象。被括号包含的表达式,在其对象上有一个 parenthesized
属性,值为 true
。
// (a + b) * c
// a + b 的表达式对象上包含 parenthesized 属性,值为 true
exprInfo = {
type: ExprType.BINARY,
segs: [
{
type: ExprType.BINARY,
parenthesized: true,
segs: [
{
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'a'}
]
},
{
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'b'}
]
}
],
operator: 43
},
{
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: 'c'}
]
}
],
operator: 42
}
ANode 的结构
template 的 parse 直接返回一个 ANode 对象。ANode 是一个 JSON Object,不包含任何方法,只有属性。
{Object?} textExpr
文本的表达式对象。当前节点为文本节点时该属性存在。
{Array.} children
ANode 的结构与 HTML 一样,是一个树状结构。children 是当前节点的子节点列表。文本节点该属性无效
{Array.} props
节点的属性绑定信息。文本节点该属性无效
// 遍历所有绑定属性
aNode.props.forEach(function (prop) {
});
{Array.} events
节点的事件绑定信息。文本节点该属性无效
// 遍历所有绑定属性
aNode.events.forEach(function (event) {
});
{Object} directives
节点的指令绑定信息。文本节点该属性无效
// 获取 if 指令信息。该信息是一个特定的指令信息对象
aNode.directives['if'];
{string} tagName
节点的标签名。文本节点该属性无效
{Array.?} elses
当节点包含 if
directive 时,其对应的 else
和 elif
节点
模板解析结果
模板解析的返回结果是一个标签节点的 ANode 实例,实例中 children
包含子节点、props
包含属性绑定信息、events
包含事件绑定信息、directives
包含指令信息、tagName
为节点标签名、elses
为条件节点结。
本章节通过一些示例说明模板解析的 ANode 结果。其中表达式信息的详细说明请参考 表达式 章节,ANode 结构请参考 ANode 的结构 章节。
文本
文本节点作为 p 标签的子节点存在。
<p>Hello {{name}}!</p>
aNode = {
"directives": {},
"props": [],
"events": [],
"children": [
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.STRING,
"value": "Hello "
},
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"filters": []
},
{
"type": ExprType.STRING,
"value": "!"
},
]
}
}
],
"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": []
}
]
}
}
],
"events": [],
"children": [
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.INTERP,
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"filters": []
}
]
}
}
],
"tagName": "span"
}
属性为单一插值并且无 filter 时,插值内部表达式被抽取。
<span title="{{name}}">{{name}}</span>
aNode = {
"directives": {},
"props": [
{
"name": "title",
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
}
}
],
"events": [],
"children": [
{
"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"
}
},
{
"name": "value",
"expr": {
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "name"
}
]
},
"x": 1
}
],
"events": [],
"children": [],
"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": {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: "comma"}
]
},
"args": [
{
"type": ExprType.BINARY,
"segs": [
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "commaLength"
}
]
},
{
"type": 2,
"value": 1
}
],
"operator": 43
}
]
}
]
}
]
}
}
],
"events": [],
"children": [],
"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"
}
}
],
"events": [
{
"name": "click",
"expr": {
"type": ExprType.CALL,
"name": {
type: ExprType.ACCESSOR,
paths: [
{type: ExprType.STRING, value: "clicker"}
]
},
"args": [
{
"type": ExprType.ACCESSOR,
"paths": [
{
"type": ExprType.STRING,
"value": "$event"
}
]
}
]
}
}
],
"children": [
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{"type": ExprType.STRING, "value": "click here"}
]
}
}
],
"tagName": "button"
}
条件指令
if 指令的值是一个表达式信息对象,else 指令的值永远等于 true。else 和 elif 对应的节点会被置于同组的 if 下的 elses 属性中。
<div>
<span s-if="isOnline">Hello!</span>
<span s-else>Offline</span>
</div>
aNode = {
"directives": {},
"props": [],
"events": [],
"children": [
{
"directives": {
"if": {
"value": {
"type": ExprType.ACCESSOR,
"paths": [
{type: ExprType.STRING, value: "isOnline"}
]
},
"name": "if"
}
},
"props": [],
"events": [],
"children": [
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{"type": ExprType.STRING, "value": "Hello!"}
]
}
}
],
"tagName": "span",
"elses": [
{
"directives": {
"else": {
"value": true,
"name": "else"
}
},
"props": [],
"events": [],
"children": [
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{"type": ExprType.STRING, "value": "Offline"}
]
}
}
],
"tagName": "span"
}
]
},
],
"tagName": "div"
}
循环指令
循环指令对象的信息包括:
- item - 表达式对象,表示循环过程中数据项对应的变量
- index - 表达式对象,表示循环过程中数据索引对应的变量
- value - 表达式对象,表示要循环的数据
- name - 恒为 for
<ul>
<li s-for="p, index in persons">{{p.name}} - {{p.email}}</li>
</ul>
aNode = {
"directives": [],
"props": [],
"events": [],
"children": [
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.STRING,
"value": "\n "
}
],
"value": "\n "
}
},
{
"directives": {
"for": {
"item": {
type: ExprType.ACCESSOR,
paths: [
{"type": ExprType.STRING, "value": "p"}
]
},
"index": {
type: ExprType.ACCESSOR,
paths: [
{"type": ExprType.STRING, "value": "index"}
]
}
"value": {
type: ExprType.ACCESSOR,
paths: [
{"type": ExprType.STRING, "value": "persons"}
]
},
"name": "for"
}
},
"props": [],
"events": [],
"children": [
{
"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"
},
{
"textExpr": {
"type": ExprType.TEXT,
"segs": [
{
"type": ExprType.STRING,
"value": "\n"
}
],
"value": "\n"
}
}
],
"tagName": "ul"
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论