最近接觸付款頁面的設計,原先系統已提供兩種付款方式(國內信用卡與ATM),並透過點擊不同支付方式的 radio button 切換付款顯示的介面!因為要新增國外信用卡的功能,需要理解原先的做法,且以往都是以 button group 的設計介面切換,這次用 radio button 來做這種切換功能倒是第一次!順便紀錄幾個當時採坑的觀念
preventDefault 是什麼呢?它是預防觸發瀏覽器預設事件的方法,例如預防按鈕、超連結點擊後跳轉到其他頁面。這個方法常用在 clickEvent,但對 changeEvent 不 work。
有人解釋說是 changeEvent 處理的事已經發生的行為,而 clickEvent 處理正在發生的行為,所以已經發生的行為是無法預防,能預防的只有正要發生的事!
另一個解釋是 changeEvent 的 cancelable 的屬性是 false,這意思是事件本身是無法取消的,只有在事件的 cancelable 是 true 的狀況下,preventDefault 才有作用!
$("#text1").change(function(e) {
alert(e.cancelable?"Is cancelable":"Not cancelable");
});
”preventDefault() not working for change event“的討論
有時候你想在某些情境下,限制使用者修改表單上的某些欄位,如果是 input 欄位可以加上 readonly 屬性,等同在欄位上加了鎖,且只有 input 和 textarea 擁有 readonly 的屬性。
如果你想鎖住像 button, select, radio button, checkbox,方法就是使用 disabled 屬性,注意 disabled 的按鈕,它的 value 是無法隨著表單一起 submit 的,但 readonly 沒有這個限制!
// 在 jquery 將未選取的 radio butoon 鎖起來
$('input[type=radio][name="your-name"]:not(:checked)').attr('disabled', true);
最近接觸付款頁面的設計,原先系統已提供兩種付款方式(國內信用卡與ATM),並透過點擊不同支付方式的 radio button 切換付款顯示的介面!因為要新增國外信用卡的功能,需要理解原先的做法,且以往都是以 button group 的設計介面切換,這次用 radio button 來做這種切換功能倒是第一次!順便紀錄幾個當時採坑的觀念
preventDefault 是什麼呢?它是預防觸發瀏覽器預設事件的方法,例如預防按鈕、超連結點擊後跳轉到其他頁面。這個方法常用在 clickEvent,但對 changeEvent 不 work。
有人解釋說是 changeEvent 處理的事已經發生的行為,而 clickEvent 處理正在發生的行為,所以已經發生的行為是無法預防,能預防的只有正要發生的事!
另一個解釋是 changeEvent 的 cancelable 的屬性是 false,這意思是事件本身是無法取消的,只有在事件的 cancelable 是 true 的狀況下,preventDefault 才有作用!
$("#text1").change(function(e) {
alert(e.cancelable?"Is cancelable":"Not cancelable");
});
”preventDefault() not working for change event“的討論
有時候你想在某些情境下,限制使用者修改表單上的某些欄位,如果是 input 欄位可以加上 readonly 屬性,等同在欄位上加了鎖,且只有 input 和 textarea 擁有 readonly 的屬性。
如果你想鎖住像 button, select, radio button, checkbox,方法就是使用 disabled 屬性,注意 disabled 的按鈕,它的 value 是無法隨著表單一起 submit 的,但 readonly 沒有這個限制!
// 在 jquery 將未選取的 radio butoon 鎖起來
$('input[type=radio][name="your-name"]:not(:checked)').attr('disabled', true);
了解瀏覽器渲染的管道(Browser Rendering pipeline),以解決渲染效能的議題(Rendering Performance Issue)。
顫抖(Juddering),想像畫面上有個按鈕或圖片持續的抖動~~。課:juddering
造成 juddering 的原因,先了解瀏覽器會以每秒 60 次的頻率更新螢幕上的 frame(影格),又稱 1 秒 60Hz 或 60fps ( 1 秒又是 1000 毫秒,所以 1 個 frame 在畫面上大約只停留了 16ms (1000 / 60 = 16.66ms)) 課:frames
若瀏覽器無法達成每秒 60 Hz,使用者就會感受到有東西在抖動(juddering),如果是圖片就會產生像殘影的東西。
相同的筆記整理
參考PJ教學
課:Layout and Paint
這裡提到渲染管道(Rendering pipeline),是指在一個動態網頁,當調整某些css屬性時對渲染流程的影響。搭配前一段產生一個frame的流程
第一種
更新畫面上的元件,像是調整 width, height, position, display。它的 pipeline 會經過 Javascript > Style > layout > Paint > Composite
第二種
更新繪圖相關的屬性,例如改變 background-image, color, text-color, shadow。它的 pipeline 會經過 Javascript > Style > 略過layout > Paint > Composite
第三種
更新圖層相關的屬性,例如改變layer的順序它的 pipeline 會經過 Javascript > Style > 略過layout > 略過Paint > Composite
。
觀察上述的三種路徑,可以發現無論何種變化都會經過 Style ,不同 style 影響到管線的選擇,並影響到 web 的渲染效能。
這裡有個 css 屬性與受影響的 rendering pipeline 對照表
https://csstriggers.com/background-color
了解瀏覽器渲染的管道(Browser Rendering pipeline),以解決渲染效能的議題(Rendering Performance Issue)。
顫抖(Juddering),想像畫面上有個按鈕或圖片持續的抖動~~。課:juddering
造成 juddering 的原因,先了解瀏覽器會以每秒 60 次的頻率更新螢幕上的 frame(影格),又稱 1 秒 60Hz 或 60fps ( 1 秒又是 1000 毫秒,所以 1 個 frame 在畫面上大約只停留了 16ms (1000 / 60 = 16.66ms)) 課:frames
若瀏覽器無法達成每秒 60 Hz,使用者就會感受到有東西在抖動(juddering),如果是圖片就會產生像殘影的東西。
相同的筆記整理
參考PJ教學
課:Layout and Paint
這裡提到渲染管道(Rendering pipeline),是指在一個動態網頁,當調整某些css屬性時對渲染流程的影響。搭配前一段產生一個frame的流程
第一種
更新畫面上的元件,像是調整 width, height, position, display。它的 pipeline 會經過 Javascript > Style > layout > Paint > Composite
第二種
更新繪圖相關的屬性,例如改變 background-image, color, text-color, shadow。它的 pipeline 會經過 Javascript > Style > 略過layout > Paint > Composite
第三種
更新圖層相關的屬性,例如改變layer的順序它的 pipeline 會經過 Javascript > Style > 略過layout > 略過Paint > Composite
。
觀察上述的三種路徑,可以發現無論何種變化都會經過 Style ,不同 style 影響到管線的選擇,並影響到 web 的渲染效能。
這裡有個 css 屬性與受影響的 rendering pipeline 對照表
https://csstriggers.com/background-color
想在哪裡呼叫都可以!!!
myFunc(10, 20);
var myFunc = function(a, b) {
console.log('a:', a, ', b:', b);
}
myFunc(10, 20);
要注意呼叫的位置!!!
myFunc(10, 20) // 放這邊會噴錯! 因為myFunc還是undefined
var myFunc = function(a, b) {
console.log('a:', a, ', b:', b);
}
myFunc(10, 20) // 放這邊ok! 因為myFunc已經是function了
具名的額外好處是會顯示回傳錯誤!
var myFunc = function show(a, b) {
return x
}
myFunc(10, 20) // error: .... at show
表達式的另一個觀念是具名函數只有在函式內部有效
var myFunc = function factorial(n) {
let x = (n == 1 ? n : n * factorial(n-1));
console.log(n + " > " + x)
return x
}
myFunc(5) // 120
先來看一般表達式的執行結果
var myFunc = function() {
return "hello";
}
console.log(myFunc) // return function itself
console.log(typeof myFunc) // type is function
console.log(myFunc()) // hello
再來看使用IIFE的執行結果,可以發現執行完,function就不存在了
var myFunc = function() {
return "hello";
}();
console.log(myFunc) // hello
console.log(typeof myFunc) // type is string
console.log(myFunc()) // error => myFunc is not function
IIFE搭配參數
var myFunc = function(a, b) {
return a+b;
}(10, 20);
console.log(myFunc) // 30
此外,如何宣告一個IIFE呢?
function() { // 錯誤版
console.log("hello");
}();
function myFunc() { // 錯誤版
console.log("hello");
}();
(function myFunc() { // 正確
console.log("hello");
})(); // 或者也可以
(function myFunc() { // 正確
console.log("hello");
}());
var myfunc = () => console.log("hello");
function func(x,y,cb) {
let num = x+y;
cb(num);
}
func(10, 20, (num) => {
console.log("num: ", num); // num: 30
});
function alertFunc() {
console.log("Hello!");
}
setTimeout(alertFunc, 3000);
學習筆記參考:Javascript精選16堂課:網頁程式設計實作
]]>想在哪裡呼叫都可以!!!
myFunc(10, 20);
var myFunc = function(a, b) {
console.log('a:', a, ', b:', b);
}
myFunc(10, 20);
要注意呼叫的位置!!!
myFunc(10, 20) // 放這邊會噴錯! 因為myFunc還是undefined
var myFunc = function(a, b) {
console.log('a:', a, ', b:', b);
}
myFunc(10, 20) // 放這邊ok! 因為myFunc已經是function了
具名的額外好處是會顯示回傳錯誤!
var myFunc = function show(a, b) {
return x
}
myFunc(10, 20) // error: .... at show
表達式的另一個觀念是具名函數只有在函式內部有效
var myFunc = function factorial(n) {
let x = (n == 1 ? n : n * factorial(n-1));
console.log(n + " > " + x)
return x
}
myFunc(5) // 120
先來看一般表達式的執行結果
var myFunc = function() {
return "hello";
}
console.log(myFunc) // return function itself
console.log(typeof myFunc) // type is function
console.log(myFunc()) // hello
再來看使用IIFE的執行結果,可以發現執行完,function就不存在了
var myFunc = function() {
return "hello";
}();
console.log(myFunc) // hello
console.log(typeof myFunc) // type is string
console.log(myFunc()) // error => myFunc is not function
IIFE搭配參數
var myFunc = function(a, b) {
return a+b;
}(10, 20);
console.log(myFunc) // 30
此外,如何宣告一個IIFE呢?
function() { // 錯誤版
console.log("hello");
}();
function myFunc() { // 錯誤版
console.log("hello");
}();
(function myFunc() { // 正確
console.log("hello");
})(); // 或者也可以
(function myFunc() { // 正確
console.log("hello");
}());
var myfunc = () => console.log("hello");
function func(x,y,cb) {
let num = x+y;
cb(num);
}
func(10, 20, (num) => {
console.log("num: ", num); // num: 30
});
function alertFunc() {
console.log("Hello!");
}
setTimeout(alertFunc, 3000);
學習筆記參考:Javascript精選16堂課:網頁程式設計實作
]]>還是研究生的時候,參加研討會的心態大多是等一下上台認真報告,報完回到台下認真耍廢!與現在單純為了喜歡或追求個人的成長有很大的轉變!今年剛好在找到第一份正式工作後參加的第一場大型年會,寫份心得紀念一下。
7/31週五剛好是公司的remote day,下午將工作結束一個段落後到了台北!不知道主辦單位的pre-party是個什麼樣子,我腦中閃過的畫面是有舞池跟Dj的那種,笑~玩那麼大,應該不會吧!
來到現場,發現原來是 尬聊~哎 好尷尬!既沒有跟認識的人一起來,也沒有出現過任何社群!要真的跟別人硬聊嗎?那要聊什麼?內心有各種掙扎!在試過一段有點尷尬的對話後,就有點想放棄了!換個角度想,其實沒有來這個派對,可能也不會發現台北有這樣一個能放輕鬆喝酒的角落(值得再訪),週五涼爽的夜晚,看101的城市夜景,蠻自在的時光!
兩天的活動地點在台科大,這一天聽了蠻多非技術,純談價值觀或工作心得分享的講次,像是與目前兼職PM工作切身相關主題『溝通的藝術,專案經理與工程師的二三事』,『Google和Orocle在JAVA API的智財權訴訟案分享』,單純好奇法律怎麼檢驗違法或合理使用!中午後原本計劃去聽唐鳳的那場演講,但才剛從教室A離開在要去的路上,就聽說已經爆滿了!口罩地圖也是!最後放棄,窩在GCG的議程軌的教室,待到最後一場講次結束!題外話,有一場介紹Google App Script利用雲端文書軟體來做office automation還蠻有趣,但會不會本來想自動化來減輕自己的負擔,到頭來卻在自動化的過程給累倒了呢!
終於,期待已久的Vue作者Evan要來跟大家分享,Vue3以及Vue的開源發展。因為疫情的關係,本人無法蒞臨現場,但為大會錄了一段影片,可惜英文的語速太快同時又提到很多技術名詞,演講時間不到一半自己就先離線了,對整場的期待打了一點折扣😢
之後的時間,大多在ruby議程軌的教室(直接變成五倍教室),坐好坐滿,心想是否能聽到一些近期開發上用得到的技術!很開心,有幾場是beginner-friendly的演講,例如用Rails Engine打包開源專案,Rails結構化專案,以及有一場談Junior工程師的存活術,講者分享一些給Junior的成長建議!我想到同樣的主題,我老闆也有寫一份Senior Rails 升級指南,以對task的交付完成度劃出J1, J2, J3, S1, S2, S3,六個階段專業能力的分水嶺,裡面有蠻具體的成長指標參考。
時間來到中午的休息時間,走回公館捷運附近,想到在公館附近實習的日子,常去吃的一間巷子內的海南雞飯,有點懷念他們的味道所以繞了點路過去,還好今天有開!下午慢慢那種friendly的感覺消失了!!! Ruby Service Object,這個有點難懂,需要多動手寫點code跟看比較多實際的case才能體會到一點sense,到 ruby meta programming 就已經完全超速,飛出我的理解範圍了!還好一天下來活動也差不多結束了。
好像有些收穫,但實際好像又沒有,像極了愛情🙄 第一次參加COSCUP這樣的大會,最大的感想就是鼓勵每個人出來交流,想到得還是那句話『一個人可以走得快,一群人能走得遠』。或許目前除了工作中外,也該為參與一些線下的社群活動了吧,期待有天也能累積點東西與別人分享!
]]>還是研究生的時候,參加研討會的心態大多是等一下上台認真報告,報完回到台下認真耍廢!與現在單純為了喜歡或追求個人的成長有很大的轉變!今年剛好在找到第一份正式工作後參加的第一場大型年會,寫份心得紀念一下。
7/31週五剛好是公司的remote day,下午將工作結束一個段落後到了台北!不知道主辦單位的pre-party是個什麼樣子,我腦中閃過的畫面是有舞池跟Dj的那種,笑~玩那麼大,應該不會吧!
來到現場,發現原來是 尬聊~哎 好尷尬!既沒有跟認識的人一起來,也沒有出現過任何社群!要真的跟別人硬聊嗎?那要聊什麼?內心有各種掙扎!在試過一段有點尷尬的對話後,就有點想放棄了!換個角度想,其實沒有來這個派對,可能也不會發現台北有這樣一個能放輕鬆喝酒的角落(值得再訪),週五涼爽的夜晚,看101的城市夜景,蠻自在的時光!
兩天的活動地點在台科大,這一天聽了蠻多非技術,純談價值觀或工作心得分享的講次,像是與目前兼職PM工作切身相關主題『溝通的藝術,專案經理與工程師的二三事』,『Google和Orocle在JAVA API的智財權訴訟案分享』,單純好奇法律怎麼檢驗違法或合理使用!中午後原本計劃去聽唐鳳的那場演講,但才剛從教室A離開在要去的路上,就聽說已經爆滿了!口罩地圖也是!最後放棄,窩在GCG的議程軌的教室,待到最後一場講次結束!題外話,有一場介紹Google App Script利用雲端文書軟體來做office automation還蠻有趣,但會不會本來想自動化來減輕自己的負擔,到頭來卻在自動化的過程給累倒了呢!
終於,期待已久的Vue作者Evan要來跟大家分享,Vue3以及Vue的開源發展。因為疫情的關係,本人無法蒞臨現場,但為大會錄了一段影片,可惜英文的語速太快同時又提到很多技術名詞,演講時間不到一半自己就先離線了,對整場的期待打了一點折扣😢
之後的時間,大多在ruby議程軌的教室(直接變成五倍教室),坐好坐滿,心想是否能聽到一些近期開發上用得到的技術!很開心,有幾場是beginner-friendly的演講,例如用Rails Engine打包開源專案,Rails結構化專案,以及有一場談Junior工程師的存活術,講者分享一些給Junior的成長建議!我想到同樣的主題,我老闆也有寫一份Senior Rails 升級指南,以對task的交付完成度劃出J1, J2, J3, S1, S2, S3,六個階段專業能力的分水嶺,裡面有蠻具體的成長指標參考。
時間來到中午的休息時間,走回公館捷運附近,想到在公館附近實習的日子,常去吃的一間巷子內的海南雞飯,有點懷念他們的味道所以繞了點路過去,還好今天有開!下午慢慢那種friendly的感覺消失了!!! Ruby Service Object,這個有點難懂,需要多動手寫點code跟看比較多實際的case才能體會到一點sense,到 ruby meta programming 就已經完全超速,飛出我的理解範圍了!還好一天下來活動也差不多結束了。
好像有些收穫,但實際好像又沒有,像極了愛情🙄 第一次參加COSCUP這樣的大會,最大的感想就是鼓勵每個人出來交流,想到得還是那句話『一個人可以走得快,一群人能走得遠』。或許目前除了工作中外,也該為參與一些線下的社群活動了吧,期待有天也能累積點東西與別人分享!
]]>這裡所指的介面(Interfaces)不是圖形化的那種,而是可以想像職缺與求職者那樣的關係。比方有個大廚的職缺,定義主廚要具備的能力,來應徵大廚的人就是要滿足這些能力並實際執行的人。換言之:
舉個實際的例子,這有兩個結構分別是 cat 跟 snak,若要形容個別動物的特徵,我可以定義一個介面並宣告裡面有一個description的方法,並讓cat、snack各自完成實作。如果後續還加入了其他的動物,並要描述它的特徵,我便可以繼續沿用這個介面。
type animal interface {
description() string
}
type cat struct {
Type string
Sound string
}
type snake struct {
Type string
Poisonous bool
}
// 實作描述snake的方法,帶入snake的結構
func (s snake) description() string {
return fmt.Sprintf("Poisonous: %v", s.Poisonous)
}
// 實作描述cat的方法,帶入cat的結構
func (c cat) description() string {
return fmt.Sprintf("Sound: %v", c.Sound)
}
func main() {
var a animal
a = snake{Poisonous: true}
fmt.Println(a.description()) //
a = cat{Sound: "Meow!!!"}
fmt.Println(a.description()) //
}
另一個Example,定義了一個geometry(幾何)的介面與兩個結構rect(矩形)和circle(圓),並為兩個結構實作介面的方法。實務上會設計一個調用函式來為不同的結構調用介面方法,這裡設計一個measure來印出變數 r 和 c 的計算結果。Go by Example: Interfaces
package main
import (
"fmt"
"math"
)
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r) // {3 4} => g
// 12 => g.area()
// 14 => g.perim()
measure(c) // {5} => g
// 78.53981633974483 => g.area()
// 31.41592653589793 => g.perim()
}
通常會使用type與interface來定義介面,這個概念是前面提到的方法介面。
type Fruit interface {
// 數組方法
}
然而,當方法介面裏頭沒有定義任何方法和type時,它就是空介面(Empty Interfaces)。
Interface {}
Example: 宣告空介面 i ,指派不同值並用fmt.Printf印出值與型別
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i) // 印出值與型別
}
func main() {
// 宣告一個空介面
var i interface{}
describe(i) // (<nil>, <nil>)
i = 42
describe(i) // (42, int)
i = "hello"
describe(i) // (hello, string)
}
t := i.(T)
t, ok := i.(T)
Example: 檢查變數i的型別
func main() {
var i interface{} = "hello" // 給定字串值,現在正確型別為字串
s := i.(string)
fmt.Println(s) // hello
s, ok := i.(string)
fmt.Println(s, ok) // hello true
f, ok := i.(float64)
fmt.Println(f, ok) // 0 false
f = i.(float64) // 程式執行中斷
fmt.Println(f) // panic: interface conversion: interface {} is string, not float64
}
t := i.(T)
相同,但是 T 要改為type關鍵字switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
Example: 用do()來判斷值的型別
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21) // Twice 21 is 42
do("hello") // "hello" is 5 bytes long
do(true) // I don't know about type bool!
}
官網教學
Golang - Interfaces(Leon's Blog => 補充不少實作與解釋的部分)
Go by example
這裡所指的介面(Interfaces)不是圖形化的那種,而是可以想像職缺與求職者那樣的關係。比方有個大廚的職缺,定義主廚要具備的能力,來應徵大廚的人就是要滿足這些能力並實際執行的人。換言之:
舉個實際的例子,這有兩個結構分別是 cat 跟 snak,若要形容個別動物的特徵,我可以定義一個介面並宣告裡面有一個description的方法,並讓cat、snack各自完成實作。如果後續還加入了其他的動物,並要描述它的特徵,我便可以繼續沿用這個介面。
type animal interface {
description() string
}
type cat struct {
Type string
Sound string
}
type snake struct {
Type string
Poisonous bool
}
// 實作描述snake的方法,帶入snake的結構
func (s snake) description() string {
return fmt.Sprintf("Poisonous: %v", s.Poisonous)
}
// 實作描述cat的方法,帶入cat的結構
func (c cat) description() string {
return fmt.Sprintf("Sound: %v", c.Sound)
}
func main() {
var a animal
a = snake{Poisonous: true}
fmt.Println(a.description()) //
a = cat{Sound: "Meow!!!"}
fmt.Println(a.description()) //
}
另一個Example,定義了一個geometry(幾何)的介面與兩個結構rect(矩形)和circle(圓),並為兩個結構實作介面的方法。實務上會設計一個調用函式來為不同的結構調用介面方法,這裡設計一個measure來印出變數 r 和 c 的計算結果。Go by Example: Interfaces
package main
import (
"fmt"
"math"
)
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r) // {3 4} => g
// 12 => g.area()
// 14 => g.perim()
measure(c) // {5} => g
// 78.53981633974483 => g.area()
// 31.41592653589793 => g.perim()
}
通常會使用type與interface來定義介面,這個概念是前面提到的方法介面。
type Fruit interface {
// 數組方法
}
然而,當方法介面裏頭沒有定義任何方法和type時,它就是空介面(Empty Interfaces)。
Interface {}
Example: 宣告空介面 i ,指派不同值並用fmt.Printf印出值與型別
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i) // 印出值與型別
}
func main() {
// 宣告一個空介面
var i interface{}
describe(i) // (<nil>, <nil>)
i = 42
describe(i) // (42, int)
i = "hello"
describe(i) // (hello, string)
}
t := i.(T)
t, ok := i.(T)
Example: 檢查變數i的型別
func main() {
var i interface{} = "hello" // 給定字串值,現在正確型別為字串
s := i.(string)
fmt.Println(s) // hello
s, ok := i.(string)
fmt.Println(s, ok) // hello true
f, ok := i.(float64)
fmt.Println(f, ok) // 0 false
f = i.(float64) // 程式執行中斷
fmt.Println(f) // panic: interface conversion: interface {} is string, not float64
}
t := i.(T)
相同,但是 T 要改為type關鍵字switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
Example: 用do()來判斷值的型別
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21) // Twice 21 is 42
do("hello") // "hello" is 5 bytes long
do(true) // I don't know about type bool!
}
官網教學
Golang - Interfaces(Leon's Blog => 補充不少實作與解釋的部分)
Go by example
理解Go世界中對屬性(attribute)和方法(method)的活用;pass by value和pass by reference的差異與做法,並要知道如何一些編譯錯誤的原因。
物件導向程式有類別(class)與物件(object)的觀念
然而,在Go的世界裡,沒有完全相似類別(class)的概念! 屬性(attribute)由struct取代;方法(method)是透過func加上自定義的struct;變數透過宣告實例化,如同物件獲得了狀態並透過不同的方法產生如物件的行為。
package main
import (
"fmt"
"math"
)
// 透過Vertex結構產生節點物件
type Vertex struct {
X, Y float64
}
// 傳入v的值,這個值是一個struct的型態
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) // 結果 5
}
延續前一個例子,這裡我加入Pointer Receiver的method叫Scale。Scale()採用Pointer Reciever,它直接指向v的位置且能夠對裏頭的X和Y的值進行修改(pass by reference),相較於,Abs()採用的是Value Recieiver,它得到的是v的備份內容,並在這備份上修改(pass by value),不會影響原來的v。實務上,Pointer Receiver會比Value Receiver更常用到。
...
// pass by value/ Value receiver
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// pass by reference/ Pointer receiver
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs()) // 50
fmt.Println(v) // {30 40}
}
這裡將method的寫法變化一下,由於method本身就是函式,因此,除了把Receiver置於方法名稱前,也可以置於名稱後的括弧中。
...
// 將Value Receiver放在Abs()中
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 將Pointer Receiver放在Scale()中
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
// 注意其使用方式也不一樣!!
Scale(&v, 10)
fmt.Println(Abs(v)) // 50
fmt.Println(v) // {30 40}
}
對於使用Pointer Receiver的方法而言,即使v是值(value),Scale()也會在執行編譯時將v.Sacle(5)自動轉換成(&v).Scale(5)。
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
以下是使用Pointer Receiver Method的例子,特別注意到v是value和p是指標,兩者都可以使用Scale(),而不會產生編譯問題。但如果是採用ScaleFunc()就要特別注意了!
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2) // v是value,將v.Scale(2)自動轉換成(&v).Scale(2)
ScaleFunc(&v, 10) // 注意,&v
p := &Vertex{4, 3}
p.Scale(3) // p是pointer
ScaleFunc(p, 8) // 注意,p
fmt.Println(v, p) // {60 80} &{96 72}
fmt.Println(v, *p) // {60 80} {96 72}
}
對於使用Value Receiver的方法而言,v是值(value),Abs()可以直接使用,p是指標(pointer)的話,p.Abs()在執行編譯時會自動轉換成(*p).Abs()。
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
以下是使用Value Receiver Method的例子,特別注意到v是value和p是指標,Abs(),而不會產生編譯問題。如果是採用AbsFunc()就要特別注意了!
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) // 5
fmt.Println(AbsFunc(v)) // 5
p := &Vertex{4, 3}
fmt.Println(p.Abs()) // 5
fmt.Println(AbsFunc(*p)) // 5
}
理解Go世界中對屬性(attribute)和方法(method)的活用;pass by value和pass by reference的差異與做法,並要知道如何一些編譯錯誤的原因。
物件導向程式有類別(class)與物件(object)的觀念
然而,在Go的世界裡,沒有完全相似類別(class)的概念! 屬性(attribute)由struct取代;方法(method)是透過func加上自定義的struct;變數透過宣告實例化,如同物件獲得了狀態並透過不同的方法產生如物件的行為。
package main
import (
"fmt"
"math"
)
// 透過Vertex結構產生節點物件
type Vertex struct {
X, Y float64
}
// 傳入v的值,這個值是一個struct的型態
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) // 結果 5
}
延續前一個例子,這裡我加入Pointer Receiver的method叫Scale。Scale()採用Pointer Reciever,它直接指向v的位置且能夠對裏頭的X和Y的值進行修改(pass by reference),相較於,Abs()採用的是Value Recieiver,它得到的是v的備份內容,並在這備份上修改(pass by value),不會影響原來的v。實務上,Pointer Receiver會比Value Receiver更常用到。
...
// pass by value/ Value receiver
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// pass by reference/ Pointer receiver
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs()) // 50
fmt.Println(v) // {30 40}
}
這裡將method的寫法變化一下,由於method本身就是函式,因此,除了把Receiver置於方法名稱前,也可以置於名稱後的括弧中。
...
// 將Value Receiver放在Abs()中
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 將Pointer Receiver放在Scale()中
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
// 注意其使用方式也不一樣!!
Scale(&v, 10)
fmt.Println(Abs(v)) // 50
fmt.Println(v) // {30 40}
}
對於使用Pointer Receiver的方法而言,即使v是值(value),Scale()也會在執行編譯時將v.Sacle(5)自動轉換成(&v).Scale(5)。
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
以下是使用Pointer Receiver Method的例子,特別注意到v是value和p是指標,兩者都可以使用Scale(),而不會產生編譯問題。但如果是採用ScaleFunc()就要特別注意了!
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2) // v是value,將v.Scale(2)自動轉換成(&v).Scale(2)
ScaleFunc(&v, 10) // 注意,&v
p := &Vertex{4, 3}
p.Scale(3) // p是pointer
ScaleFunc(p, 8) // 注意,p
fmt.Println(v, p) // {60 80} &{96 72}
fmt.Println(v, *p) // {60 80} {96 72}
}
對於使用Value Receiver的方法而言,v是值(value),Abs()可以直接使用,p是指標(pointer)的話,p.Abs()在執行編譯時會自動轉換成(*p).Abs()。
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
以下是使用Value Receiver Method的例子,特別注意到v是value和p是指標,Abs(),而不會產生編譯問題。如果是採用AbsFunc()就要特別注意了!
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) // 5
fmt.Println(AbsFunc(v)) // 5
p := &Vertex{4, 3}
fmt.Println(p.Abs()) // 5
fmt.Println(AbsFunc(*p)) // 5
}
學習寫第一個Go的單元測試的作法與了解它的使用方式。
為什麼要寫測試?
在動手寫測試以前得先知道測試要寫在哪裡。一般在Go的環境中,測試檔案要另外寫在一個檔案並放在同一個路徑下,且命名要是程式執行檔名加 _test,例如我的程式實際執行在main.go,我的測試就會寫在main_test.go裡,並執行go test
來測試。
# 專案結構
myproject/
- calc.go
- calc_test.go
- main.go
- main_test.go
這裡簡單在main.go寫一個回傳x+2結果的函式,並印出結果。接著在同一個目錄底下新增一個main_test.go,引入Go提供的testing套件,並寫一個test case。
# main.go
package main
import "fmt"
func Calculate(x int) (result int){
result = x + 2
return result
}
func main() {
fmt.Println("main start!!")
cal := Calculate(2)
fmt.Println("cal:", cal)
}
---
# main_test.go
package main
import "testing"
func TestCalculate(t *testing.T) {
if Calculate(2) != 4 { // 如果結果不為4,就噴出error裡的內容
t.Error("Expected 2 + 2 to equal 4")
}
}
由於,Calculate(2)結果必為4,所以通過所有測試。go test
會幫忙執行所有_test.go的test case,如果想要看到詳細的測試輸出(verbose Test output),例如那些功能被執行、那些通過可以執行go test -v
。
nacho@ubuntu:~/go/simpleTest$ go test
PASS
ok _/home/nacho/go/simpleTest 0.003s
此外,也可以設計Table driven testing來為不同範圍的輸入輸出值來進行測試,所以測試相同的函式不需要再做重複的copy&past,此外,設計多組測試也能降低Edge case跟bug在程式實際上線後才發現的情況。
# main_test.go
...
func TestTableCalculate(t * testing.T){
var tests = []struct {
input int
expected int
}{
{2, 4},
{-1, 1},
{0, 2},
{-5, -3},
{9999, 10001},
}
for _, test := range tests {
if output := Calculate(test.input); output != test.expected {
t.Error("Test Failed: input {}, expect {}, should receive: {}", test.input, test.expected, output)
}
}
}
詳細的測試輸出結果。
nacho@ubuntu:~/go/simpleTest$ go test -v
=== RUN TestCalculate
--- PASS: TestCalculate (0.00s)
=== RUN TestTableCalculate
--- PASS: TestTableCalculate (0.00s)
PASS
ok _/home/nacho/go/simpleTest 0.008s
Go還提供另一個方便的參數-cover,讓開發者能評估測試的覆蓋率(coverage)。覆蓋率是一個參考,但高覆蓋率也代表這支程式完全freebug或功能完全運作正常。不需要為所有的功能撰寫詳盡的測試,以追求高覆蓋率,而要將重點放在於重要/核心的business logic上,並確保能cover到更多可能的Edge cases發生才能發揮測試做大的價值。
nacho@ubuntu:~/go/simpleTest$ go test -cover
PASS
coverage: 40.0% of statements
ok _/home/nacho/go/simpleTest 0.003s
學習寫第一個Go的單元測試的作法與了解它的使用方式。
為什麼要寫測試?
在動手寫測試以前得先知道測試要寫在哪裡。一般在Go的環境中,測試檔案要另外寫在一個檔案並放在同一個路徑下,且命名要是程式執行檔名加 _test,例如我的程式實際執行在main.go,我的測試就會寫在main_test.go裡,並執行go test
來測試。
# 專案結構
myproject/
- calc.go
- calc_test.go
- main.go
- main_test.go
這裡簡單在main.go寫一個回傳x+2結果的函式,並印出結果。接著在同一個目錄底下新增一個main_test.go,引入Go提供的testing套件,並寫一個test case。
# main.go
package main
import "fmt"
func Calculate(x int) (result int){
result = x + 2
return result
}
func main() {
fmt.Println("main start!!")
cal := Calculate(2)
fmt.Println("cal:", cal)
}
---
# main_test.go
package main
import "testing"
func TestCalculate(t *testing.T) {
if Calculate(2) != 4 { // 如果結果不為4,就噴出error裡的內容
t.Error("Expected 2 + 2 to equal 4")
}
}
由於,Calculate(2)結果必為4,所以通過所有測試。go test
會幫忙執行所有_test.go的test case,如果想要看到詳細的測試輸出(verbose Test output),例如那些功能被執行、那些通過可以執行go test -v
。
nacho@ubuntu:~/go/simpleTest$ go test
PASS
ok _/home/nacho/go/simpleTest 0.003s
此外,也可以設計Table driven testing來為不同範圍的輸入輸出值來進行測試,所以測試相同的函式不需要再做重複的copy&past,此外,設計多組測試也能降低Edge case跟bug在程式實際上線後才發現的情況。
# main_test.go
...
func TestTableCalculate(t * testing.T){
var tests = []struct {
input int
expected int
}{
{2, 4},
{-1, 1},
{0, 2},
{-5, -3},
{9999, 10001},
}
for _, test := range tests {
if output := Calculate(test.input); output != test.expected {
t.Error("Test Failed: input {}, expect {}, should receive: {}", test.input, test.expected, output)
}
}
}
詳細的測試輸出結果。
nacho@ubuntu:~/go/simpleTest$ go test -v
=== RUN TestCalculate
--- PASS: TestCalculate (0.00s)
=== RUN TestTableCalculate
--- PASS: TestTableCalculate (0.00s)
PASS
ok _/home/nacho/go/simpleTest 0.008s
Go還提供另一個方便的參數-cover,讓開發者能評估測試的覆蓋率(coverage)。覆蓋率是一個參考,但高覆蓋率也代表這支程式完全freebug或功能完全運作正常。不需要為所有的功能撰寫詳盡的測試,以追求高覆蓋率,而要將重點放在於重要/核心的business logic上,並確保能cover到更多可能的Edge cases發生才能發揮測試做大的價值。
nacho@ubuntu:~/go/simpleTest$ go test -cover
PASS
coverage: 40.0% of statements
ok _/home/nacho/go/simpleTest 0.003s
延續之前的寫的Simple RESTful API,加入解析JSON的功能使API可以存取JSON資料。
the small wiki of JSON(參考)
先進行專案的基礎建設,開一個新專案叫simpleJson,新增一個檔案叫simplejson.go,並導入了套件gorilla/mux。用這行指令$ go get -u github.com/gorilla/mux
取得這個套件。
package main
import(
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "welcome home!")
}
func main(){
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(":8080", router))
}
首先,宣告一個叫event的結構,json格式的宣告附在value之後。宣告一個allEvents的slice用來存取多筆event,並在events初始化兩筆event,另外,還需要匯入encoding套件裡的json函示庫,之後要透過它來解析json資料。
注意: 在struct的宣告中key必須是大寫開頭,否則會拋出錯誤(切身經驗),但可以在json內將key轉換成小寫!! 原因
import(
"encoding/json"
...
)
type event struct {
Id string `json:"id"`
Title string `json:"title"`
Desc string `json:"desc"`
}
type allEvents []event
var events = allEvents{ // 加入一些events
{
Id: "1", Title: "test1", Desc: "this is the first test",
},
{
Id: "2", Title: "test2", Desc: "this is the second test",
},
}
加入一個getAllEvents的func,內容是將回傳值透過json加入解析後的events資料。打開Terminal使用curl -X GET localhost:8080/events
可以看到回傳值是一串JSON解析後的格式,如果與fmt.Println印出來的結果相比應該會發現JSON提供完整的key/value。
func getAllEvents(w http.ResponseWriter, r *http.Request){
json.NewEncoder(w).Encode(events)
fmt.Println("call all events api")
// fmt.Println(events)
}
func main(){
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", home)
router.HandleFunc("/events", getAllEvents).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", router))
}
新增一筆event,這裡使用到io套件的ioutil功能,ioutil.ReadAll(r.Body)讀取API傳來的HTTP body。Go透過 json.Unmarshal 將JSON字串轉成 struct(json.marshal則相反),所以我將reqBody的值轉換後指給newEvent,並將它加入events中,最後回傳成功結果給client。打開Terminal用curl -X POST -d '{"id":"3","title":"test3","desc":"This is the third test"}' localhost:8080/event
import(
"io/ioutil"
...
)
func createEvent(w http.ResponseWriter, r *http.Request){
var newEvent event
reqBody, _ := ioutil.ReadAll(r.Body)
// 轉換成 event struct
json.Unmarshal(reqBody, &newEvent)
events = append(events, newEvent)
// 201 created status code
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newEvent)
}
func main(){
..
router.HandleFunc("/event", createEvent).Methods("POST")
..
}
取得其中一筆event,假設我在Terminal輸入curl -X GET localhost:8080/events/1
,mux幫我解析這串url,若是發現符合events/{id}的格式,mux.Vars(r)["id"]幫我把id的值取出給路徑變數,接著在迴圈中找出有等於這個id的event,並回傳給client。
func getOneEvent(w http.ResponseWriter, r *http.Request) {
// 宣告路徑變數,解析 url取出 id 的值
eventId := mux.Vars(r)["id"]
// loop 找出對應 id
for _, singleEvent := range events {
if singleEvent.Id == eventId {
json.NewEncoder(w).Encode(singleEvent)
}
}
}
func main(){
..
router.HandleFunc("/events/{id}", getOneEvent).Methods("GET")
..
}
另外,還有編輯跟刪除event的功能,因為概念與新增和讀取是相似的,所以不多做紀錄。
完整範例參考
延續之前的寫的Simple RESTful API,加入解析JSON的功能使API可以存取JSON資料。
the small wiki of JSON(參考)
先進行專案的基礎建設,開一個新專案叫simpleJson,新增一個檔案叫simplejson.go,並導入了套件gorilla/mux。用這行指令$ go get -u github.com/gorilla/mux
取得這個套件。
package main
import(
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "welcome home!")
}
func main(){
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(":8080", router))
}
首先,宣告一個叫event的結構,json格式的宣告附在value之後。宣告一個allEvents的slice用來存取多筆event,並在events初始化兩筆event,另外,還需要匯入encoding套件裡的json函示庫,之後要透過它來解析json資料。
注意: 在struct的宣告中key必須是大寫開頭,否則會拋出錯誤(切身經驗),但可以在json內將key轉換成小寫!! 原因
import(
"encoding/json"
...
)
type event struct {
Id string `json:"id"`
Title string `json:"title"`
Desc string `json:"desc"`
}
type allEvents []event
var events = allEvents{ // 加入一些events
{
Id: "1", Title: "test1", Desc: "this is the first test",
},
{
Id: "2", Title: "test2", Desc: "this is the second test",
},
}
加入一個getAllEvents的func,內容是將回傳值透過json加入解析後的events資料。打開Terminal使用curl -X GET localhost:8080/events
可以看到回傳值是一串JSON解析後的格式,如果與fmt.Println印出來的結果相比應該會發現JSON提供完整的key/value。
func getAllEvents(w http.ResponseWriter, r *http.Request){
json.NewEncoder(w).Encode(events)
fmt.Println("call all events api")
// fmt.Println(events)
}
func main(){
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", home)
router.HandleFunc("/events", getAllEvents).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", router))
}
新增一筆event,這裡使用到io套件的ioutil功能,ioutil.ReadAll(r.Body)讀取API傳來的HTTP body。Go透過 json.Unmarshal 將JSON字串轉成 struct(json.marshal則相反),所以我將reqBody的值轉換後指給newEvent,並將它加入events中,最後回傳成功結果給client。打開Terminal用curl -X POST -d '{"id":"3","title":"test3","desc":"This is the third test"}' localhost:8080/event
import(
"io/ioutil"
...
)
func createEvent(w http.ResponseWriter, r *http.Request){
var newEvent event
reqBody, _ := ioutil.ReadAll(r.Body)
// 轉換成 event struct
json.Unmarshal(reqBody, &newEvent)
events = append(events, newEvent)
// 201 created status code
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newEvent)
}
func main(){
..
router.HandleFunc("/event", createEvent).Methods("POST")
..
}
取得其中一筆event,假設我在Terminal輸入curl -X GET localhost:8080/events/1
,mux幫我解析這串url,若是發現符合events/{id}的格式,mux.Vars(r)["id"]幫我把id的值取出給路徑變數,接著在迴圈中找出有等於這個id的event,並回傳給client。
func getOneEvent(w http.ResponseWriter, r *http.Request) {
// 宣告路徑變數,解析 url取出 id 的值
eventId := mux.Vars(r)["id"]
// loop 找出對應 id
for _, singleEvent := range events {
if singleEvent.Id == eventId {
json.NewEncoder(w).Encode(singleEvent)
}
}
}
func main(){
..
router.HandleFunc("/events/{id}", getOneEvent).Methods("GET")
..
}
另外,還有編輯跟刪除event的功能,因為概念與新增和讀取是相似的,所以不多做紀錄。
完整範例參考
寫一個簡單的Web Server,處裡不同HTTP動作的請求,並輸出字串回應。
在Go的工作目錄底下,建立一個新資料夾叫 SimpleRESTAPI,並建立一個主程式的執行檔 main.go。基本的檔案內容如下,這裡需要引入net套件中的http函式庫,後續才能用Go產生一個 http server。
package main
import(
"fmt"
"net/http"
)
func main() {
}
撰寫一個notFound的處裡器,讓 server 啟動後可以顯示一點東西。在這個notFound函式會傳入兩個參數。先從第二個參數說起 ,r 代表Request負責接收client端發出的請求,並交由第一個參數 w 負責為請求做出回應與寫入訊息。針對寫入的訊息,在函式內可以設定了http的表頭、狀態及內容。
func notFound(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"message": "not found"}`)) // 傳輸 []byte 格式的資料
}
func main() {
http.HandleFunc("/", notFound) // 指定任何路徑下的都去呼叫 notFound 函式
http.ListenAndServe(":8000", nil) // 指定server監聽 port 8000
}
這裡加上例外處裡顯示像是port號已被其它程式占用或其他的錯誤訊息。
func main() {
http.HandleFunc("/", notFound)
err := http.ListenAndServe(":8000", nil)
if err != nil {
panic(err) // 如果有錯誤就終止程式
}
fmt.Println("The port 8000 is successfully connected")
}
雖然使用 net/http 可以實現路由功能,但日後若有更多功能要開發,這樣的設計方式會衍伸路由管理的麻煩,因此這個階段導入了一個路由設計的套件叫gorilla/mux。可以用這行指令$ go get -u github.com/gorilla/mux
取得這個套件。
import(
"net/http"
"github.com/gorilla/mux"
)
func get(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "get called"}`))
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/", get).Methods(http.MethodGet)
err := http.ListenAndServe(":8000", router)
if err != nil {
panic(err) // 如果有錯誤就終止程式
}
fmt.Println("The port 8000 is successfully connected")
}
引入"github.com/gorilla/mux" ,這裡我註冊了一條新url路徑並指給 get 處裡,註冊新 url的方式與 http.HandleFunc()一樣,若有符合路徑的請求就到對應的處裡器並傳入參數(http.ResponseWriter, *http.Request)。參考文件 另外,這裡在HandleFunc()之後綁定 HTTP的動作,所以同一個路徑會針對有不同HTTP動作執行相對應的處裡器。現在可以打開終端機,先輸入go run main.go
啟動go主程式,接著輸入curl -X GET localhost:8000/
就會看到 {message: get called} 的訊息了。
func main() {
router := mux.NewRouter()
api := router.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/", get).Methods(http.MethodGet)
err := http.ListenAndServe(":8000", router)
if err != nil {
panic(err) // 如果有錯誤就終止程式
}
fmt.Println("The port 8000 is successfully connected")
}
假如在我的server上有很多支API路徑,我希望透過不同的namespace來做管理。透過 mux 利用 PathPrefix("/api/v1").Subrouter(),篩選符合路徑的請求並做路徑分流。現在要輸入curl -X GET localhost:8000/api/v1/
才會看到 {message: get called} 的訊息。參考文件
之後可以加入其它像是POST、PUT、DELETE的動作來做不同的路由處裡。
完整範例參考
寫一個簡單的Web Server,處裡不同HTTP動作的請求,並輸出字串回應。
在Go的工作目錄底下,建立一個新資料夾叫 SimpleRESTAPI,並建立一個主程式的執行檔 main.go。基本的檔案內容如下,這裡需要引入net套件中的http函式庫,後續才能用Go產生一個 http server。
package main
import(
"fmt"
"net/http"
)
func main() {
}
撰寫一個notFound的處裡器,讓 server 啟動後可以顯示一點東西。在這個notFound函式會傳入兩個參數。先從第二個參數說起 ,r 代表Request負責接收client端發出的請求,並交由第一個參數 w 負責為請求做出回應與寫入訊息。針對寫入的訊息,在函式內可以設定了http的表頭、狀態及內容。
func notFound(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"message": "not found"}`)) // 傳輸 []byte 格式的資料
}
func main() {
http.HandleFunc("/", notFound) // 指定任何路徑下的都去呼叫 notFound 函式
http.ListenAndServe(":8000", nil) // 指定server監聽 port 8000
}
這裡加上例外處裡顯示像是port號已被其它程式占用或其他的錯誤訊息。
func main() {
http.HandleFunc("/", notFound)
err := http.ListenAndServe(":8000", nil)
if err != nil {
panic(err) // 如果有錯誤就終止程式
}
fmt.Println("The port 8000 is successfully connected")
}
雖然使用 net/http 可以實現路由功能,但日後若有更多功能要開發,這樣的設計方式會衍伸路由管理的麻煩,因此這個階段導入了一個路由設計的套件叫gorilla/mux。可以用這行指令$ go get -u github.com/gorilla/mux
取得這個套件。
import(
"net/http"
"github.com/gorilla/mux"
)
func get(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "get called"}`))
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/", get).Methods(http.MethodGet)
err := http.ListenAndServe(":8000", router)
if err != nil {
panic(err) // 如果有錯誤就終止程式
}
fmt.Println("The port 8000 is successfully connected")
}
引入"github.com/gorilla/mux" ,這裡我註冊了一條新url路徑並指給 get 處裡,註冊新 url的方式與 http.HandleFunc()一樣,若有符合路徑的請求就到對應的處裡器並傳入參數(http.ResponseWriter, *http.Request)。參考文件 另外,這裡在HandleFunc()之後綁定 HTTP的動作,所以同一個路徑會針對有不同HTTP動作執行相對應的處裡器。現在可以打開終端機,先輸入go run main.go
啟動go主程式,接著輸入curl -X GET localhost:8000/
就會看到 {message: get called} 的訊息了。
func main() {
router := mux.NewRouter()
api := router.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/", get).Methods(http.MethodGet)
err := http.ListenAndServe(":8000", router)
if err != nil {
panic(err) // 如果有錯誤就終止程式
}
fmt.Println("The port 8000 is successfully connected")
}
假如在我的server上有很多支API路徑,我希望透過不同的namespace來做管理。透過 mux 利用 PathPrefix("/api/v1").Subrouter(),篩選符合路徑的請求並做路徑分流。現在要輸入curl -X GET localhost:8000/api/v1/
才會看到 {message: get called} 的訊息。參考文件
之後可以加入其它像是POST、PUT、DELETE的動作來做不同的路由處裡。
完整範例參考
在Go的世界中,指標意思是存取一個值在記憶的位置。
func main() {
i, j := 42, 2701
// 取值的方法
p := &i // &i 表示 p 得到了 i 在記憶體中的位置
fmt.Println(*p) // *p 表示 透過 p 所記錄的記憶體位置,幫我拿到 i 的值,印出 42
// 存值的方法
*p = 21 // 透過 p 所記錄的記憶體位置,我給定一個新的值
fmt.Println(i) // 印出 21
p = &j // &j 表示 p 得到了 j 在記憶體中的位置
*p = *p / 37 // 拿 j 的值除以 37 並將這個新的值指給 j
fmt.Println(j) // 印出 73
}
在Go的世界中,如果想對陣列的size做規範可以採用array
// array的宣告,給定一個size
var a [2]string
a[0] = "Hello"
a[1] = "World"
a[2] = "!" // error 超出size
// 初始化陣列
b := [6]int{2, 3, 5, 7, 11, 13}
在Go的世界中,如果對陣列的大小希望保持彈性就採用slice。
// 只要不給定一個size,就是告訴 Go 這是一個 slice 的宣告
s := []int{2, 3, 5, 7, 11, 13}
// 沒有初始化就是 nil
var s []int // 是 nil不是 0 喔!!
len(s) // 長度才是 0
cap(s) // 容量也是 0
在Go的世界中,array和slice都具有length和capacity的屬性,不一樣的是array是固定的,無法被切割(run)
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]
// 切割這個 slice 使它的 length 為零
s = s[:0]
printSlice(s) // len=0 cap=6 []
// 擴張這個 slice 使它的 length 為 4
s = s[:4]
printSlice(s) // len=4 cap=6 [2 3 5 7]
// 移除掉前兩個元素,改變原來的容量
s = s[2:]
printSlice(s) // len=2 cap=4 [5 7]
}
擴充slice的長度和裁減範圍
s := []int{0, 1, 2, 3}
fmt.Println(s) // [0 1 2 3]
// 增加長度
s = append(s, 4, 5)
fmt.Println(s) // [0, 1, 2, 3, 4, 5]
// 也可將它裁切
fmt.Println(arr[1:4]) // [1, 2, 3]
fmt.Println(arr[1:]) // [1, 2, 3, 4, 5]
設計一個可以複用的結構(stuct)來開發
// 宣告一個 struct
type Rectangle struct {
H int
W int
}
// 宣告一個 method
func (r Rectangle) Area() int {
return r.H * r.W
}
func main() {
rec := Rectangle{H: 10, W: 5}
// rec := Rectangle{10, 5} 也可以註明 H 和 W
fmt.Println(rec.Area()) // 10 x 5 = 50
rec.W = 10
fmt.Println(rec.Area()) // 10 x 10 = 100
}
(補充)結構指標
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v // 讓 p 取得 v 的記憶體位置
p.X = 6 // (*p).X = 6 也是可以,但不必這麼做
fmt.Println(v) // 印出 {6 2}
}
Map 是一個鍵值配對(key/value)的結構。鍵是唯一的,而值可以重複。
宣告方式
// 宣告一個變數是 map 它包含一個鍵(String)一個值(int)
var x map[string]int // 未初始化,所以會產生一個 nil map,也就是沒有任何 key
// 初始化宣告,給定一個key和value
var x = map[string]int{"apple": 1}
// 短宣告,給定一個key和value
x := map[string]int{"apple": 1}
// 初始化多組 key/value
elements := map[string]string{
"H": "Hydrogen",
"He": "Helium",
"Li": "Lithium",
"Be": "Beryllium",
"B": "Boron",
"C": "Carbon",
"N": "Nitrogen",
"O": "Oxygen",
"F": "Fluorine",
"Ne": "Neon",
}
賦予值的方式
// Example1: 新增一個鍵與值
x["apple"] = 1
x["banana"] = 5
fmt.Println(x) // 印出 map[apple:1 banana:5]
fmt.Println(x["banana"]) // 印出 5
// Example2: 刪除某個鍵
delete(x,"banana") // (map, key name)
// Example3: 查詢map的長度
len(x)
印出不存在的鍵
通常在編譯的時候會報錯,但執行中(go run)的 Map 不會,而是丟出空值。
name, ok := elements["Al"]
fmt.Println(name, ok) // 印出 空值 false
// 修正後
if name, ok := elements["Al"]; ok {
fmt.Println(name, ok)
}
設計一個 Nested Map
elements := map[string]map[string]string{
"H": map[string]string{
"name":"Hydrogen",
"state":"gas",
},
"He": map[string]string{
"name":"Helium",
"state":"gas",
},
"Li": map[string]string{
"name":"Lithium",
"state":"solid",
},
"Be": map[string]string{
"name":"Beryllium",
"state":"solid",
},
"B": map[string]string{
"name":"Boron",
"state":"solid",
},
"C": map[string]string{
"name":"Carbon",
"state":"solid",
},
"N": map[string]string{
"name":"Nitrogen",
"state":"gas",
},
"O": map[string]string{
"name":"Oxygen",
"state":"gas",
},
"F": map[string]string{
"name":"Fluorine",
"state":"gas",
},
"Ne": map[string]string{
"name":"Neon",
"state":"gas",
},
}
if el, ok := elements["Li"]; ok {
fmt.Println(el["name"], el["state"])
}
當有了array或slice或map這種集合資料的結構,我們可能需要從這些集合中找到某一個值或列出所有的值,因此,我們可以給定一個range來代表這些集合
numbers := [4]int{1, 2, 3}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i, x)
}
// 第 0 位 x 的值 = 1
// 第 1 位 x 的值 = 2
// 第 2 位 x 的值 = 3
// 第 3 位 x 的值 = 0
m := map[string]int{
"one": 1,
"two": 2,
}
for k, v := range m {
fmt.Printf("%s -> %d\n", k, v)
}
// one -> 1
// two -> 2
在Go的世界中,指標意思是存取一個值在記憶的位置。
func main() {
i, j := 42, 2701
// 取值的方法
p := &i // &i 表示 p 得到了 i 在記憶體中的位置
fmt.Println(*p) // *p 表示 透過 p 所記錄的記憶體位置,幫我拿到 i 的值,印出 42
// 存值的方法
*p = 21 // 透過 p 所記錄的記憶體位置,我給定一個新的值
fmt.Println(i) // 印出 21
p = &j // &j 表示 p 得到了 j 在記憶體中的位置
*p = *p / 37 // 拿 j 的值除以 37 並將這個新的值指給 j
fmt.Println(j) // 印出 73
}
在Go的世界中,如果想對陣列的size做規範可以採用array
// array的宣告,給定一個size
var a [2]string
a[0] = "Hello"
a[1] = "World"
a[2] = "!" // error 超出size
// 初始化陣列
b := [6]int{2, 3, 5, 7, 11, 13}
在Go的世界中,如果對陣列的大小希望保持彈性就採用slice。
// 只要不給定一個size,就是告訴 Go 這是一個 slice 的宣告
s := []int{2, 3, 5, 7, 11, 13}
// 沒有初始化就是 nil
var s []int // 是 nil不是 0 喔!!
len(s) // 長度才是 0
cap(s) // 容量也是 0
在Go的世界中,array和slice都具有length和capacity的屬性,不一樣的是array是固定的,無法被切割(run)
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]
// 切割這個 slice 使它的 length 為零
s = s[:0]
printSlice(s) // len=0 cap=6 []
// 擴張這個 slice 使它的 length 為 4
s = s[:4]
printSlice(s) // len=4 cap=6 [2 3 5 7]
// 移除掉前兩個元素,改變原來的容量
s = s[2:]
printSlice(s) // len=2 cap=4 [5 7]
}
擴充slice的長度和裁減範圍
s := []int{0, 1, 2, 3}
fmt.Println(s) // [0 1 2 3]
// 增加長度
s = append(s, 4, 5)
fmt.Println(s) // [0, 1, 2, 3, 4, 5]
// 也可將它裁切
fmt.Println(arr[1:4]) // [1, 2, 3]
fmt.Println(arr[1:]) // [1, 2, 3, 4, 5]
設計一個可以複用的結構(stuct)來開發
// 宣告一個 struct
type Rectangle struct {
H int
W int
}
// 宣告一個 method
func (r Rectangle) Area() int {
return r.H * r.W
}
func main() {
rec := Rectangle{H: 10, W: 5}
// rec := Rectangle{10, 5} 也可以註明 H 和 W
fmt.Println(rec.Area()) // 10 x 5 = 50
rec.W = 10
fmt.Println(rec.Area()) // 10 x 10 = 100
}
(補充)結構指標
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v // 讓 p 取得 v 的記憶體位置
p.X = 6 // (*p).X = 6 也是可以,但不必這麼做
fmt.Println(v) // 印出 {6 2}
}
Map 是一個鍵值配對(key/value)的結構。鍵是唯一的,而值可以重複。
宣告方式
// 宣告一個變數是 map 它包含一個鍵(String)一個值(int)
var x map[string]int // 未初始化,所以會產生一個 nil map,也就是沒有任何 key
// 初始化宣告,給定一個key和value
var x = map[string]int{"apple": 1}
// 短宣告,給定一個key和value
x := map[string]int{"apple": 1}
// 初始化多組 key/value
elements := map[string]string{
"H": "Hydrogen",
"He": "Helium",
"Li": "Lithium",
"Be": "Beryllium",
"B": "Boron",
"C": "Carbon",
"N": "Nitrogen",
"O": "Oxygen",
"F": "Fluorine",
"Ne": "Neon",
}
賦予值的方式
// Example1: 新增一個鍵與值
x["apple"] = 1
x["banana"] = 5
fmt.Println(x) // 印出 map[apple:1 banana:5]
fmt.Println(x["banana"]) // 印出 5
// Example2: 刪除某個鍵
delete(x,"banana") // (map, key name)
// Example3: 查詢map的長度
len(x)
印出不存在的鍵
通常在編譯的時候會報錯,但執行中(go run)的 Map 不會,而是丟出空值。
name, ok := elements["Al"]
fmt.Println(name, ok) // 印出 空值 false
// 修正後
if name, ok := elements["Al"]; ok {
fmt.Println(name, ok)
}
設計一個 Nested Map
elements := map[string]map[string]string{
"H": map[string]string{
"name":"Hydrogen",
"state":"gas",
},
"He": map[string]string{
"name":"Helium",
"state":"gas",
},
"Li": map[string]string{
"name":"Lithium",
"state":"solid",
},
"Be": map[string]string{
"name":"Beryllium",
"state":"solid",
},
"B": map[string]string{
"name":"Boron",
"state":"solid",
},
"C": map[string]string{
"name":"Carbon",
"state":"solid",
},
"N": map[string]string{
"name":"Nitrogen",
"state":"gas",
},
"O": map[string]string{
"name":"Oxygen",
"state":"gas",
},
"F": map[string]string{
"name":"Fluorine",
"state":"gas",
},
"Ne": map[string]string{
"name":"Neon",
"state":"gas",
},
}
if el, ok := elements["Li"]; ok {
fmt.Println(el["name"], el["state"])
}
當有了array或slice或map這種集合資料的結構,我們可能需要從這些集合中找到某一個值或列出所有的值,因此,我們可以給定一個range來代表這些集合
numbers := [4]int{1, 2, 3}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i, x)
}
// 第 0 位 x 的值 = 1
// 第 1 位 x 的值 = 2
// 第 2 位 x 的值 = 3
// 第 3 位 x 的值 = 0
m := map[string]int{
"one": 1,
"two": 2,
}
for k, v := range m {
fmt.Printf("%s -> %d\n", k, v)
}
// one -> 1
// two -> 2
Go的變數宣告又兩種方式,其中短變數宣告比較特別,由於沒有指明特定型別,因此,Go會依據等號右邊的值來決定變數的型別(參考)。此外,短變數宣告不能寫在函式外面否則會出錯。
// 第一種: 常見的變數宣告
var a int // 沒有指定初始值就會預設為 0
var a int = 1 // 給定初始值 1
// 第二種: 短變數宣告(Short variable declarations)
message := "hello world"
另外是多個變數的宣告(節省空間用)
var b,c int = 2, 3
(補充)宣告後得到空值(Zero values)的情況
var i int // 印出 0
var f float64 // 印出 0
var b bool // 印出 false
var s string // 印出 ""
fmt.Printf("%v %v %v %q\n", i, f, b, s)
Go的程式是由Package組成的,以先前的 hellow world 例子來說,程式從 main package 開始,接著引入內建的 library ,並在 main() 這個主程式的函式開始執行。
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
Go的函式的特色就是能夠接受多個回傳值,另外也能把函式當成變數傳給別人用。
// 帶入兩個參數,但同一種型別的省略寫法
// 當函式只有一個值要回傳時的寫法
func add(x, y int) int {
return x + y
}
// 當函式有一個以上的值要回傳時的寫法
func swap(x, y string) (string, string) {
return y, x
}
func main() {
fmt.Println(add(42, 13))
// 讓 a 跟 b 告別接收回傳值
a, b := swap("hello", "world")
fmt.Println(a, b)
}
import "math/rand"
func randInt() int {
return rand.Intn(100) // 隨機跑出 0 ~ 100 的正整數
}
func main() {
// 寫法一
a := randInt() // 將函數指定為 a 的變數
if a%2 == 1 {
fmt.Println("a 是奇數")
}else {
fmt.Println("a 是偶數")
}
// 寫法二(加入短宣告寫法的簡略版)
if a := randInt(); a%2 == 1 {
fmt.Println("a 是奇數")
}else {
fmt.Println("a 是偶數")
}
}
func pow(x, n int) int {
result := 1
// for 迴圈的寫法
for i := 0; i < n; i++ {
result *= x
}
// Go 沒有while迴圈,因為可以拿 for 來用
result, i := 1, 0
for i < n {
result *= x
i++
}
return result
}
func main() {
result := pow(2, 10)
fmt.Println(result) // 1024
}
以下的例子來說,defer 會等主函式執行完所有的函式後再回頭做defer 裡的工作。
有什麼用途呢?可以拿來例如釋放資源、停止資料庫連線之類的、調度程式執行的順序
後續會再有更多補充!!!
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
Go的變數宣告又兩種方式,其中短變數宣告比較特別,由於沒有指明特定型別,因此,Go會依據等號右邊的值來決定變數的型別(參考)。此外,短變數宣告不能寫在函式外面否則會出錯。
// 第一種: 常見的變數宣告
var a int // 沒有指定初始值就會預設為 0
var a int = 1 // 給定初始值 1
// 第二種: 短變數宣告(Short variable declarations)
message := "hello world"
另外是多個變數的宣告(節省空間用)
var b,c int = 2, 3
(補充)宣告後得到空值(Zero values)的情況
var i int // 印出 0
var f float64 // 印出 0
var b bool // 印出 false
var s string // 印出 ""
fmt.Printf("%v %v %v %q\n", i, f, b, s)
Go的程式是由Package組成的,以先前的 hellow world 例子來說,程式從 main package 開始,接著引入內建的 library ,並在 main() 這個主程式的函式開始執行。
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
Go的函式的特色就是能夠接受多個回傳值,另外也能把函式當成變數傳給別人用。
// 帶入兩個參數,但同一種型別的省略寫法
// 當函式只有一個值要回傳時的寫法
func add(x, y int) int {
return x + y
}
// 當函式有一個以上的值要回傳時的寫法
func swap(x, y string) (string, string) {
return y, x
}
func main() {
fmt.Println(add(42, 13))
// 讓 a 跟 b 告別接收回傳值
a, b := swap("hello", "world")
fmt.Println(a, b)
}
import "math/rand"
func randInt() int {
return rand.Intn(100) // 隨機跑出 0 ~ 100 的正整數
}
func main() {
// 寫法一
a := randInt() // 將函數指定為 a 的變數
if a%2 == 1 {
fmt.Println("a 是奇數")
}else {
fmt.Println("a 是偶數")
}
// 寫法二(加入短宣告寫法的簡略版)
if a := randInt(); a%2 == 1 {
fmt.Println("a 是奇數")
}else {
fmt.Println("a 是偶數")
}
}
func pow(x, n int) int {
result := 1
// for 迴圈的寫法
for i := 0; i < n; i++ {
result *= x
}
// Go 沒有while迴圈,因為可以拿 for 來用
result, i := 1, 0
for i < n {
result *= x
i++
}
return result
}
func main() {
result := pow(2, 10)
fmt.Println(result) // 1024
}
以下的例子來說,defer 會等主函式執行完所有的函式後再回頭做defer 裡的工作。
有什麼用途呢?可以拿來例如釋放資源、停止資料庫連線之類的、調度程式執行的順序
後續會再有更多補充!!!
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}