欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Go template模板使用詳解

 更新時(shí)間:2022年04月18日 10:09:51   作者:駿馬金龍  
這篇文章主要介紹了深入解析Go template模板使用詳解,需要的朋友可以參考下

本文只關(guān)注Go text/template的底層結(jié)構(gòu),帶上了很詳細(xì)的圖片以及示例幫助理解,有些地方也附帶上了源碼進(jìn)行解釋。有了本文的解釋,對(duì)于Go template的語(yǔ)法以及html/template的用法,一切都很簡(jiǎn)單。

關(guān)于template的語(yǔ)法以及具體使用方法,見(jiàn):Go template用法詳解

入門示例

package main

import (
	"html/template"
	"os"
)

type Person struct {
	Name string
	Age    int
}

func main() {
	p := Person{"longshuai", 23}
	tmpl, err := template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, p)
	if err != nil {
		panic(err)
	}
	fmt.Println(tmpl)
}

上面定義了一個(gè)Person結(jié)構(gòu),有兩個(gè)大寫字母開(kāi)頭(意味著這倆字段是導(dǎo)出的)的字段Name和Age。然后main()中創(chuàng)建了Person的實(shí)例對(duì)象p。

緊接著使用template.New()函數(shù)創(chuàng)建了一個(gè)空Template實(shí)例(對(duì)象),然后通過(guò)這個(gè)template實(shí)例調(diào)用Parse()方法,Parse()方法用來(lái)解析、評(píng)估模板中需要執(zhí)行的action,其中需要評(píng)估的部分都使用{{}}包圍,并將評(píng)估后(解析后)的結(jié)果賦值給tmpl。

最后調(diào)用Execute()方法,該方法將數(shù)據(jù)對(duì)象Person的實(shí)例p應(yīng)用到已經(jīng)解析的tmpl模板,最后將整個(gè)應(yīng)用合并后的結(jié)果輸出到os.Stdout。

上面的示例很簡(jiǎn)單,兩個(gè)注意點(diǎn):

  • 流程:構(gòu)建模板對(duì)象New()-->解析數(shù)據(jù)Parse()-->應(yīng)用合并Execute()
  • Parse()解析的對(duì)象中包含了{{}},其中使用了點(diǎn)(.),{{.Name}}代表Execute()第二個(gè)參數(shù)p對(duì)象的Name字段,同理{{.Age}}

也就是說(shuō),{{.}}代表的是要應(yīng)用的對(duì)象,類似于java/c++中的this,python/perl中的self。

更通用地,{{.}}表示的是所處作用域的當(dāng)前對(duì)象,而不僅僅只代表Execute()中的第二個(gè)參數(shù)對(duì)象。例如,本示例中{{.}}代表頂級(jí)作用域的對(duì)象p,如果Parse()中還有嵌套的作用域range,則{{.}}代表range迭代到的每個(gè)元素對(duì)象。如果了解perl語(yǔ)言,{{.}}可以理解為默認(rèn)變量$_。

模板關(guān)聯(lián)(associate)

template中有不少函數(shù)、方法都直接返回*Template類型。

上圖中使用紅色框線框起來(lái)一部分返回值是*Template的函數(shù)、方法。對(duì)于函數(shù),它們返回一個(gè)Template實(shí)例(假設(shè)為t),對(duì)于使用t作為參數(shù)的Must()函數(shù)和那些框起來(lái)的Template方法,它們返回的*Template其實(shí)是原始實(shí)例t。

例如:

t := template.New("abc")
tt,err := t.Parse("xxxxxxxxxxx")

這里的t和tt其實(shí)都指向同一個(gè)模板對(duì)象。

這里的t稱為模板的關(guān)聯(lián)名稱。通俗一點(diǎn),就是創(chuàng)建了一個(gè)模板,關(guān)聯(lián)到變量t上。但注意,t不是模板的名稱,因?yàn)門emplate中有一個(gè)未導(dǎo)出的name字段,它才是模板的名稱??梢酝ㄟ^(guò)Name()方法返回name字段的值,而且仔細(xì)觀察上面的函數(shù)、方法,有些是以name作為參數(shù)的。

之所以要區(qū)分模板的關(guān)聯(lián)名稱(t)和模板的名稱(name),是因?yàn)?strong>一個(gè)關(guān)聯(lián)名稱t(即模板對(duì)象)上可以"包含"多個(gè)name,也就是多個(gè)模板,通過(guò)t和各自的name,可以調(diào)用到指定的模板。

模板結(jié)構(gòu)詳解

首先看Template結(jié)構(gòu):

type Template struct {
	name string
	*parse.Tree
	*common
	leftDelim  string
	rightDelim string
}

name是這個(gè)Template的名稱,Tree是解析樹(shù),common是另一個(gè)結(jié)構(gòu),稍后解釋。leftDelim和rightDelim是左右兩邊的分隔符,默認(rèn)為{{}}

這里主要關(guān)注name和common兩個(gè)字段,name字段沒(méi)什么解釋的。common是一個(gè)結(jié)構(gòu):

type common struct {
	tmpl   map[string]*Template // Map from name to defined templates.
	option option
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

這個(gè)結(jié)構(gòu)的第一個(gè)字段tmpl是一個(gè)Template的map結(jié)構(gòu),key為template的name,value為Template。也就是說(shuō),一個(gè)common結(jié)構(gòu)中可以包含多個(gè)Template,而Template結(jié)構(gòu)中又指向了一個(gè)common結(jié)構(gòu)。所以,common是一個(gè)模板組,在這個(gè)模板組中的(tmpl字段)所有Template都共享一個(gè)common(模板組),模板組中包含parseFuncs和execFuncs。

大概結(jié)構(gòu)如下圖:

除了需要關(guān)注的name和common,parseFuncs和execFuncs這兩個(gè)字段也需要了解下,它們共同成為模板的FuncMap。

New()函數(shù)和init()方法

使用template.New()函數(shù)可以創(chuàng)建一個(gè)空的、無(wú)解析數(shù)據(jù)的模板,同時(shí)還會(huì)創(chuàng)建一個(gè)common,也就是模板組。

func New(name string) *Template {
	t := &Template{
		name: name,
	}
	t.init()
	return t
}

其中t為模板的關(guān)聯(lián)名稱,name為模板的名稱,t.init()表示如果模板對(duì)象t還沒(méi)有common結(jié)構(gòu),就構(gòu)造一個(gè)新的common組:

func (t *Template) init() {
	if t.common == nil {
		c := new(common)
		c.tmpl = make(map[string]*Template)
		c.parseFuncs = make(FuncMap)
		c.execFuncs = make(map[string]reflect.Value)
		t.common = c
	}
}

也就是說(shuō),template.New()函數(shù)不僅創(chuàng)建了一個(gè)模板,還創(chuàng)建了一個(gè)空的common結(jié)構(gòu)(模板組)。需要注意,新創(chuàng)建的common是空的,只有進(jìn)行模板解析(Parse(),ParseFiles()等操作)之后,才會(huì)將模板添加到common的tmpl字段(map結(jié)構(gòu))中。

所以,下面的代碼:

tmpl := template.New("mytmpl1")

執(zhí)行完后將生成如下結(jié)構(gòu),其中tmpl為模板關(guān)聯(lián)名稱,mytmpl1為模板名稱。

因?yàn)檫€沒(méi)有進(jìn)行解析操作,所以上圖使用虛線表示尚不存在的部分。

實(shí)際上,在template包中,很多涉及到操作Template的函數(shù)、方法,都會(huì)調(diào)用init()方法保證返回的Template都有一個(gè)有效的common結(jié)構(gòu)。當(dāng)然,因?yàn)閕nit()方法中進(jìn)行了判斷,對(duì)于已存在common的模板,不會(huì)新建common結(jié)構(gòu)。

假設(shè)現(xiàn)在執(zhí)行了Parse()方法,將會(huì)把模板name添加到common tmpl字段的map結(jié)構(gòu)中,其中模板name為map的key,模板為map的value。

例如:

func main() {
	t1 := template.New("test1")
	tmpl,_ := t1.Parse(
			`{{define "T1"}}ONE{{end}}
			{{define "T2"}}TWO{{end}}
			{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
			{{template "T3"}}`)
	fmt.Println(t1)
	fmt.Println(tmpl)
	fmt.Println(t1.Lookup("test1"))  // 使用關(guān)聯(lián)名稱t1檢索test1模板
	fmt.Println(t1.Lookup("T1"))
	fmt.Println(tmpl.Lookup("T2")) // 使用關(guān)聯(lián)名稱tmpl檢索T2模板
	fmt.Println(tmpl.Lookup("T3"))
}

上述代碼的執(zhí)行結(jié)果:注意前3行的結(jié)果完全一致,所有行的第二個(gè)地址完全相同。

&{test1 0xc0420a6000 0xc0420640c0  }
&{test1 0xc0420a6000 0xc0420640c0  }
&{test1 0xc0420a6000 0xc0420640c0  }
&{T1 0xc0420a6100 0xc0420640c0  }
&{T2 0xc0420a6200 0xc0420640c0  }
&{T3 0xc0420a6300 0xc0420640c0  }

首先使用template.New()函數(shù)創(chuàng)建了一個(gè)名為test1的模板,同時(shí)創(chuàng)建了一個(gè)模板組(common),它們關(guān)聯(lián)在t1變量上。

然后調(diào)用Parse()方法,在Parse()的待解析字符串中使用define又定義了3個(gè)新的模板對(duì)象,模板的name分別為T1、T2和T3,其中T1和T2嵌套在T3中,因?yàn)檎{(diào)用的是t1的Parse(),所以這3個(gè)新創(chuàng)建的模板都會(huì)關(guān)聯(lián)到t1上。

也就是說(shuō),現(xiàn)在t1上關(guān)聯(lián)了4個(gè)模板:test1、T1、T2、T3,它們?nèi)脊蚕硗粋€(gè)common。因?yàn)橐呀?jīng)執(zhí)行了Parse()解析操作,這個(gè)Parse()會(huì)將test1、T1、T2、T3的name添加到common.tmpl的map中。也就是說(shuō),common的tmpl字段的map結(jié)構(gòu)中有4個(gè)元素。

結(jié)構(gòu)如下圖:

必須注意,雖然test1、T1、T2、T3都關(guān)聯(lián)在t1上,但t1只能代表test1(所以上圖中只有test1下面標(biāo)注了t1),因?yàn)閠1是一個(gè)Template類型??梢哉J(rèn)為test1、T1、T2、T3這4個(gè)模板共享一個(gè)組,但T1、T2、T3都是對(duì)外部不可見(jiàn)的,只能通過(guò)特殊方法的查詢找到它們。

另外,前文說(shuō)過(guò),template包中很多返回*Template的函數(shù)、方法返回的其實(shí)是原始的t(看源代碼即可知道),這個(gè)規(guī)則也適用于這里的Parse()方法,所以tmpl和t1這兩個(gè)變量是完全等價(jià)的,都指向同一個(gè)template,即test1。所以前面的執(zhí)行結(jié)果中前3行完全一致。

再回頭看上面代碼的執(zhí)行結(jié)果,假設(shè)結(jié)果中的每一行都分為3列,第一列為template name,第二個(gè)字段為parseTree的地址,第三列為common結(jié)構(gòu)的地址。因?yàn)閠mpl1、t1都指向test1模板,所以前3行結(jié)果完全一致。因?yàn)閠est1、T1、T2、T3共享同一個(gè)common,所以第三列全都相同。因?yàn)槊總€(gè)模板的解析樹(shù)不一樣,所以第二列全都不一樣。

New()方法

除了template.New()函數(shù),還有一個(gè)Template.New()方法:

// New allocates a new, undefined template associated with the given one and with the same
// delimiters. The association, which is transitive, allows one template to
// invoke another with a {{template}} action.
func (t *Template) New(name string) *Template {
	t.init()
	nt := &Template{
		name:       name,
		common:     t.common,
		leftDelim:  t.leftDelim,
		rightDelim: t.rightDelim,
	}
	return nt
}

看注釋很難理解,但是看它的代碼,結(jié)合前文的解釋,New()方法的作用很明顯。

首先t.init()保證有一個(gè)有效的common結(jié)構(gòu),然后構(gòu)造一個(gè)新的Template對(duì)象nt,這個(gè)nt除了name和解析樹(shù)parse.Tree字段之外,其它所有內(nèi)容都和t完全一致。換句話說(shuō),nt和t共享了common。

也就是說(shuō),New()方法使得名為name的nt模板對(duì)象加入到了關(guān)聯(lián)組中。更通俗一點(diǎn),通過(guò)調(diào)用t.New()方法,可以創(chuàng)建一個(gè)新的名為name的模板對(duì)象,并將此對(duì)象加入到t模板組中。

這和New()函數(shù)的作用基本是一致的,只不過(guò)New()函數(shù)是構(gòu)建新的模板對(duì)象并構(gòu)建一個(gè)新的common結(jié)構(gòu),而New()方法則是構(gòu)建一個(gè)新的模板對(duì)象,并加入到已有的common結(jié)構(gòu)中。

只是還是要說(shuō)明,因?yàn)镹ew()出來(lái)的新對(duì)象在執(zhí)行解析之前(如Parse()),它們暫時(shí)都還不會(huì)加入到common組中,在New()出來(lái)之后,僅僅只是讓它指向已有的一個(gè)common結(jié)構(gòu)。

所以:

t1 := template.New("test1")
t1 = t1.Parse(...)
t2 := t1.New("test2")
t2 = t2.Parse(...)
t3 := t1.New("test3")

結(jié)構(gòu)圖:

如果t1和t2的Parse()中,都定義一個(gè)或多個(gè)name相同的模板會(huì)如何?例如:

t1 := template.New("test1")
t2 := t1.New("test2")
t1, _ = t1.Parse(
	`{{define "T1"}}ONE{{end}}
	{{define "T2"}}TWO{{end}}
	{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
	{{template "T3"}}`)
t2, _ = t2.Parse(
	`{{define "T4"}}ONE{{end}}
	{{define "T2"}}TWOO{{end}}
	{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
	{{template "T3"}}`)

	_ = t1.Execute(os.Stdout, "a")
	_ = t2.Execute(os.Stdout, "a")

在上面的t1和t2中,它們共享同一個(gè)common,且t1.Parse()中定義了T1、T2和T3,t2.Parse()中定義了T4、T2和T3,且兩個(gè)T2的解析內(nèi)容不一樣(解析樹(shù)不一樣)。

因?yàn)門1、T2、T3、T4都會(huì)加入到t1和t2共享的common中,所以無(wú)論是通過(guò)t1還是通過(guò)t2這兩個(gè)關(guān)聯(lián)名稱都能找到T1、T2、T3、T4。但是后解析的會(huì)覆蓋先解析的,也就是說(shuō),無(wú)論是t1.Lookup("T2")還是t2.Lookup("T2")得到的T2對(duì)應(yīng)的template,都是在t2.Parse()中定義的。當(dāng)t1.Execute()的時(shí)候,會(huì)得到t2中定義的T2的值。

ONE TWOO
ONE TWOO

Parse()

Parse(string)方法用于解析給定的文本內(nèi)容string。用法上很簡(jiǎn)單,前面也已經(jīng)用過(guò)幾次了,沒(méi)什么可解釋的。重點(diǎn)在于它的作用。

當(dāng)創(chuàng)建了一個(gè)模板對(duì)象后,會(huì)有一個(gè)與之關(guān)聯(lián)的common(如果不存在,template包中的各種函數(shù)、方法都會(huì)因?yàn)檎{(diào)用init()方法而保證common的存在)。只有在Parse()之后,才會(huì)將相關(guān)的template name放進(jìn)common中,表示這個(gè)模板已經(jīng)可用了,或者稱為已經(jīng)定義了(defined),可用被Execute()或ExecuteTemplate(),也表示可用使用Lookup()和DefinedTemplates()來(lái)檢索模板。另外,調(diào)用了Parse()解析后,會(huì)將給定的FuncMap中的函數(shù)添加到common的FuncMap中,只有添加到common的函數(shù),才可以在模板中使用。

Parse()方法是解析字符串的,且只解析New()出來(lái)的模板對(duì)象。如果想要解析文件中的內(nèi)容,見(jiàn)后文ParseFiles()、ParseGlob()。

Lookup()、DefinedTemplates()和Templates()方法

這三個(gè)方法都用于檢索已經(jīng)定義的模板,Lookup()根據(jù)template name來(lái)檢索并返回對(duì)應(yīng)的template,DefinedTemplates()則是返回所有已定義的templates。Templates()和DefinedTemplates()類似,但是它返回的是[]*Template,也就是已定義的template的slice。

前面多次說(shuō)過(guò),只有在解析之后,模板才加入到common結(jié)構(gòu)中,才算是已經(jīng)定義,才能被檢索或執(zhí)行。

當(dāng)檢索不存在的templates時(shí),Lookup()將返回nil。當(dāng)common中沒(méi)有模板,DefinedTemplates()將返回空字符串"",Templates()將返回空的slice。

func main() {
	t1 := template.New("test1")
	t2 := t1.New("test2")
	t1, _ = t1.Parse(
		`{{define "T1"}}ONE{{end}}
		{{define "T2"}}TWO{{end}}
		{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)
	t2, _ = t2.Parse(
		`{{define "T4"}}ONE{{end}}
		{{define "T2"}}TWOO{{end}}
		{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)

	fmt.Println(t1.DefinedTemplates())
	fmt.Println(t2.DefinedTemplates())
	fmt.Println(t2.Templates())
}

返回結(jié)果:

; defined templates are: "T1", "T2", "T3", "test1", "T4", "test2"
; defined templates are: "test1", "T4", "test2", "T1", "T2", "T3"
[0xc04201c280 0xc042064100 0xc04201c1c0 0xc04201c2c0 0xc04201c300 0xc042064080]

從結(jié)果可見(jiàn),返回的順序雖然不一致,但包含的template name是完全一致的。

Clone()方法

Clone()方法用于克隆一個(gè)完全一樣的模板,包括common結(jié)構(gòu)也會(huì)完全克隆。

t1 := template.New("test1")
t1 = t1.Parse(...)
t2 := t1.New("test2")
t2 = t2.Parse(...)

t3, err := t1.Clone()
if err != nil {
	panic(err)
}

這里的t3和t1在內(nèi)容上完全一致,但在內(nèi)存中它們是兩個(gè)不同的對(duì)象。但無(wú)論如何,目前t3中會(huì)包含t1和t2共享的common,即使t2中定義了{{define "Tx"}}...{{end}},這個(gè)Tx也會(huì)包含在t3中。

因?yàn)槭遣煌膶?duì)象,所以修改t3,不會(huì)影響t1/t2。

看下面的例子:

func main() {
	t1 := template.New("test1")
	t2 := t1.New("test2")
	t1, _ = t1.Parse(
		`{{define "T1"}}ONE{{end}}
		{{define "T2"}}TWO{{end}}
		{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)
	t2, _ = t2.Parse(
		`{{define "T4"}}ONE{{end}}
		{{define "T2"}}TWOO{{end}}
		{{define "T3"}}{{template "T4"}} {{template "T2"}}{{end}}
		{{template "T3"}}`)

	t3, err := t1.Clone()
	if err != nil {
		panic(err)
	}

    // 結(jié)果完全一致
	fmt.Println(t1.Lookup("T4"))
	fmt.Println(t3.Lookup("T4"))
    
    // 修改t3
	t3,_ = t3.Parse(`{{define "T4"}}one{{end}}`)
    // 結(jié)果將不一致
	fmt.Println(t1.Lookup("T4"))
	fmt.Println(t3.Lookup("T4"))
}

Must()函數(shù)

正常情況下,很多函數(shù)、方法都返回兩個(gè)值,一個(gè)是想要返回的值,一個(gè)是err信息。template包中的函數(shù)、方法也一樣如此。

但有時(shí)候不想要err信息,而是直接取第一個(gè)返回值,并賦值給變量。操作大概是這樣的:

t1 := template.New("ttt")
t1,err := t1.Parse(...)
if err != nil {
    panic(err)
}
...

Must()函數(shù)將上面的過(guò)程封裝了,使得Must()可以簡(jiǎn)化上面的操作:

func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

當(dāng)某個(gè)返回*Template,err的函數(shù)、方法需要直接使用時(shí),可用將其包裝在Must()中,它會(huì)自動(dòng)在有err的時(shí)候panic,無(wú)錯(cuò)的時(shí)候只返回其中的*Template。

這在賦值給變量的時(shí)候非常簡(jiǎn)便,例如:

var t = template.Must(template.New("name").Parse("text"))

ParseFiles()和ParseGlob()

Parse()只能解析字符串,要解析文件中的內(nèi)容,需要使用ParseFiles()或ParseGlob()。

template包中有ParseFiles()和ParseGlob()函數(shù),也有ParseFiles()和ParseGlob()方法。

這兩個(gè)函數(shù)和這兩個(gè)方法的區(qū)別,看一下文檔就很清晰:

$ go doc template.ParseFiles
func ParseFiles(filenames ...string) (*Template, error)
    ParseFiles creates a new Template and parses the template definitions from
    the named files. The returned template's name will have the (base) name and
    (parsed) contents of the first file. There must be at least one file. If an
    error occurs, parsing stops and the returned *Template is nil.

$ go doc template.template.ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
    ParseFiles parses the named files and associates the resulting templates
    with t. If an error occurs, parsing stops and the returned template is nil;
    otherwise it is t. There must be at least one file.

解釋很清晰。ParseFiles()函數(shù)是直接解析一個(gè)或多個(gè)文件的內(nèi)容,并返回第一個(gè)文件名的basename作為Template的名稱,也就是說(shuō)這些文件的template全都關(guān)聯(lián)到第一個(gè)文件的basename上。ParseFiles()方法則是解析一個(gè)或多個(gè)文件的內(nèi)容,并將這些內(nèi)容關(guān)聯(lián)到t上。

看示例就一目了然。

例如,當(dāng)前go程序的目錄下有3個(gè)文件:a.cnf、b.cnf和c.cnf,它們的內(nèi)容無(wú)所謂,反正空內(nèi)容也可以解析。

func main() {
	t1,err := template.ParseFiles("a.cnf","b.cnf","c.cnf")
	if err != nil {
		panic(err)
	}
	fmt.Println(t1.DefinedTemplates())
	fmt.Println()
	fmt.Println(t1)
	fmt.Println(t1.Lookup("a.cnf"))
	fmt.Println(t1.Lookup("b.cnf"))
	fmt.Println(t1.Lookup("c.cnf"))
}

輸出結(jié)果:

; defined templates are: "a.cnf", "b.cnf", "c.cnf"

&{a.cnf 0xc0420ae000 0xc042064140  }
&{a.cnf 0xc0420ae000 0xc042064140  }
&{b.cnf 0xc0420bc000 0xc042064140  }
&{c.cnf 0xc0420bc100 0xc042064140  }

從結(jié)果中可以看到,已定義的template name都是文件的basename,且t1和a.cnf這個(gè)template是完全一致的,即t1是文件列表中的第一個(gè)模板對(duì)象。

結(jié)構(gòu)如下圖:

理解了ParseFiles()函數(shù),理解ParseFiles()方法、ParseGlob()函數(shù)、ParseGlob()方法,應(yīng)該不會(huì)再有什么問(wèn)題。但是還是有需要注意的地方:

func main() {
	t1 := template.New("test")
	t1,err := t1.ParseFiles("a.cnf","b.cnf","c.cnf")
	if err != nil {
		panic(err)
	}
    // 先注釋下面這行
	//t1.Parse("")
	fmt.Println(t1.DefinedTemplates())
	fmt.Println()
	fmt.Println(t1)
	fmt.Println(t1.Lookup("a.cnf"))
	fmt.Println(t1.Lookup("b.cnf"))
	fmt.Println(t1.Lookup("c.cnf"))
}

執(zhí)行結(jié)果:

; defined templates are: "a.cnf", "b.cnf", "c.cnf"

&{test <nil> 0xc0420640c0  }
&{a.cnf 0xc0420b0000 0xc0420640c0  }
&{b.cnf 0xc0420be000 0xc0420640c0  }
&{c.cnf 0xc0420be100 0xc0420640c0  }

發(fā)現(xiàn)template.New()函數(shù)創(chuàng)建的模板對(duì)象test并沒(méi)有包含到common中。為什么?

因?yàn)閠.ParseFiles()、t.ParseGlob()方法的解析過(guò)程是獨(dú)立于t之外的,它們只解析文件內(nèi)容,不解析字符串。而New()出來(lái)的模板,需要Parse()方法來(lái)解析才會(huì)加入到common中。

將上面的注釋行取消掉,執(zhí)行結(jié)果將如下:

; defined templates are: "a.cnf", "b.cnf", "c.cnf", "test"

&{test 0xc0420bc200 0xc0420640c0  }
&{a.cnf 0xc0420ae000 0xc0420640c0  }
&{b.cnf 0xc0420bc000 0xc0420640c0  }
&{c.cnf 0xc0420bc100 0xc0420640c0  }

具體原因可分析parseFiles()源碼:

func parseFiles(t *Template, filenames ...string) (*Template, error) {
	if len(filenames) == 0 {
		// Not really a problem, but be consistent.
		return nil, fmt.Errorf("template: no files named in call to ParseFiles")
	}
	for _, filename := range filenames {
		b, err := ioutil.ReadFile(filename)
		if err != nil {
			return nil, err
		}
		s := string(b)

		// name為文件名的basename部分
		name := filepath.Base(filename)

		var tmpl *Template
		if t == nil {
			t = New(name)
		}
		// 如果調(diào)用t.Parsefiles(),則t.Name不為空
		// name也就不等于t.Name
		// 于是新New(name)一個(gè)模板對(duì)象給tmpl
		if name == t.Name() {
			tmpl = t
		} else {
			tmpl = t.New(name)
		}
		// 解析tmpl。如果選中了上面的else分支,則和t無(wú)關(guān)
		_, err = tmpl.Parse(s)
		if err != nil {
			return nil, err
		}
	}
	return t, nil
}

Execute()和ExecuteTemplate()

這兩個(gè)方法都可以用來(lái)應(yīng)用已經(jīng)解析好的模板,應(yīng)用表示對(duì)需要評(píng)估的數(shù)據(jù)進(jìn)行操作,并和無(wú)需評(píng)估數(shù)據(jù)進(jìn)行合并,然后輸出到io.Writer中:

func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

兩者的區(qū)別在于Execute()是應(yīng)用整個(gè)common中已定義的模板對(duì)象,而ExecuteTemplate()可以選擇common中某個(gè)已定義的模板進(jìn)行應(yīng)用。

例如:

func main() {
	t1 := template.New("test1")
	t1, _ = t1.Parse(`{{define "T1"}}ONE{{end}}
		{{- define "T2"}}TWO{{end}}
		{{- define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
		{{- template "T3"}}`)
	
	_ = t1.Execute(os.Stdout,"")
	fmt.Println()
	fmt.Println("-------------")
	_ = t1.ExecuteTemplate(os.Stdout, "T2", "")
}

輸出結(jié)果:

ONE TWO
-------------
TWO

FuncMap和Funcs()

template內(nèi)置了一系列函數(shù),但這些函數(shù)畢竟有限,可能無(wú)法滿足特殊的需求。template允許我們定義自己的函數(shù),添加到common中,然后就可以在待解析的內(nèi)容中像使用內(nèi)置函數(shù)一樣使用自定義的函數(shù)。

自定義函數(shù)的優(yōu)先級(jí)高于內(nèi)置的函數(shù)優(yōu)先級(jí),即先檢索自定義函數(shù),再檢索內(nèi)置函數(shù)。也就是說(shuō),如果自定義函數(shù)的函數(shù)名和內(nèi)置函數(shù)名相同,則內(nèi)置函數(shù)將失效。

本文只對(duì)此稍作解釋,本文的重點(diǎn)不是template的具體語(yǔ)法和用法。

在common結(jié)構(gòu)中,有一個(gè)字段是FuncMap類型的:

type common struct {
	tmpl   map[string]*Template
	option option
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

這個(gè)類型的定義為:

type FuncMap map[string]interface{}

它是一個(gè)map結(jié)構(gòu),key為模板中可以使用的函數(shù)名,value為函數(shù)對(duì)象(為了方便稱呼,這里直接成為函數(shù))。函數(shù)必須只有1個(gè)值或2個(gè)值,如果有兩個(gè)值,第二個(gè)值必須是error類型的,當(dāng)執(zhí)行函數(shù)時(shí)err不為空,則執(zhí)行自動(dòng)停止。

函數(shù)可以有多個(gè)參數(shù)。假如函數(shù)str有兩個(gè)參數(shù),在待解析的內(nèi)容中調(diào)用函數(shù)str時(shí),如果調(diào)用方式為{{str . "aaa"}},表示第一個(gè)參數(shù)為當(dāng)前對(duì)象,第二個(gè)參數(shù)為字符串"aaa"。

假如,要定義一個(gè)將字符串轉(zhuǎn)換為大寫的函數(shù),可以:

import "strings"
func upper(str string) string {
	return strings.ToUpper(str)
}

然后將其添加到FuncMap結(jié)構(gòu)中,并將此函數(shù)命名為"strupper",以后在待解析的內(nèi)容中就可以調(diào)用"strupper"函數(shù)。

funcMap := template.FuncMap{
	"strupper": upper,
}

或者,直接將匿名函數(shù)放在FuncMap內(nèi)部:

funcMap := template.FuncMap{
	"strupper": func(str string) string { return strings.ToUpper(str) },
}

現(xiàn)在只是定義了一個(gè)FuncMap實(shí)例,這個(gè)實(shí)例中有一個(gè)函數(shù)。還沒(méi)有將它關(guān)聯(lián)到模板,嚴(yán)格地說(shuō)還沒(méi)有將其放進(jìn)common結(jié)構(gòu)。要將其放進(jìn)common結(jié)構(gòu),調(diào)用Funcs()方法(其實(shí)調(diào)用此方法也沒(méi)有將其放進(jìn)common,只有在解析的時(shí)候才會(huì)放進(jìn)common):

func (t *Template) Funcs(funcMap FuncMap) *Template

例如:

funcMap := template.FuncMap{
	"strupper": func(str string) string { return strings.ToUpper(str) },
}
t1 := template.New("test")
t1 = t1.Funcs(funcMap)

這樣,和t1共享common的所有模板都可以調(diào)用"strupper"函數(shù)。

注意,必須在解析之前調(diào)用Funcs()方法,在解析的時(shí)候會(huì)將函數(shù)放進(jìn)common結(jié)構(gòu)。

下面是完整的示例代碼:

package main

import (
	"os"
	"strings"
	"text/template"
)

func main() {
	funcMap := template.FuncMap{
		"strupper": upper,
	}
	t1 := template.New("test1")
	tmpl, err := t1.Funcs(funcMap).Parse(`{{strupper .}}`)
	if err != nil {
		panic(err)
	}
	_ = tmpl.Execute(os.Stdout, "go programming")
}

func upper(str string) string {
	return strings.ToUpper(str)
}

上面調(diào)用了{{strupper .}},這里的strupper是我們自定義的函數(shù),"."是它的參數(shù)(注意,參數(shù)不是放進(jìn)括號(hào)里)。這里的"."代表當(dāng)前作用域內(nèi)的當(dāng)前對(duì)象,對(duì)于這個(gè)示例來(lái)說(shuō),當(dāng)前對(duì)象就是那段字符串對(duì)象"go programming"。

 

相關(guān)文章

  • Go語(yǔ)言與gRPC的完美結(jié)合實(shí)戰(zhàn)演練

    Go語(yǔ)言與gRPC的完美結(jié)合實(shí)戰(zhàn)演練

    這篇文章主要介紹了Go語(yǔ)言與gRPC的完美結(jié)合實(shí)戰(zhàn)演練,gRPC(Remote?Procedure?Call)是一種遠(yuǎn)程過(guò)程調(diào)用技術(shù),通過(guò)壓縮和序列化數(shù)據(jù)來(lái)優(yōu)化網(wǎng)絡(luò)通信,可以顯著提高服務(wù)調(diào)用的性能和效率
    2024-01-01
  • Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn)

    Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn)

    go語(yǔ)言是一門功能強(qiáng)大的編程語(yǔ)言,它提供了眾多的網(wǎng)絡(luò)編程庫(kù),其中包括tcp/ip,本文主要介紹了Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以來(lái)了解一下
    2024-06-06
  • Go語(yǔ)言實(shí)戰(zhàn)之切片內(nèi)存優(yōu)化

    Go語(yǔ)言實(shí)戰(zhàn)之切片內(nèi)存優(yōu)化

    Go 語(yǔ)言的切片是一個(gè)動(dòng)態(tài)的數(shù)據(jù)結(jié)構(gòu),可以方便地對(duì)其進(jìn)行擴(kuò)容和縮容操作。這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言如何實(shí)現(xiàn)切片內(nèi)存優(yōu)化,需要的可以參考一下
    2023-03-03
  • 深入淺出Golang中的sync.Pool

    深入淺出Golang中的sync.Pool

    sync.Pool是可伸縮的,也是并發(fā)安全的,其大小僅受限于內(nèi)存大小。本文主要為大家介紹一下Golang中sync.Pool的原理與使用,感興趣的小伙伴可以了解一下
    2023-03-03
  • Go語(yǔ)言實(shí)現(xiàn)可選參數(shù)的方法小結(jié)

    Go語(yǔ)言實(shí)現(xiàn)可選參數(shù)的方法小結(jié)

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)可選參數(shù)的一些常見(jiàn)方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-02-02
  • Go語(yǔ)言并發(fā)技術(shù)詳解

    Go語(yǔ)言并發(fā)技術(shù)詳解

    這篇文章主要介紹了Go語(yǔ)言并發(fā)技術(shù)詳解,本文講解了goroutine、channels、Buffered Channels、Range和Close等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • Go語(yǔ)言中 Channel 詳解

    Go語(yǔ)言中 Channel 詳解

    Go 語(yǔ)言中的 channel 是實(shí)現(xiàn) goroutine 間無(wú)鎖通信的關(guān)鍵機(jī)制,他使得寫多線程并發(fā)程序變得簡(jiǎn)單、靈活、觸手可得。下面就個(gè)人理解對(duì) channel 使用過(guò)程中應(yīng)該注意的地方進(jìn)行一個(gè)簡(jiǎn)要的總結(jié)。
    2018-10-10
  • go基礎(chǔ)語(yǔ)法50問(wèn)及方法詳解

    go基礎(chǔ)語(yǔ)法50問(wèn)及方法詳解

    這篇文章主要為大家介紹了go基礎(chǔ)語(yǔ)法50問(wèn)及方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Go語(yǔ)言實(shí)現(xiàn)并發(fā)控制的常見(jiàn)方式詳解

    Go語(yǔ)言實(shí)現(xiàn)并發(fā)控制的常見(jiàn)方式詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)并發(fā)控制的幾種常見(jiàn)方式,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下
    2024-03-03
  • golang中的struct操作

    golang中的struct操作

    結(jié)構(gòu)體是一種聚合的數(shù)據(jù)類型,是由零個(gè)或多個(gè)任意類型的值聚合成的實(shí)體,每個(gè)值稱為結(jié)構(gòu)體的成員。下面介紹下golang中的struct,感興趣的朋友一起看看吧
    2021-11-11

最新評(píng)論