text/template 的管道、变量和函数调用有一套自己的求值规则,和 Go 代码的直觉不完全一样:管道把值放在最后一个参数,变量用 := 和 = 的作用域不同,缺 key 默认不报错。这篇用一个个小模板验证这些规则,并量解析与执行的成本。
{{.Name | printf "%q"}} "小明"
{{3 | printf "%d-%d" 1}} 1-3
{{.Name | printf "%q" | len}} 8
{{len .Tags | printf "标签数=%d"}} 标签数=2
{{printf "%s=%d" .Name .Age}} 小明=20
{{.Greeting}} 你好,小明
{{.PtrMethod}}(值 u) 输出 "",执行出错:template: t:1:2: executing "t" at <.PtrMethod>: can't evaluate field PtrMethod in type main.User
{{.PtrMethod}}(&u) 指针方法:小明管道把前一个命令的结果放在后一个命令最后一个参数的位置:{{3 | printf "%d-%d" 1}} 得到 1-3。所以要在函数设计时把“被管道传进来的值”放在参数列表最后。方法也能直接调用,但指针接收者的方法在传值数据上会报 “can't evaluate field”。
在 if 里用 := 声明 1 在 if 里用 = 赋值 2 range 里累加 10 range 的下标和值 0=x;1=y; $ 指向根数据 小明-a;小明-b; with 改变 . a|小明
:= 声明的变量到所在块的 {{end}} 为止,内层的 {{$x := 2}} 不会影响外面的 $x;= 才是改外面的变量(range 里累加就靠它);$ 始终指向传入的根数据。
map 缺 key(默认) <no value> map 缺 key(missingkey=error) 输出 "",执行出错:template: t:1:2: executing "t" at <.b>: map has no entry for key "b" map 缺 key(missingkey=zero) 0 struct 缺字段 输出 "",执行出错:template: t:1:2: executing "t" at <.Nope>: can't evaluate field Nope in type main.User nil 指针上取字段 输出 "",执行出错:template: t:1:2: executing "t" at <.Name>: nil pointer evaluating *main.User.Name 未定义的函数 解析失败:template: t:1: function "nofunc" not defined
map 缺 key 默认输出 <no value>,要报错得设 missingkey=error;结构体缺字段在执行时报错;nil 指针取字段报错;未定义的函数在解析时就失败。所以模板里的拼写错误,有的要等到运行时才暴露。
{{if .}} 对空字符串 [假]
{{if .}} 对 0 [假]
{{if .}} 对空切片 [假]
{{if .}} 对 nil [假]
{{if .}} 对非空 map [真]
{{if .}} 对结构体(零值) [真]
or 短路:{{or true (side)}} true
side() 被调用了 0 次空字符串、0、nil、空切片/map 为假,非空 map 与零值结构体为真;or/and 短路,{{or true (side)}} 里 side() 一次都没被调用。
div 10 2 5 div 10 0(函数返回 error) 输出 "前缀",执行出错:template: t:1:8: executing "t" at <div 10 0>: error calling div: 除数为 0 先 Parse 再 Funcs panic:template: t:1: function "up" not defined FuncMap 里函数返回 3 个值 panic:function bad has 3 return values; should be 1 or 2
函数必须在 Parse 之前注册,否则解析时就 panic;函数可以返回一个值或两个值(第二个是 error),返回 error 会中断执行,中断前已经写出的输出仍然保留在 writer 里。
不修剪空白 [\n小明\n]
{{- 修剪左边 -}} [小明]{{- 与 -}} 去掉动作两侧的空白(包括换行),生成纯文本格式时很常用。
已解析的模板 Execute:612 ns/次、6 次分配;fmt.Fprintf:77 ns/次、1 次分配;每次都 Parse + Execute:3474 ns/次、69 次分配
已解析的模板 Execute 约 612 ns,是 fmt.Fprintf 的约 8 倍;每次都 Parse 要 3474 ns、69 次分配。模板应该启动时解析一次,复用。
A pipeline may be "chained" by separating a sequence of commands with pipeline characters '|'. In a chained pipeline, the result of each command is passed as the last argument of the following command. The output of the final command in the pipeline is the val
文档对管道的描述与第一节的实验一致:链式管道里每个命令的结果是下一个命令的最后一个参数。