目录

深入详解 Go Template 模板语法及使用

Go 标准库的text/templatehtml/template是数据驱动的模板引擎,广泛用于文本生成、Web 页面渲染等场景。

本文基于 Go 官方定义的完整语法体系,结合代码示例,讲解说明 Go 模板语法指南,涵盖从基础分隔符到高级递归模板的所有特性

一、前提

在深入语法前,必须先明确 Go 模板的核心基础 —— 两大包的定位与语法设计本质,这是理解所有语法的前提:

包名 核心定位 语法支持 核心差异点 官方设计目标
text/template 通用文本模板引擎 支持所有基础语法与高级特性 无自动转义,输出原始文本 适配配置文件、日志、邮件正文等非 HTML 场景
html/template 安全 HTML 模板引擎 完全继承text/template所有语法 上下文感知自动转义,防 XSS 注入 适配 Web 页面、HTML 邮件等场景

语法本质:Go 模板语法是 “数据占位符 + 逻辑控制语句” 的组合,所有动态逻辑均包裹在{{ }}分隔符内,静态文本直接保留原样。

语法设计遵循 “极简逻辑 + 强安全约束”,不支持复杂计算,仅专注于数据渲染与基础流程控制。

二、基础语法

(一)分隔符与空白控制

Go 模板默认使用{{ }}作为动态语法分隔符,同时支持通过{{--}}控制空白字符(空格、换行、制表符):

  • {{- 语法 -}}:同时移除语法块前后的空白
  • {{- 语法 }}:仅移除语法块前面的空白
  • {{ 语法 -}}:仅移除语法块后面的空白
  • 未加-:保留所有空白字符(包括语法块前后的换行、空格)

示例:

package main

import (
	"os"
	"text/template"
)

func main() {
	tplStr := `
基础用法:{{ 10 + 20 }}
移除前面空白:{{- 10 + 20 }}
移除后面空白:{{ 10 + 20 -}}
移除前后空白:{{- 10 + 20 -}}
`
	tpl := template.Must(template.New("blankTest").Parse(tplStr))
	tpl.Execute(os.Stdout, nil)
}

结果:

基础用法: 30 
移除前面空白:30 
移除后面空白: 30
移除前后空白:30

(二)注释语法

模板注释的官方语法为{{/* 注释内容 */}},注释内容不会被渲染,且支持跨行,但需注意空白控制:

  • 普通注释:{{/* 这是单行注释 */}}
  • 多行注释:{{/* 这是跨多行的注释, 不会被渲染到输出结果中 */}}
  • 无空白注释:{{- /* 注释前后无空白 */ -}}(避免注释占用空行)

注意:官方强调,注释本身会占用文本位置,若未加空白控制,可能导致输出多余空行,建议在模板中使用{{- /* 注释 */ -}}格式。

(三)作用域与 “.”

Go 模板的 “作用域” 是官方语法的核心,所有动态操作均依赖作用域上下文,而.(点)是作用域的核心标识:

  • .的官方定义:当前作用域的 “当前对象”,类似 Java 的this、Python 的self,本质是作用域内可访问的数据源。
  • 作用域类型
    1. 顶级作用域:Execute方法传入的第二个参数,整个模板全局可通过$访问($是顶级作用域的永久引用,不会随局部作用域变化)。
    2. 局部作用域:rangewithblockdefine定义的模板会创建独立局部作用域,局部作用域内的.会被重定向为该结构的数据源。
  • 作用域继承规则:局部作用域可访问外层作用域的变量(包括$),但外层作用域无法访问局部作用域的变量。

示例

package main

import (
	"os"
	"text/template"
)

type User struct {
	Name    string
	Hobbies []string
	Profile Profile
}

type Profile struct {
	Age  int
	City string
}

func main() {
	tplStr := `
顶级作用域:{{ .Name }}(.指向User对象)
{{ range .Hobbies }}
  range局部作用域:{{ . }}(.指向当前Hobby元素)
  局部作用域访问顶级变量:{{ $.Name }}(通过$访问顶级User)
{{ end }}
{{ with .Profile }}
  with局部作用域:{{ .Age }}-{{ .City }}(.指向Profile对象)
  局部作用域访问外层变量:{{ $.Hobbies }}(通过$访问顶级Hobbies)
{{ end }}
`
	user := User{
		Name:    "李明",
		Hobbies: []string{"读书", "跑步", "编程"},
		Profile: Profile{Age: 30, City: "北京"},
	}
	tpl := template.Must(template.New("scopeTest").Parse(tplStr))
	tpl.Execute(os.Stdout, user)
}	

三、变量语法

1. 变量定义与赋值(官方语法格式)
  • 定义变量:{{ $变量名 := pipeline }}pipeline是产生数据的表达式,如{{ $name := .Name }}
  • 重新赋值:{{ $变量名 = pipeline }}(仅能对已定义变量赋值)
  • 命名规则:变量名以$开头,后跟字母、数字、下划线,区分大小写(如$UserName$user_age
  • 特殊变量:$(固定指向顶级作用域对象,不可重新赋值)
2. 变量作用域规则(官方明确约束)
  1. 变量仅在其定义的作用域及子作用域内有效,作用域结束(end}})后失效。
  2. 子作用域可定义与父作用域同名变量,会覆盖父作用域变量(局部优先)。
  3. 跨模板变量隔离:通过define定义的独立模板,变量互不继承(包括$,但$的值由执行模板时传入的参数决定)。

示例:

tplStr := `
{{ $globalVar := "顶级变量" }}
顶级作用域:{{ $globalVar }}
{{ range .Hobbies }}
  父作用域变量:{{ $globalVar }}(子作用域可访问)
  {{ $localVar := . }}(子作用域定义局部变量)
  子作用域变量:{{ $localVar }}
  {{ $globalVar = "被子作用域修改" }}(修改父作用域变量)
{{ end }}
顶级作用域变量(被修改后):{{ $globalVar }}
{{/* 子作用域变量$localVar此处不可访问,会报错 */}}
`

四、管道(Pipeline)语法

Go 模板的 “管道” 是数据传递核心,所有能产生数据的表达式都是管道,支持多管道串联,语法与 Unix 管道一致:

1. 管道的官方定义与分类
  • 基础管道:{{ . }}(当前对象)、{{ .Name }}(对象字段)、{{ len . }}(内置函数调用)、{{ "字符串" }}(直接量)
  • 复合管道:通过|串联多个管道,前一个管道的输出作为后一个管道的最后一个参数(如{{ .Age | printf "%d岁" }}
  • 括号管道:通过()包裹复杂管道,优先执行(如{{ printf "%d+%d=%d" 1 2 (add 3 4) }},其中(add 3 4)是优先执行的管道)
2. 管道的官方约束与特性
  1. 管道支持多返回值:若管道返回多个值,第二个返回值必须是error类型,模板引擎会自动处理错误(报错并终止渲染)。
  2. 空管道处理:若管道返回空值(0、nil、空字符串、空切片等),后续依赖该管道的操作会按 “空值逻辑” 处理(如if判断为 false)。

示例

tplStr := `
基础管道:{{ .Name }}
函数管道:{{ .Age | printf "年龄:%02d" }}
多管道串联:{{ .Name | strings.ToUpper | printf "大写姓名:%s" }}
括号管道:{{ printf "%s(长度:%d)" .Name (len .Name) }}
错误管道:{{ .Age | invalidFunc }}(第二个返回值为error,会报错)
`
// 需注册自定义函数strings.ToUpper(模板默认不支持,需手动注册)
tpl := template.New("pipelineTest").Funcs(template.FuncMap{
	"strings.ToUpper": strings.ToUpper,
})

五、流程控制语法

Go 模板支持 4 类官方定义的流程控制语句,覆盖条件判断、循环、作用域切换、模板默认实现,语法简洁且严格:

(一)if-else if-else

条件判断

{{/* 格式1:单分支 */}}
{{ if pipeline }} 满足条件时执行 {{ end }}

{{/* 格式2:双分支 */}}
{{ if pipeline }} 满足条件时执行 {{ else }} 不满足条件时执行 {{ end }}

{{/* 格式3:多分支(官方推荐写法) */}}
{{ if pipeline1 }} 满足条件1 {{ else if pipeline2 }} 满足条件2 {{ else }} 都不满足 {{ end }}

{{/* 格式4:嵌套if(等价于else if,官方不推荐,建议用格式3) */}}
{{ if pipeline1 }} 满足条件1 {{ else }} {{ if pipeline2 }} 满足条件2 {{ end }} {{ end }}

注意末尾需要加上 {{end}}

条件判断规则:管道返回 “非空值”(非 0、非 nil、非空字符串 / 切片 / Map)时,if判断为 true,否则为 false。

(二)range

循环遍历

支持遍历切片、数组、Map、Channel,完整语法:

{{/* 格式1:基础遍历(仅取元素值) */}}
{{ range pipeline }} 循环体.指向当前元素 {{ end }}

{{/* 格式2:遍历+else(空集合时执行) */}}
{{ range pipeline }} 循环体 {{ else }} 集合为空时执行 {{ end }}

{{/* 格式3:遍历取索引/键(切片/数组取索引,Map取key) */}}
{{ range $index, $value := pipeline }} 索引{{ $index }}{{ $value }} {{ end }}

{{/* 格式4:遍历Map取key和value */}}
{{ range $key, $value := .MapData }} key{{ $key }}value{{ $value }} {{ end }}

注意末尾需要加上 {{end}}

扩展特性:循环体内支持{{ break }}(终止循环)和{{ continue }}(跳过当前迭代),这是官方在 Go 1.16 + 明确支持的语法。

示例

tplStr := `
{{ range $idx, $hobby := .Hobbies }}
  {{ if eq $idx 1 }} {{ continue }} {{ end }}(跳过索引1的元素)
  索引:{{ $idx }},爱好:{{ $hobby }}
  {{ if eq $idx 2 }} {{ break }} {{ end }}(索引2后终止循环)
{{ else }}
  无爱好数据
{{ end }}
`

(三)with

作用域切换

用于临时切换当前作用域的.对象,完整语法:

{{/* 格式1:单分支(非空时切换) */}}
{{ with pipeline }} 作用域切换后.指向pipeline结果 {{ end }}

{{/* 格式2:双分支(非空切换,空时执行else) */}}
{{ with pipeline }} 切换作用域{{ . }} {{ else }} pipeline为空时执行 {{ end }}

核心逻辑:若pipeline非空,将当前作用域的.赋值为pipeline的结果,执行循环体;若为空,跳过循环体(或执行 else)。

示例

tplStr := `
{{ with .Profile }}
  切换后:{{ .Age }}岁(.指向Profile对象)
  访问顶级对象:{{ $.Name }}(通过$访问)
{{ else }}
  无Profile数据
{{ end }}
`

(四)define

模板默认实现(官方语法糖)

官方定义define是 “定义模板 + 执行模板” 的语法糖,核心用于实现模板继承(父模板定义默认块,子模板覆盖),完整语法:

{{ define "模板名" }} 默认模板内容 {{ end }}
{{ block "模板名" pipeline }} 默认模板内容 {{ end }}

以上三种效果是一样的,但是目前比较常用的是:define

在子模板使用定义的父模板

{{ template "模板名" pipeline }}

一般情况下 pipeline 为 .

示例

// 父模板(base.tpl)
{{ define "base" }}
<!DOCTYPE html>
<html>
<head>
	<title>{{ block "title" . }}默认标题{{ end }}</title>
</head>
<body>
	{{ block "content" . }}默认内容{{ end }}
</body>
</html>
{{ end }}

// 子模板(index.tpl)这里表示引用了 父模板(base.tpl),并且 . 数据传入
{{ template "base" . }}

  <h1>首页内容{{ .Name }}</h1>
{{ end }}

六、内置函数语法

Go 模板内置了 20 + 官方函数,覆盖布尔运算、比较运算、字符串处理、索引操作等场景,所有函数均遵循 “前缀语法”(函数名在前,参数在后),以下是完整函数列表:

(一)布尔运算函数

函数名 官方定义 示例 输出
and 多参数 “与”,返回第一个空值或最后一个值 {{ and "a" "" "b" }} ""
or 多参数 “或”,返回第一个非空值或最后一个值 {{ or "" "a" "b" }} "a"
not 布尔取反,参数为空值返回 true,否则 false {{ not .Name }}(若.Name 为空) true

示例

tplStr := `
{{ if and (.IsMan) (not .IsOldMan)   }}
   .IsMan=true && .IsOldMan=false 执行
{{ else }}
  .IsMan=false || .IsOldMan=true 执行
{{ end }}
`

(二)比较运算函数

函数名 官方定义 示例 输出
eq 等于(支持多参数:eq a b c → a==b 或 a==c) {{ eq .Age 30 25 }}(.Age=30) true
ne 不等于 {{ ne .Age 30 }} false
lt 小于(<) {{ lt .Age 30 }} false
le 小于等于(<=) {{ le .Age 30 }} true
gt 大于(>) {{ gt .Age 30 }} false
ge 大于等于(>=) {{ ge .Age 30 }} true

示例

tplStr := `
{{ if and (eq .Name 'fiveyoboy') (gt .Age 20)   }}
   Name='fiveyoboy' 并且 Age>20 执行
{{ else }}
  反之执行
{{ end }}
`

(三)字符串 / 文本函数

函数名 官方定义 示例 输出
print 等价于 fmt.Sprint,拼接参数为字符串 {{ print "name:" .Name }} name:李明
printf 等价于 fmt.Sprintf,格式化输出 {{ printf "age:%02d" .Age }} age:30
println 等价于 fmt.Sprintln,拼接并加换行 {{ println .Name }} 李明\n
len 返回字符串 / 切片 / Map 的长度 {{ len .Hobbies }}(3 个元素) 3

(四)索引 / 访问函数

函数名 官方定义 示例 输出
index 访问可索引对象(切片 / 数组 / Map / 字符串) {{ index .Hobbies 0 }}(第一个爱好) 读书
支持多维索引:index .MapData "key" 1 {{ index .Profile "Age" }}(Map 字段) 30
slice 切片操作(等价于 Go 的 slice [i:j:cap]) {{ slice .Hobbies 1 3 }} [跑步 编程]

(五) 函数调用函数

函数名 官方定义 示例 输出
call 调用非模板内置函数(如结构体方法、自定义函数) {{ call .Calc 10 20 }}(.Calc 是结构体方法) 30(假设方法返回和)

(六)安全转义函数(仅 text/template 支持)

函数名 官方定义 示例 输出
html 将文本转义为 HTML 安全格式 {{ html "<script>" }} <script>
js 将文本转义为 JS 安全格式 {{ js "'alert()'" }} \x27alert()\x27
urlquery 将文本转义为 URL 查询参数格式 {{ urlquery "a b" }} a+b
css 将文本转义为 CSS 安全格式 {{ css "background: #fff;" }} background: #fff;

常见问题

Q1、range 循环中修改外层变量生效?

range 子作用域可修改父作用域变量(因为变量是引用传递)

若需保留父作用域变量原值,在子作用域内先复制变量({{ $copy := $globalVar }}

Q2、html/template 转义导致原生 HTML 失效?

自动转义是安全特性,非 bug

确认内容安全后,使用template.HTML类型转换,而非改用text/template

Q3、引用父模板后获取变量失效?

引用父模板应该同时传入当前变量 .

错误示例:({{ template "base" }}

正确示例:({{ template "base" . }}

大家在使用过程中还有哪些语法是本文没有提到的?欢迎大家在评论区进行交流沟通!!!

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/go-template-guide/

备用原文链接: https://blog.fiveyoboy.com/articles/go-template-guide/