内容简介:官方定义:什么是模板?下面是一个简单的模板示例:
官方定义:
Package template implements data-driven templates for generating textual output.
template 包是数据驱动的文本输出模板,其实就是在写好的模板中填充数据。
模板
什么是模板?
下面是一个简单的模板示例:
// 模板定义 tepl := "My name is {{ . }}" // 解析模板 tmpl, err := template.New("test").Parse(tepl) // 数据驱动模板 data := "jack" err = tmpl.Execute(os.Stdout, data) 复制代码
{{ 和 }} 中间的句号 .
代表传入模板的数据,根据传入的数据不同渲染不同的内容。
.
可以代表 go 语言中的任何类型,如结构体、哈希等。
至于 {{ 和 }} 包裹的内容统称为 action,分为两种类型:
- 数据求值(data evaluations)
- 控制结构(control structures)
action 求值的结果会直接复制到模板中,控制结构和我们写 Go 程序差不多,也是条件语句、循环语句、变量、函数调用等等...
将模板成功解析(Parse)后,可以安全地在并发环境中使用,如果输出到同一个 io.Writer
数据可能会重叠(因为不能保证并发执行的先后顺序)。
Actions
模板中的 action 并不多,我们一个一个看。
注释
{{/* comment */}} 复制代码
裁剪空格
// 裁剪 content 前后的空格 {{- content -}} // 裁剪 content 前面的空格 {{- content }} // 裁剪 content 后面的空格 {{ content -}} 复制代码
文本输出
{{ pipeline }} 复制代码
pipeline 代表的数据会产生与调用 fmt.Print
函数类似的输出,例如整数类型的 3 会转换成字符串 "3" 输出。
条件语句
{{ if pipeline }} T1 {{ end }} {{ if pipeline }} T1 {{ else }} T0 {{ end }} {{ if pipeline }} T1 {{ else if pipeline }} T0 {{ end }} // 上面的语法其实是下面的简写 {{ if pipeline }} T1 {{ else }}{{ if pipeline }} T0 { {end }}{{ end }} {{ if pipeline }} T1 {{ else if pipeline }} T2 {{ else }} T0 {{ end }} 复制代码
如果 pipeline 的值为空,不会输出 T1,除此之外 T1 都会被输出。
空值有 false、0、任意 nil 指针、接口值、数组、切片、字典和空字符串 ""
(长度为 0 的字符串)。
循环语句
{{ range pipeline }} T1 {{ end }} // 这个 else 比较有意思,如果 pipeline 的长度为 0 则输出 else 中的内容 {{ range pipeline }} T1 {{ else }} T0 {{ end }} // 获取容器的下标 {{ range $index, $value := pipeline }} T1 {{ end }} 复制代码
pipeline 的值必须是数组、切片、字典和通道中的一种,即可迭代类型的值,根据值的长度输出多个 T1。
define
{{ define "name" }} T {{ end }} 复制代码
定义命名为 name 的模板。
template
{{ template "name" }} {{ template "name" pipeline }} 复制代码
引用命名为 name 的模板。
block
{{ block "name" pipeline }} T1 {{ end }} 复制代码
block 的语义是如果有命名为 name 的模板,就引用过来执行,如果没有命名为 name 的模板,就是执行自己定义的内容。
也就是多做了一步模板是否存在的判断,根据这个结果渲染不同的内容。
with
{{ with pipeline }} T1 {{ end }} // 如果 pipeline 是空值则输出 T0 {{ with pipeline }} T1 {{ else }} T0 {{ end }} {{ with arg }} . // 此时 . 就是 arg {{ end }} 复制代码
with 创建一个新的上下文环境,在此环境中的 .
与外面的 .
无关。
参数
参数的值有多种表现形式,可以求值任何类型,包括函数、指针(指针会自动间接取值到原始的值):
- 布尔、字符串、字符、浮点数、复数的行为和 Go 类似
-
关键字
nil
代表 go 语言中的nil
- 字符句号 . 代表值的结果
- 以 $ 字符开头的变量则为变量对应的值
-
结构体的字段表示为
.Field
,结果是 Field 的值,支持链式调用.Field1.Field2
-
字典的 key 表示为
.Key
结果是 Key 对应的值 -
如果是结构体的方法集中的方法 .Method 结果是方法调用后返回的值(The result is the value of invoking the method with dot as the receiver)**
- 方法要么只有一个任意类型的返回值要么第二个返回值为 error,不能再多了,如果 error 不为 nil,会直接报错,停止模板渲染
-
方法调用的结果可以继续链式调用
.Field1.Key1.Method1.Field2.Key2.Method2
-
声明变量方法集也可以调用
$x.Method1.Field
-
用括号将调用分组
print (.Func1 arg1) (.Func2 arg2)
或(.StructValuedMethod "arg").Field
这里最难懂的可能就是函数被调用的方式,如果访问结构体方法集中的函数和字段中的函数,此时的行为有什么不同?
写个 demo 测一下:
type T struct { Add func(int) int } func (t *T) Sub(i int) int { log.Println("get argument i:", i) return i - 1 } func arguments() { ts := &T{ Add: func(i int) int { return i + 1 }, } tpl := ` // 只能使用 call 调用 call field func Add: {{ call .ts.Add .y }} // 直接传入 .y 调用 call method func Sub: {{ .ts.Sub .y }} ` t, _ := template.New("test").Parse(tpl) t.Execute(os.Stdout, map[string]interface{}{ "y": 3, "ts": ts, }) } output: call field func Add: 4 call method func Sub: 2 复制代码
可以得出结论:如果函数是结构体中的函数字段,该函数不会自动调用,只能使用内置函数 call
调用。
如果函数是结构体方法集中的方法,会自动调用该方法,并且会将返回值赋值给 .
,如果函数返回新的结构体、map,可以继续链式调用。
变量
action 中的 pipeline 可以初始化变量存储结果,语法也很简单:
$variable = pipeline 复制代码
此时,这个 action 声明了一个变量而没有产生任何输出。
range 循环可以声明两个变量:
range $index, $element := pipeline 复制代码
在 if、with 和 range 中,变量的作用域拓展到 {{ end }} 所在的位置。
如果不是控制结构,声明的变量的作用域会扩展到整个模板。
例如在模板开始时声明变量:
{{ $pages := .pagination.Pages }} {{ $current := .pagination.Current }} 复制代码
在渲染开始的时候, $
变量会被替换成 .
开头的值,例如 $pages
会被替换成 .pagenation.Pages
。所以在模板间的相互引用不会传递变量,变量只在某个特定的作用域中产生作用。
函数
模板渲染时会在两个地方查找函数:
- 自定义的函数 map
- 全局函数 map,这些函数是模板内置的
自定义函数使用 func (t *Template) Funcs(funcMap FuncMap) *Template
注册。
全局函数列表:
and
返回参数之间 and 布尔操作的结果,其实就是 JavaScript 中的逻辑操作符 &&
,返回第一个能转换成 false 的值,在 Go 中就是零值,如果都为 true 返回最后一个值。
tpl := "{{ and .x .y .z }}" t, _ := template.New("test").Parse(tpl) t.Execute(os.Stdout, map[string]interface{}{ "x": 1, "y": 0, "z": 3, }) output: 0 复制代码
or
逻辑操作符 ||
,返回第一个能转换成 true 的值,在 Go 中就是非零值,如果都为 false 返回最后一个值。
tpl := "{{ or .x .y .z }}" t, _ := template.New("test").Parse(tpl) t.Execute(os.Stdout, map[string]interface{}{ "x": 1, "y": 0, "z": 3, }) output: 1 复制代码
call
返回调用第一个函数参数的结果,函数必须有一个或两个回值(第二个返回值必须是 error,如果值不为 nil 会停止模板渲染)
tpl := "call: {{ call .x .y .z }} \n" t, _ := template.New("test").Parse(tpl) t.Execute(os.Stdout, map[string]interface{}{ "x": func(x, y int) int { return x+y}, "y": 2, "z": 3, }) output: 5 复制代码
html
返回转义后的 HTML 字符串,这个函数不能在 html/template
中使用。
js
返回转义后的 JavaScript 字符串。
index
在第一个参数是 array、slice、map 时使用,返回对应下标的值。
index x 1 2 3
等于 x[1][2][3]
。
len
返回复合类型的长度。
not
返回布尔类型参数的相反值。
等于 fmt.Sprint
。
printf
等于 fmt.Sprintf
。
println
等于 fmt.Sprintln
。
urlquery
对字符串进行 url Query 转义,不能在 html/template
包中使用。
// URLQueryEscaper returns the escaped value of the textual representation of // its arguments in a form suitable for embedding in a URL query. func URLQueryEscaper(args ...interface{}) string { return url.QueryEscape(evalArgs(args)) } 复制代码
从源码可以看到这个函数直接调用 url.QueryEscape
对字符串进行转义,并没有什么神秘的。
比较函数
eq ge gt le lt ne
分析两个源码:
// eq evaluates the comparison a == b || a == c || ... func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) { v1 := indirectInterface(arg1) k1, err := basicKind(v1) if err != nil { return false, err } if len(arg2) == 0 { return false, errNoComparison } for _, arg := range arg2 { v2 := indirectInterface(arg) k2, err := basicKind(v2) if err != nil { return false, err } truth := false if k1 != k2 { // Special case: Can compare integer values regardless of type's sign. switch { case k1 == intKind && k2 == uintKind: truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint() case k1 == uintKind && k2 == intKind: truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int()) default: return false, errBadComparison } } else { switch k1 { case boolKind: truth = v1.Bool() == v2.Bool() case complexKind: truth = v1.Complex() == v2.Complex() case floatKind: truth = v1.Float() == v2.Float() case intKind: truth = v1.Int() == v2.Int() case stringKind: truth = v1.String() == v2.String() case uintKind: truth = v1.Uint() == v2.Uint() default: panic("invalid kind") } } if truth { return true, nil } } return false, nil } // ne evaluates the comparison a != b. func ne(arg1, arg2 reflect.Value) (bool, error) { // != is the inverse of ==. equal, err := eq(arg1, arg2) return !equal, err } 复制代码
eq 先判断接口类型是否相等,然后判断值是否相等,没什么特殊的地方。
ne 更是简单的调用 eq,然后取反。
ge、gt、le、lt 与 eq 类似,先判断类型,然后判断大小。
嵌套模板
下面是一个更复杂的例子:
// 加载模板 template.ParseFiles("templates/") // 加载多个模板到一个命名空间(同一个命名空间的模块可以互相引用) template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl") // must 加载失败时 panic tmpl := template.Must(template.ParseFiles("layout.html")) // 执行加载后的模板文件,默认执行第一个 tmpl.Execute(w, "test") // 如果 tmpl 中有很多个模板,可以指定要执行的模板名 tmpl.ExecuteTemplate(w, "layout", "Hello world") 复制代码
ExecuteTemplate
指定的名字就是模板文件中 define "name"
的 name。
总结
Parse
系列函数初始化的 Template
类型实例。
Execute
系列函数则将数据传递给模板渲染最终的字符串。
模板本质上就是 Parse
函数加载多个文件到一个 Tempalte
类型实例中,解析文件中的 define 关键字注册命名模板,命名模板之间可以使用 template
互相引用, Execute
传入对应的数据渲染。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Golang 语言深入理解:channel
- 深入了解 Go 语言的方法
- 深入理解 Go 语言的垃圾回收机制
- 深入理解 Go 语言中的 Interface
- Go语言的匿名函数与闭包深入理解
- 深入理解 Go 语言中的 Testable Examples
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
测试驱动的JavaScript开发
Christian Johansen / 赵勇、程德、凌杰、高博 / 机械工业出版社 / 2012-2-9 / 69.00元
本书是一本完整的、基于最佳实践的JavaScript敏捷测试指南,同时又有着测试驱动开发方法(TDD)所带来的质量保证。领先一步的JavaScript敏捷开发者Christian Johansen的讨论涵盖了将最先进的自动化测试用于JavaScript开发环境的方方面面,带领读者走查整个开发的生命周期,从项目启动到应用程序部署。本书的主要内容包括:掌握自动化测试和TDD;构建有效的自动化测试工作流......一起来看看 《测试驱动的JavaScript开发》 这本书的介绍吧!