現在時間: 22 週 / 我的進度: 20 週
人真的不能拖,居然已經到了要寫 20 週心得的日子了(閉眼)
感謝認真寫每日進度ㄉ過去ㄉ我,找不到像之前那樣可以貫穿的主題,以下內容可視為生菜每日進度ㄉ精華集 (???)
對我來說,擁有知識非常重要的一點就是除魅化。
也不是說有超自然信仰的人都是笨蛋,而是透過系統性的了解內部原理,能一點一點拿回對日子的掌控感——這樣說來相信倒掛晴天娃娃就會下雨的人,也能透過這些行為讓自己好過一點,接下來科學或迷信就是知識正確性 / 可行性的決鬥了。
兩年前曾經有個朋友半夜打電話給我,說他很難過,因為看到我無名小站紀錄的國中經歷的事,讓他很替我難過——可是大,無名小站好幾年前就關了欸——之後他變本加厲開始宣稱能看到我們的健保卡資料、我鎖起來的噗浪,甚至是同步電腦的螢幕畫面。
我當時根本不敢和當事人確認真假,於是偷偷問了工程師前輩:這是有可能的嗎?他說不可能。之後我問了畢業後想轉職工程師的事,對方很認真地替我解惑後,補了一句:「你該不會是因為剛剛那件事才想當工程師的吧?」
才不是勒,我想轉職工程師只是因為我想轉職工程師。
但不可否認的是,我在 11 週學到資安理論時感到十分紓壓。
雖然至今我還是不知道對方採取什麼樣的手段(甚至可能只是虛張聲勢),但恐懼無疑是存在的。 這週拆解了「原來這樣做就可以拿到其它網站使用者的資料!」光是理解其中原理就能舒緩我的不安,就算對方真的滲透我所有帳號,也不會是使用我完全不懂的黑魔法了 XD 這完全是我打算開始學程式時從沒想過的收穫(笑)
然後這週還得到了人生第一個值得參考,不過真正的問題才正要開始。
從 11 週到 12 週,獨立架好一個網站十分有成就感,雖然常常在 PHP 和 HTML 中迷路,或是 JavaScript 動不動就不鳥我,不過說不定是目前(cosplay 15 週的目前)為止我最享受課程的兩週了。
參加過的新竹和台南的地方小聚,助教都有提到這期課程設計會不會太手把手的問題。同時這兩週也有大量的課程檢討影片,還特別說明寫完再來看——我幾乎沒有看(喂)而且課程影片可以的話我都會先暫停自己試試看,繼續如果和呼哩做得差不多就會有種贏了的感覺,用精神勝利法讓自己有動力繼續往前的概念。
不過也開始思考,課程結束後我還能繼續成長嗎?因為目前都只有跟課而且沒做挑戰題,雖然這週因為課程規劃上已經寫了好幾週 PHP ,現在忽然要把 JS 撿回來,有些寫法常常會忘記或搞混,但都能靠搜群引擎化險為夷,看起來是多了點自信,不過對於自己是否有能力獨自學習一個新工具感到有點擔心。
另一組令人感到踏實的事件,是我這週挑戰回同學在 Slack 上的問題。雖然有些看起來是幫倒忙,但也算是踏出一步了。不知為何我一直對「成為有求知慾的人」有迷之執著,常常會焦慮自己是否不求甚解。這幾次不管幫忙或被幫忙 debug 雖然當下沒有自覺,回神過來自己已經深陷其中了,發現的時候感覺,好爽~~~
……但是,這週並沒有拿到值得參考。
我對自己的了解準確到,連我自己都嚇一跳的地步。前陣子(上個月?)看到大家在討論值得參考的問題時,心想:「看看我都沒拿過值得參考,還不是活得好好ㄉ XD」不過正是因為無欲無求才沒有痛苦。11 週毫無預兆的拿到一次值得參考時心想,完了,我也是嘗過值得參考滋味的人了。聽有在化妝的朋友說,如果用過專櫃就很難回開架了,人只會越來越難滿足,也許值得參考是這樣。總之做出「我完蛋了」的預言後,今天看同學作業時就迎來反撲,陷入強烈腫脹到有點疼痛的情感中無法動彈——好不甘心!!!
不過現在回頭看也不完全是壞事,畢竟在此之前無動於衷也超不合理,就像是直接投降一樣,說難聽點也只是鴕鳥心態替自己的失敗找藉口而已吧。反之不甘心的心情也許才是真正站上起點,並且開始對自己負責的象徵嗎?我覺得很有趣,希望能找到和這份心情共處的方式,並轉化為己所用。
動力急速下降的一週。
可能是這週的內容忽然變得有點發散,也不太可能真的從零把什麼東西蓋起來,或是真的不看影片獨自把 webpack 架好。雖然說是之後會越來越明白的,但還是有點空虛啊。而且這週還必須帶著上週混亂的心繼續前進。
之前有人提到跟課程像是修行,我覺得非常有道理。
前幾年曾為了「理解後才能理直氣壯的討厭」,而加入校內的中國哲學讀書會,總體來說根本沒讀到什麼書算是個浪費時間的努力,不過人品超差的老師偶爾還是會說出讓我有一點點點點收穫的東西,像是「注重禮節是由外而內的改造過程」。在此之前,我想像中的理想行動是,內心有滿滿的衝動,以此為燃料付諸實踐,甚至不允許一點雜質,因此我當下非常不諒解老師說的話——不重視真心的行為,根本只是政治手段而已吧。
雖然恥於承認,但我想我慢慢可以理解了。與其說是認同這個價值觀,應該說偶爾有採取此策略的必要性。
就像是練體能好了,就絕對不會有人懷疑「我真的適合追求更好的體型嗎?」或是「我真的喜歡生酮飲食嗎?」很多時候我們什麼都不想,就只是做,雖然內心會有非常非常多自我懷疑,但抱著這些迷惘繼續行動,某天回神過來發現自己又前進了一步而發自內心感到開心,就是邁向目標的起點了。雖然很痛苦,但如果因為自我懷疑就停下腳步也太可惜了。
雖然學到 13 週又很多被程式搞到瘋掉或自我懷疑的時刻,但無法否認得到的快樂就像原石一樣在那裡閃閃發亮,為了這些時刻就再多走一段吧……修但幾勒,忽然又開始灌雞湯。唉呦反正我現在因為寫程式感到焦慮不安的時候,就會很中二的認為「畢竟是在修行嘛」然後多撐一下。
卡到爆的一週。
在此之前還曾經對後端工程師有過幻想,覺得能只用 CLI 好帥,經歷十四週後完全不敢想自己現在能勝任後端工作。
真正體悟出會在部屬時那麼卡的原因,以及其和我本人性格間的關係,是在 17 週的時候。為了好好扮演 15 週的生菜,這週的心得就用對課程制度的反饋覆蓋好了:
每日進度
課程每日進度,是要求學員每天紀錄當天完成的進度、學到的東西、和明日預計進度等等。
請讓我在此引用 lauviah0622
同學精準的心得:(已得到授權)
不會有一定要寫很長之類的壓力,然後可能還會不經意地被人看到滿足虛榮心,那種有點暴露,但是又其實沒人理你的感覺有點上癮(幹超變態的)。
我個人認為這完全點出該制度的精妙之處。不知道有誰讀過、沒有按讚系統,唯一就是點 icon 到 slack 私訊當事人,完全適合容易被社群軟體綁架的我。還可以快樂地使用韓國瑜思維:門檻那麼高,沒有表達反感的人一定都是蠻喜歡我的每日進度的啦 (?????)
另外也蠻感謝沒有人阻止我一直在每日進度寫一些和 coding 無關的紀錄。不過,雖然看似無關緊要的廢話,卻也扎扎實實的讓我能夠更穩定的學習。有點像過動的孩子會被送去游泳一樣,每天開始學習前,將腦內過多的思考再檢視整理並記錄下來,互動不高不用擔心被綁架但依然可以滿足表現欲,然後就可以專心學習了 。也有學習中出現無數次很想放過自己「算了今天就這樣吧」,但因為前一個進度報告已經承諾過的進度還沒達到,想到這樣明天就要說抱歉我食言而肥——太羞愧了真是太羞愧了!多努力一點吧!然後充滿決心的狀況也有 XD
助教們的 Podcast
去年九月畢業後,我正式般到台南。
前陣子臉書動態回顧說,當時的我寫了胡遷《大象席地而坐》的句子:
我告訴你最好的狀況,就是你站在這裡,你可以看到那邊的那個地方。你想著那邊一定比這裡好,但你不能去。你不去,才能解決好這的問題。
我還沒當過正職,甚至人生還有很多經歷空缺,這種時候只能依著想像行事——當初我想像我到台南後就會自動找到人生方向,現在我想像要是轉職成為工程師,會不會還是無法融入想像的社會,或是所有問題都迎刃而解,最好是連膝蓋的舊傷都自己好了。
而課程助教每週的 podcast ,在這之中也許就扮演某種教練的角色。
除了「工作的煩惱會是這樣」的想像外,最重要的果然還是「原來工程師長這個樣子」……不對,應該說是對「就算是這樣的人,還是可以持續追求優雅的生存方式啊」的想像——覺得這不重要的人,去給我和《炎拳》的賀利田說啊(爆雷王)
另外也在 15 週左右上了 Podcast。雖然對自己不太滿意,但反正我是無論如何都不可能對自己滿意的人,重點是整體而言是個有趣的體驗,是那種我老年時纏著來幫忙打掃的社工喋喋不休時,會反覆體起的經驗(不要用這麼難以理解的情境說明!)
我:這次寫三百字就好了。
也是我:(也了三千字)
這次的心得內容和之前比起來發散好多,看起來我就是一個很愛碎碎念的人而已(說得好像本來不是一樣),好不滿意ㄛ……算了我要繼續往前了,20 週心得見~
]]>現在時間: 22 週 / 我的進度: 20 週
人真的不能拖,居然已經到了要寫 20 週心得的日子了(閉眼)
感謝認真寫每日進度ㄉ過去ㄉ我,找不到像之前那樣可以貫穿的主題,以下內容可視為生菜每日進度ㄉ精華集 (???)
對我來說,擁有知識非常重要的一點就是除魅化。
也不是說有超自然信仰的人都是笨蛋,而是透過系統性的了解內部原理,能一點一點拿回對日子的掌控感——這樣說來相信倒掛晴天娃娃就會下雨的人,也能透過這些行為讓自己好過一點,接下來科學或迷信就是知識正確性 / 可行性的決鬥了。
兩年前曾經有個朋友半夜打電話給我,說他很難過,因為看到我無名小站紀錄的國中經歷的事,讓他很替我難過——可是大,無名小站好幾年前就關了欸——之後他變本加厲開始宣稱能看到我們的健保卡資料、我鎖起來的噗浪,甚至是同步電腦的螢幕畫面。
我當時根本不敢和當事人確認真假,於是偷偷問了工程師前輩:這是有可能的嗎?他說不可能。之後我問了畢業後想轉職工程師的事,對方很認真地替我解惑後,補了一句:「你該不會是因為剛剛那件事才想當工程師的吧?」
才不是勒,我想轉職工程師只是因為我想轉職工程師。
但不可否認的是,我在 11 週學到資安理論時感到十分紓壓。
雖然至今我還是不知道對方採取什麼樣的手段(甚至可能只是虛張聲勢),但恐懼無疑是存在的。 這週拆解了「原來這樣做就可以拿到其它網站使用者的資料!」光是理解其中原理就能舒緩我的不安,就算對方真的滲透我所有帳號,也不會是使用我完全不懂的黑魔法了 XD 這完全是我打算開始學程式時從沒想過的收穫(笑)
然後這週還得到了人生第一個值得參考,不過真正的問題才正要開始。
從 11 週到 12 週,獨立架好一個網站十分有成就感,雖然常常在 PHP 和 HTML 中迷路,或是 JavaScript 動不動就不鳥我,不過說不定是目前(cosplay 15 週的目前)為止我最享受課程的兩週了。
參加過的新竹和台南的地方小聚,助教都有提到這期課程設計會不會太手把手的問題。同時這兩週也有大量的課程檢討影片,還特別說明寫完再來看——我幾乎沒有看(喂)而且課程影片可以的話我都會先暫停自己試試看,繼續如果和呼哩做得差不多就會有種贏了的感覺,用精神勝利法讓自己有動力繼續往前的概念。
不過也開始思考,課程結束後我還能繼續成長嗎?因為目前都只有跟課而且沒做挑戰題,雖然這週因為課程規劃上已經寫了好幾週 PHP ,現在忽然要把 JS 撿回來,有些寫法常常會忘記或搞混,但都能靠搜群引擎化險為夷,看起來是多了點自信,不過對於自己是否有能力獨自學習一個新工具感到有點擔心。
另一組令人感到踏實的事件,是我這週挑戰回同學在 Slack 上的問題。雖然有些看起來是幫倒忙,但也算是踏出一步了。不知為何我一直對「成為有求知慾的人」有迷之執著,常常會焦慮自己是否不求甚解。這幾次不管幫忙或被幫忙 debug 雖然當下沒有自覺,回神過來自己已經深陷其中了,發現的時候感覺,好爽~~~
……但是,這週並沒有拿到值得參考。
我對自己的了解準確到,連我自己都嚇一跳的地步。前陣子(上個月?)看到大家在討論值得參考的問題時,心想:「看看我都沒拿過值得參考,還不是活得好好ㄉ XD」不過正是因為無欲無求才沒有痛苦。11 週毫無預兆的拿到一次值得參考時心想,完了,我也是嘗過值得參考滋味的人了。聽有在化妝的朋友說,如果用過專櫃就很難回開架了,人只會越來越難滿足,也許值得參考是這樣。總之做出「我完蛋了」的預言後,今天看同學作業時就迎來反撲,陷入強烈腫脹到有點疼痛的情感中無法動彈——好不甘心!!!
不過現在回頭看也不完全是壞事,畢竟在此之前無動於衷也超不合理,就像是直接投降一樣,說難聽點也只是鴕鳥心態替自己的失敗找藉口而已吧。反之不甘心的心情也許才是真正站上起點,並且開始對自己負責的象徵嗎?我覺得很有趣,希望能找到和這份心情共處的方式,並轉化為己所用。
動力急速下降的一週。
可能是這週的內容忽然變得有點發散,也不太可能真的從零把什麼東西蓋起來,或是真的不看影片獨自把 webpack 架好。雖然說是之後會越來越明白的,但還是有點空虛啊。而且這週還必須帶著上週混亂的心繼續前進。
之前有人提到跟課程像是修行,我覺得非常有道理。
前幾年曾為了「理解後才能理直氣壯的討厭」,而加入校內的中國哲學讀書會,總體來說根本沒讀到什麼書算是個浪費時間的努力,不過人品超差的老師偶爾還是會說出讓我有一點點點點收穫的東西,像是「注重禮節是由外而內的改造過程」。在此之前,我想像中的理想行動是,內心有滿滿的衝動,以此為燃料付諸實踐,甚至不允許一點雜質,因此我當下非常不諒解老師說的話——不重視真心的行為,根本只是政治手段而已吧。
雖然恥於承認,但我想我慢慢可以理解了。與其說是認同這個價值觀,應該說偶爾有採取此策略的必要性。
就像是練體能好了,就絕對不會有人懷疑「我真的適合追求更好的體型嗎?」或是「我真的喜歡生酮飲食嗎?」很多時候我們什麼都不想,就只是做,雖然內心會有非常非常多自我懷疑,但抱著這些迷惘繼續行動,某天回神過來發現自己又前進了一步而發自內心感到開心,就是邁向目標的起點了。雖然很痛苦,但如果因為自我懷疑就停下腳步也太可惜了。
雖然學到 13 週又很多被程式搞到瘋掉或自我懷疑的時刻,但無法否認得到的快樂就像原石一樣在那裡閃閃發亮,為了這些時刻就再多走一段吧……修但幾勒,忽然又開始灌雞湯。唉呦反正我現在因為寫程式感到焦慮不安的時候,就會很中二的認為「畢竟是在修行嘛」然後多撐一下。
卡到爆的一週。
在此之前還曾經對後端工程師有過幻想,覺得能只用 CLI 好帥,經歷十四週後完全不敢想自己現在能勝任後端工作。
真正體悟出會在部屬時那麼卡的原因,以及其和我本人性格間的關係,是在 17 週的時候。為了好好扮演 15 週的生菜,這週的心得就用對課程制度的反饋覆蓋好了:
每日進度
課程每日進度,是要求學員每天紀錄當天完成的進度、學到的東西、和明日預計進度等等。
請讓我在此引用 lauviah0622
同學精準的心得:(已得到授權)
不會有一定要寫很長之類的壓力,然後可能還會不經意地被人看到滿足虛榮心,那種有點暴露,但是又其實沒人理你的感覺有點上癮(幹超變態的)。
我個人認為這完全點出該制度的精妙之處。不知道有誰讀過、沒有按讚系統,唯一就是點 icon 到 slack 私訊當事人,完全適合容易被社群軟體綁架的我。還可以快樂地使用韓國瑜思維:門檻那麼高,沒有表達反感的人一定都是蠻喜歡我的每日進度的啦 (?????)
另外也蠻感謝沒有人阻止我一直在每日進度寫一些和 coding 無關的紀錄。不過,雖然看似無關緊要的廢話,卻也扎扎實實的讓我能夠更穩定的學習。有點像過動的孩子會被送去游泳一樣,每天開始學習前,將腦內過多的思考再檢視整理並記錄下來,互動不高不用擔心被綁架但依然可以滿足表現欲,然後就可以專心學習了 。也有學習中出現無數次很想放過自己「算了今天就這樣吧」,但因為前一個進度報告已經承諾過的進度還沒達到,想到這樣明天就要說抱歉我食言而肥——太羞愧了真是太羞愧了!多努力一點吧!然後充滿決心的狀況也有 XD
助教們的 Podcast
去年九月畢業後,我正式般到台南。
前陣子臉書動態回顧說,當時的我寫了胡遷《大象席地而坐》的句子:
我告訴你最好的狀況,就是你站在這裡,你可以看到那邊的那個地方。你想著那邊一定比這裡好,但你不能去。你不去,才能解決好這的問題。
我還沒當過正職,甚至人生還有很多經歷空缺,這種時候只能依著想像行事——當初我想像我到台南後就會自動找到人生方向,現在我想像要是轉職成為工程師,會不會還是無法融入想像的社會,或是所有問題都迎刃而解,最好是連膝蓋的舊傷都自己好了。
而課程助教每週的 podcast ,在這之中也許就扮演某種教練的角色。
除了「工作的煩惱會是這樣」的想像外,最重要的果然還是「原來工程師長這個樣子」……不對,應該說是對「就算是這樣的人,還是可以持續追求優雅的生存方式啊」的想像——覺得這不重要的人,去給我和《炎拳》的賀利田說啊(爆雷王)
另外也在 15 週左右上了 Podcast。雖然對自己不太滿意,但反正我是無論如何都不可能對自己滿意的人,重點是整體而言是個有趣的體驗,是那種我老年時纏著來幫忙打掃的社工喋喋不休時,會反覆體起的經驗(不要用這麼難以理解的情境說明!)
我:這次寫三百字就好了。
也是我:(也了三千字)
這次的心得內容和之前比起來發散好多,看起來我就是一個很愛碎碎念的人而已(說得好像本來不是一樣),好不滿意ㄛ……算了我要繼續往前了,20 週心得見~
]]>從剛剛的例子開始說吧:
function createWallet(init) {
var money = init
return {
add: function(num) {
money += num
},
deduct: function(num) {
money -= num
}
}, getMoney() {
return money
}
}
var myWallet = createWallet(99)
myWallet.add(1)
myWallet.deduct(10)
console.log(myWallet.getMoney()) // 90
這個例子中回傳的值是一個物件,其實就算是物件導向。在使用 JS 時,也時常不是直接 call 一個 function ,而是對某個物件做操作,這種做法的好處是方便模組化。
從 ES6 的 class
開始談起。
首先, class 的名稱一定是大寫開頭,例如:
class Dog {
sayHello() {
console.log('hello')
}
}
class 有點像設計圖,當我們實際使用前時,要用 new
將 class 實體化 (instance):
var d = new Dog()
d.sayHello() // hello
另一個概念是 this
,它會指向呼叫它的東西:
class Dog {
setName(name) {
this.name = name
}
getName() {
return this.name
}
}
var d = new Dog()
d.setName('jojo')
console.log(getName()) // jojo
上面範例中 d.setName('jojo')
中的 this
因為是由 d 呼喚的,因此 this
當然就指向變數 d 。
class 中 setName(name)
這樣的函數被稱為 setter ,讓裡面存取到外面的值;而 sayHello()
則叫 getter ,是讓外面得到 class 的值。 另外我們也可以直接這樣寫:
d.name = 'dio'
console.log(d.name) // dio
但還是建議用 setter 和 getter 。
如果想要用像是函式傳參數的方式設定,可以用 建構子 constructor
:
class Dog {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
var d = new Dog('jojo') // 字串 'jojo' 被傳入 constructor() 中
console.log(getName()) // jojo
var d = new Dog('dio')
console.log(getName()) // dio
ES5 的 class
在 ES5 中沒有 class ,因此要這樣寫:
function Dog(name) {
var myName = name
return {
getName: function() {
return myName
},
sayHello: funcrion() {
console.log(myName)
}
}
}
var d = Dog('jojo')
d.sayHello // jojo
var b = Dog('dio')
d.sayHello // dio
不過因為每次都是呼叫一個新的物件,會出現這種狀況:
console.log(b.sayHello === d.sayHello) // false
不過兩個是同個 function ,所以共用同個 function 比較省記憶體吧?
因此在 ES5 中,可以將 function 當作 constructor 用:
function Dog(name) {
this.name = name
}
var d = new Dog('abc')
console.log(d) // Dog { name: 'abc' }
自動變成物件了!
不過這樣要怎麼知道是 constructor 還是平常的 function?只有加 new
才會被認定是 constructor ,如果沒加就是 function 。
設定屬性的問題搞定了,但要怎麼設定輸出名字和其他操作ㄋ?這時候可以把東西掛在 .prototype
上:
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('jojo')
d.sayHello // jojo
JavaScript 中,每個變數都有個隱藏屬性 __proto__
,暗示如果在 d
上面找不到 sayHello
的屬性:
function Dog(name) {
this.name = name
}
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('jojo')
d.sayHello // jojo
console.log(d.__proto__)
// Dog { sayHello: [Function (anonymous)] }
// 其實就是 Dog.prototype
當我們呼叫 d.sayHello
時,我們其實是做了:
d
本身是否有 sayHello
d.__proto__
是否有 sayHello
,也就是 Dog.prototype
d.__proto__.__proto__
,也就是 Object.prototype
d.__proto__.__proto__.__proto__
,沒有的話會回傳 null。以上都是只要有就會回傳值,沒有的話才往下進行,這個步驟被稱為原型練(Prototype Chain)。
我們來看一下:
console.log(d.__proto__)
// Dog.prototype
// 結果:Dog { sayHello: [Function (anonymous)] }
console.log(d.__proto__.__proto__)
// Dog.prototype.__proto__
// Object.prototype
// 結果:{}
console.log(d.__proto__.__proto__.__proto__)
// null
他們之間的關係如下:
d.__proto__ = Dog.prototype
d.__proto__.__proto__ = Object.prototype
Dog.prototype.__proto__ = Object.prototype
因此我們也可以設定 Object 的 prototype ,這樣就會在第三個步驟呼叫到結果:
Object.prototype.sayHello = function() {
console.log('object', this.name)
}
var d = new Dog('jojo')
d.sayHello // object jojo
如果同時設定 Object 和 Dog 的 prototype ,則會因為原型鍊會先選到 Dog 的:
Dog.prototype.sayHello = function() {
console.log(this.name)
}
Object.prototype.sayHello = function() {
console.log('object', this.name)
}
var d = new Dog('jojo')
d.sayHello // jojo
同理,此處的 Object 如果被換成 Function ,第四個步驟就會被換成 Function.prototype 。
function.call()
這個函數可以指定 function 中的 this
值:
function test() {
console.log(this)
}
test.call(123) // [Number: 123]
接著來拆解 new
到底幫我們做了甚麼,因此用另一個 function 來模擬:
function newDog(name) {
// 模擬 new 做了一些事情
}
// 最後目標
var a = newDog('jojo')
a.sayHello() // 印出 jojo
var a = newDog('jojo') //{ name: 'jojo' }
2. 設定 prototype 連結
function newDog(name) {
var obj = {}
Dog.call(obj, name) // 第一個是 this ,後面依序是傳入值
obj.proto = Dog.prototype
}
var a = newDog('jojo')
3. 回傳 object
function newDog(name) {
var obj = {}
Dog.call(obj, name) // 第一個是 this ,後面依序是傳入值
obj.proto = Dog.prototype
return obj
}
var a = newDog('jojo')
a.sayHello() // 印出 jojo
就完成ㄌ!
### Inheritance
設想有一個狗的 class ,今天我需要設定黑狗和白狗,這時有名字、會叫、丟飛盤會去接回來之類的和狗有關的屬性就不用再設定一次了。要是有人問你「黑狗有幾個眼睛」時,只要回頭查看「狗」的條目就可以了。這就是 `Inheritance` 繼承的概念。
ES6 中的繼承可以這樣寫:
class BlackDog extands Dog{
// 其他黑狗的屬性
}
const d = BlackDog('jojo')
d.sayHello()
上面的例子中 `d.sayHello()` 實際上是往上找到 Dog 的屬性。
此時若我們想讓黑狗被建立的時候就呼叫 `sayHello()`:
class BlackDog extands Dog{
constructure() {
this.sayHello()
}
}
const d = BlackDog('jojo')
這樣會噴錯,因為在 `constructor` 中呼叫 `this` 前要用 `super()` 另外引入上一層的`constructor` ,如下:
class BlackDog extands Dog{
constructure(name) {
super(name)
this.sayHello()
}
}
const d = BlackDog('jojo') // jojo
### this
`this` 在物件導向中被使用,可以用代表其所對應到的 instance 。
如果直接呼叫 `this` 例如:
function test() {
console.log(this)
}
test()
會出現一長串的東西。
若不是物件導向的環境下,預設值為 Global ,node.js 跑是 `global` 的變數,瀏覽器則是 `window` 。也可以在檔案最上方輸入 `'use strict';` 進入嚴格模式,此時的預設值就會是 `undefined` 。
另一個例外是使用 DOM 的時候:
document.querySelector('.dom').addEventListener('click', function() {
console.log(this) // 點擊到的東西
})
### call 和 apply
`.call()` 的第一個值被預設為 this 的值:
function test() {
console.log(this)
}
test.call(123) // [Number: 123]
`apply` 也是:
function test() {
console.log(this)
}
test.apply(123) // [Number: 123]
兩個的差別是後面的參數引入的方法, call 就是用逗號連接,但 apply 只有兩個參數,第二個參數則是將要傳入的參數們用陣列包起來。
#### 怎麼看 this
const obj = {
a: 123,
test: function() {
console.log(this)
}
}
obj.test() // this 對應到 obj 本身
`this` 和放在哪裡無關,而是看呼叫的方法。例如以下寫法雖然一樣,結果卻不同:
const obj = {
a: 123,
test: function() {
console.log(this)
}
}
const func = obj.test
func() // undefined
因為第一個寫法 `obj.test()` 可被視為 `obj.test.call(obj)` ,因此會呼叫到 obj 。
#### bind
小小練習,自己先猜猜看答案:
function log() {
console.log(this);
}
var a = { a: 1, log: log };
var b = { a: 2, log: log };
log(); // global
a.log(); // a
b.log.apply(a) // a,因為 call 的值優先
如果希望不管怎麼呼叫, this 的值都不會變,可以是用 `.bind()` :
const bindTest = obj.test.bind(obj)
之後不管從哪裡呼叫 `bindTest()` , this 的結果都是 obj 。
`bind` 和 `call` / `apply` 的差別在於,前者會回傳一個新的 function ,後者則是直接呼叫。
#### 碰到箭頭函式,一切都不一樣ㄌ
使用到箭頭函式時, this 的值和如何呼叫沒有關係,此時的規則和 scope 比較像,也就是和定義在哪裡有關係。
可以看這個例子:
class Test {
run() {
consoel.log(this) // Test
setTimeOut(function() {
console.log(this) // unefined
}, 1000)
}
}
const t = newTest()
t.run()
但如果用箭頭函式:
class Test {
run() {
consoel.log(this) // Test
setTimeOut(()=>{
console.log(this) // Test
}, 1000)
}
}
const t = newTest()
t.run()
```
從剛剛的例子開始說吧:
function createWallet(init) {
var money = init
return {
add: function(num) {
money += num
},
deduct: function(num) {
money -= num
}
}, getMoney() {
return money
}
}
var myWallet = createWallet(99)
myWallet.add(1)
myWallet.deduct(10)
console.log(myWallet.getMoney()) // 90
這個例子中回傳的值是一個物件,其實就算是物件導向。在使用 JS 時,也時常不是直接 call 一個 function ,而是對某個物件做操作,這種做法的好處是方便模組化。
從 ES6 的 class
開始談起。
首先, class 的名稱一定是大寫開頭,例如:
class Dog {
sayHello() {
console.log('hello')
}
}
class 有點像設計圖,當我們實際使用前時,要用 new
將 class 實體化 (instance):
var d = new Dog()
d.sayHello() // hello
另一個概念是 this
,它會指向呼叫它的東西:
class Dog {
setName(name) {
this.name = name
}
getName() {
return this.name
}
}
var d = new Dog()
d.setName('jojo')
console.log(getName()) // jojo
上面範例中 d.setName('jojo')
中的 this
因為是由 d 呼喚的,因此 this
當然就指向變數 d 。
class 中 setName(name)
這樣的函數被稱為 setter ,讓裡面存取到外面的值;而 sayHello()
則叫 getter ,是讓外面得到 class 的值。 另外我們也可以直接這樣寫:
d.name = 'dio'
console.log(d.name) // dio
但還是建議用 setter 和 getter 。
如果想要用像是函式傳參數的方式設定,可以用 建構子 constructor
:
class Dog {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
var d = new Dog('jojo') // 字串 'jojo' 被傳入 constructor() 中
console.log(getName()) // jojo
var d = new Dog('dio')
console.log(getName()) // dio
ES5 的 class
在 ES5 中沒有 class ,因此要這樣寫:
function Dog(name) {
var myName = name
return {
getName: function() {
return myName
},
sayHello: funcrion() {
console.log(myName)
}
}
}
var d = Dog('jojo')
d.sayHello // jojo
var b = Dog('dio')
d.sayHello // dio
不過因為每次都是呼叫一個新的物件,會出現這種狀況:
console.log(b.sayHello === d.sayHello) // false
不過兩個是同個 function ,所以共用同個 function 比較省記憶體吧?
因此在 ES5 中,可以將 function 當作 constructor 用:
function Dog(name) {
this.name = name
}
var d = new Dog('abc')
console.log(d) // Dog { name: 'abc' }
自動變成物件了!
不過這樣要怎麼知道是 constructor 還是平常的 function?只有加 new
才會被認定是 constructor ,如果沒加就是 function 。
設定屬性的問題搞定了,但要怎麼設定輸出名字和其他操作ㄋ?這時候可以把東西掛在 .prototype
上:
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('jojo')
d.sayHello // jojo
JavaScript 中,每個變數都有個隱藏屬性 __proto__
,暗示如果在 d
上面找不到 sayHello
的屬性:
function Dog(name) {
this.name = name
}
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('jojo')
d.sayHello // jojo
console.log(d.__proto__)
// Dog { sayHello: [Function (anonymous)] }
// 其實就是 Dog.prototype
當我們呼叫 d.sayHello
時,我們其實是做了:
d
本身是否有 sayHello
d.__proto__
是否有 sayHello
,也就是 Dog.prototype
d.__proto__.__proto__
,也就是 Object.prototype
d.__proto__.__proto__.__proto__
,沒有的話會回傳 null。以上都是只要有就會回傳值,沒有的話才往下進行,這個步驟被稱為原型練(Prototype Chain)。
我們來看一下:
console.log(d.__proto__)
// Dog.prototype
// 結果:Dog { sayHello: [Function (anonymous)] }
console.log(d.__proto__.__proto__)
// Dog.prototype.__proto__
// Object.prototype
// 結果:{}
console.log(d.__proto__.__proto__.__proto__)
// null
他們之間的關係如下:
d.__proto__ = Dog.prototype
d.__proto__.__proto__ = Object.prototype
Dog.prototype.__proto__ = Object.prototype
因此我們也可以設定 Object 的 prototype ,這樣就會在第三個步驟呼叫到結果:
Object.prototype.sayHello = function() {
console.log('object', this.name)
}
var d = new Dog('jojo')
d.sayHello // object jojo
如果同時設定 Object 和 Dog 的 prototype ,則會因為原型鍊會先選到 Dog 的:
Dog.prototype.sayHello = function() {
console.log(this.name)
}
Object.prototype.sayHello = function() {
console.log('object', this.name)
}
var d = new Dog('jojo')
d.sayHello // jojo
同理,此處的 Object 如果被換成 Function ,第四個步驟就會被換成 Function.prototype 。
function.call()
這個函數可以指定 function 中的 this
值:
function test() {
console.log(this)
}
test.call(123) // [Number: 123]
接著來拆解 new
到底幫我們做了甚麼,因此用另一個 function 來模擬:
function newDog(name) {
// 模擬 new 做了一些事情
}
// 最後目標
var a = newDog('jojo')
a.sayHello() // 印出 jojo
var a = newDog('jojo') //{ name: 'jojo' }
2. 設定 prototype 連結
function newDog(name) {
var obj = {}
Dog.call(obj, name) // 第一個是 this ,後面依序是傳入值
obj.proto = Dog.prototype
}
var a = newDog('jojo')
3. 回傳 object
function newDog(name) {
var obj = {}
Dog.call(obj, name) // 第一個是 this ,後面依序是傳入值
obj.proto = Dog.prototype
return obj
}
var a = newDog('jojo')
a.sayHello() // 印出 jojo
就完成ㄌ!
### Inheritance
設想有一個狗的 class ,今天我需要設定黑狗和白狗,這時有名字、會叫、丟飛盤會去接回來之類的和狗有關的屬性就不用再設定一次了。要是有人問你「黑狗有幾個眼睛」時,只要回頭查看「狗」的條目就可以了。這就是 `Inheritance` 繼承的概念。
ES6 中的繼承可以這樣寫:
class BlackDog extands Dog{
// 其他黑狗的屬性
}
const d = BlackDog('jojo')
d.sayHello()
上面的例子中 `d.sayHello()` 實際上是往上找到 Dog 的屬性。
此時若我們想讓黑狗被建立的時候就呼叫 `sayHello()`:
class BlackDog extands Dog{
constructure() {
this.sayHello()
}
}
const d = BlackDog('jojo')
這樣會噴錯,因為在 `constructor` 中呼叫 `this` 前要用 `super()` 另外引入上一層的`constructor` ,如下:
class BlackDog extands Dog{
constructure(name) {
super(name)
this.sayHello()
}
}
const d = BlackDog('jojo') // jojo
### this
`this` 在物件導向中被使用,可以用代表其所對應到的 instance 。
如果直接呼叫 `this` 例如:
function test() {
console.log(this)
}
test()
會出現一長串的東西。
若不是物件導向的環境下,預設值為 Global ,node.js 跑是 `global` 的變數,瀏覽器則是 `window` 。也可以在檔案最上方輸入 `'use strict';` 進入嚴格模式,此時的預設值就會是 `undefined` 。
另一個例外是使用 DOM 的時候:
document.querySelector('.dom').addEventListener('click', function() {
console.log(this) // 點擊到的東西
})
### call 和 apply
`.call()` 的第一個值被預設為 this 的值:
function test() {
console.log(this)
}
test.call(123) // [Number: 123]
`apply` 也是:
function test() {
console.log(this)
}
test.apply(123) // [Number: 123]
兩個的差別是後面的參數引入的方法, call 就是用逗號連接,但 apply 只有兩個參數,第二個參數則是將要傳入的參數們用陣列包起來。
#### 怎麼看 this
const obj = {
a: 123,
test: function() {
console.log(this)
}
}
obj.test() // this 對應到 obj 本身
`this` 和放在哪裡無關,而是看呼叫的方法。例如以下寫法雖然一樣,結果卻不同:
const obj = {
a: 123,
test: function() {
console.log(this)
}
}
const func = obj.test
func() // undefined
因為第一個寫法 `obj.test()` 可被視為 `obj.test.call(obj)` ,因此會呼叫到 obj 。
#### bind
小小練習,自己先猜猜看答案:
function log() {
console.log(this);
}
var a = { a: 1, log: log };
var b = { a: 2, log: log };
log(); // global
a.log(); // a
b.log.apply(a) // a,因為 call 的值優先
如果希望不管怎麼呼叫, this 的值都不會變,可以是用 `.bind()` :
const bindTest = obj.test.bind(obj)
之後不管從哪裡呼叫 `bindTest()` , this 的結果都是 obj 。
`bind` 和 `call` / `apply` 的差別在於,前者會回傳一個新的 function ,後者則是直接呼叫。
#### 碰到箭頭函式,一切都不一樣ㄌ
使用到箭頭函式時, this 的值和如何呼叫沒有關係,此時的規則和 scope 比較像,也就是和定義在哪裡有關係。
可以看這個例子:
class Test {
run() {
consoel.log(this) // Test
setTimeOut(function() {
console.log(this) // unefined
}, 1000)
}
}
const t = newTest()
t.run()
但如果用箭頭函式:
class Test {
run() {
consoel.log(this) // Test
setTimeOut(()=>{
console.log(this) // Test
}, 1000)
}
}
const t = newTest()
t.run()
```
先看一個例子:
function test() {
var a = 10
function inner() {
a++
console.log(a)
}
return inner // 回傳 inner 這個函數
}
var func = test()
fanc() // 也就是 inner(), 11
fanc() 12
fanc() 13
所謂 Closure 閉包
就是像這樣,在一個 function 中 return 一個 function 。當我們呼叫裡面的 function 時,裡面的 function 會將外面 finction 的值記起來並鎖在裡面,因此稱為閉包。
如果我們希望能記住上次計算的值,不用再算一次,就可以使用閉包,例如:
function complex(n) {
// 複雜計算
console.log('caculate')
return n * n
}
function cache(func) {
var ans = {}
return function(num) {
if(ans[num]) { // 如果有紀錄就直接回傳值
return ans[num]
}
ans[num] = func(num)
return ans[num]
}
}
const cacheComplex = cache(complex)
console.log(cacheComplex(20)) // caculate 400
console.log(cacheComplex(20)) // 400
console.log(cacheComplex(20)) // 400
只要算過一次 ans[num]
就會被記起來,之後就都不用再跑一次 complex 了。
ECMAScript ES3 版本中有提到,每個 CE 都有一個 Scope Chain
,進入 EC 時, Scope Chain
被初始化為 Activation Object
(其實就是 function 中的 VO
) 和 [[scope]]
。
看一個簡單的例子:
var a = 1
function test() {
var b = 2
function inner() {
var c = 3
console.log(a) // 1
console.log(b) // 2
}
inner()
}
test()
此時底層的狀態是:
Global EC: {
VO: {
a: undefined,
test:func
},
scopeChain: [Global VO]
}
// 初始化一下
test.[[Scope]] = globalEC.scopeChain // [Global.VO]
進到 testEC 之後如下:
test EC: {
AO: {
b: undefined,
inner: func
},
scopeChain: [testEC.AO, test.[[Scope]]]
// 看上一格,也就是 [testEC.AO, globalEC.scopeChain]
// 也就是 [testEC.AO, Global.VO]
}
// 初始化一下
INNER.[[Scope]] = testEC.scopeChain // [testEC.AO, Global.VO]
Global EC: {
VO: {
a: 1,
test: func
},
scopeChain: [Global.VO]
}
最後進入 innerEC:
inner EC: {
AO: {
c: undefined,
},
scopeChain: [innerEC.AO, inner.[[Scope]]]
// 也就是 [innerEC.AO, testEC.AO, Global.VO]
}
test EC: {
AO: {
b: 2,
inner: func
},
scopeChain: [testEC.AO, test.[[Scope]]]
// 也就是 [testEC.AO, Global.VO]
}
Global EC: {
VO: {
a: 1,
test: func
},
scopeChain: [Global.VO]
}
此時回到一開始的例子:
function test() {
var a = 10
function inner() {
a++
console.log(a)
}
return inner // 回傳 inner 這個函數
}
var func = test()
fanc() // 也就是 inner()
最後一行執行時 test EC
已經結束,本來底層機制應該要全部拿掉,但因為 innerEC.scopeChain
是 [innerEC.AO, testEC.AO, Global.VO]
,因此 testEC.AO
還不能那麼快退場。這就是為什麼 inner 可以拿到上一層變數值並儲存更改的原因。
不過偶爾閉包也會產生一些問題,例如外層包了超大的物件,就算之後只使用內層,因為關聯外層的 AO ,那個超大物件就無法被回收。
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[0]()
本來預期會得到一到五,結果出來卻只有 5 。
此處的 i 是一個 global 的變數,當我們呼叫 arr[0]()
時,是進到 for 迴圈中拿函數,因此函數中的 scope chain 會連動到 global 的 AO ,呼叫時 for 迴圈已經跑完,所以 global.VO
中 i 的值是 5 ,延用該 VO 的函數自然而然會輸出 5 。
解決方法:
function logN(n) { // 閉包的概念
return function() {
console.log(n)
}
}
const log2 = logN(2)
log2() // 2
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = logN(i)
}
arr[0]() // 0
因為 arr[i]
會迴傳一個新的 function ,因此會產生新的作用域去記住傳入的值。
也可以使用 IIFE
,也就是立即呼叫函式,例如:
(() => {
console.log(123)
})() // 立刻執行,輸出 123
回到剛剛的問題,我們也可以把剛剛的函式 logN
放進去:
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = (function(num) {
return function() {
console.log(num)
}
})(i)
}
arr[0]() // 0
也可以直接這樣寫:
var arr = []
for (let i = 0; i < 5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[0]()
因為 let
的作用域只存在 block 中,迴圈等於是跑了五個 block arr[i] = function() { console.log(i) }
,每一圈都有自己的作用域,所以呼叫 arr[0]
時自然就找到印出 0 的函數。
Closure 隱藏資訊不被額外操控的時候很好用,例如:
var money = 99
function add(num) {
money += num
}
function deduct(mun) {
money -= num
}
add(1)
deduct(10)
console.log(money) // 90
// 這時你同事很壞,加了一行
money -= 90
// 就算繞過任何操作還是可以改變 money 的值
此時就可以使用 Closure:
function createWallet(init) {
var money = init
return {
add: function(num) {
money += num
},
deduct: function(num) {
money -= num
}
}, getMoney() {
return money
}
}
var myWallet = createWallet(99)
myWallet.add(1)
myWallet.deduct(10)
console.log(myWallet.getMoney()) // 90
]]>先看一個例子:
function test() {
var a = 10
function inner() {
a++
console.log(a)
}
return inner // 回傳 inner 這個函數
}
var func = test()
fanc() // 也就是 inner(), 11
fanc() 12
fanc() 13
所謂 Closure 閉包
就是像這樣,在一個 function 中 return 一個 function 。當我們呼叫裡面的 function 時,裡面的 function 會將外面 finction 的值記起來並鎖在裡面,因此稱為閉包。
如果我們希望能記住上次計算的值,不用再算一次,就可以使用閉包,例如:
function complex(n) {
// 複雜計算
console.log('caculate')
return n * n
}
function cache(func) {
var ans = {}
return function(num) {
if(ans[num]) { // 如果有紀錄就直接回傳值
return ans[num]
}
ans[num] = func(num)
return ans[num]
}
}
const cacheComplex = cache(complex)
console.log(cacheComplex(20)) // caculate 400
console.log(cacheComplex(20)) // 400
console.log(cacheComplex(20)) // 400
只要算過一次 ans[num]
就會被記起來,之後就都不用再跑一次 complex 了。
ECMAScript ES3 版本中有提到,每個 CE 都有一個 Scope Chain
,進入 EC 時, Scope Chain
被初始化為 Activation Object
(其實就是 function 中的 VO
) 和 [[scope]]
。
看一個簡單的例子:
var a = 1
function test() {
var b = 2
function inner() {
var c = 3
console.log(a) // 1
console.log(b) // 2
}
inner()
}
test()
此時底層的狀態是:
Global EC: {
VO: {
a: undefined,
test:func
},
scopeChain: [Global VO]
}
// 初始化一下
test.[[Scope]] = globalEC.scopeChain // [Global.VO]
進到 testEC 之後如下:
test EC: {
AO: {
b: undefined,
inner: func
},
scopeChain: [testEC.AO, test.[[Scope]]]
// 看上一格,也就是 [testEC.AO, globalEC.scopeChain]
// 也就是 [testEC.AO, Global.VO]
}
// 初始化一下
INNER.[[Scope]] = testEC.scopeChain // [testEC.AO, Global.VO]
Global EC: {
VO: {
a: 1,
test: func
},
scopeChain: [Global.VO]
}
最後進入 innerEC:
inner EC: {
AO: {
c: undefined,
},
scopeChain: [innerEC.AO, inner.[[Scope]]]
// 也就是 [innerEC.AO, testEC.AO, Global.VO]
}
test EC: {
AO: {
b: 2,
inner: func
},
scopeChain: [testEC.AO, test.[[Scope]]]
// 也就是 [testEC.AO, Global.VO]
}
Global EC: {
VO: {
a: 1,
test: func
},
scopeChain: [Global.VO]
}
此時回到一開始的例子:
function test() {
var a = 10
function inner() {
a++
console.log(a)
}
return inner // 回傳 inner 這個函數
}
var func = test()
fanc() // 也就是 inner()
最後一行執行時 test EC
已經結束,本來底層機制應該要全部拿掉,但因為 innerEC.scopeChain
是 [innerEC.AO, testEC.AO, Global.VO]
,因此 testEC.AO
還不能那麼快退場。這就是為什麼 inner 可以拿到上一層變數值並儲存更改的原因。
不過偶爾閉包也會產生一些問題,例如外層包了超大的物件,就算之後只使用內層,因為關聯外層的 AO ,那個超大物件就無法被回收。
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[0]()
本來預期會得到一到五,結果出來卻只有 5 。
此處的 i 是一個 global 的變數,當我們呼叫 arr[0]()
時,是進到 for 迴圈中拿函數,因此函數中的 scope chain 會連動到 global 的 AO ,呼叫時 for 迴圈已經跑完,所以 global.VO
中 i 的值是 5 ,延用該 VO 的函數自然而然會輸出 5 。
解決方法:
function logN(n) { // 閉包的概念
return function() {
console.log(n)
}
}
const log2 = logN(2)
log2() // 2
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = logN(i)
}
arr[0]() // 0
因為 arr[i]
會迴傳一個新的 function ,因此會產生新的作用域去記住傳入的值。
也可以使用 IIFE
,也就是立即呼叫函式,例如:
(() => {
console.log(123)
})() // 立刻執行,輸出 123
回到剛剛的問題,我們也可以把剛剛的函式 logN
放進去:
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = (function(num) {
return function() {
console.log(num)
}
})(i)
}
arr[0]() // 0
也可以直接這樣寫:
var arr = []
for (let i = 0; i < 5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[0]()
因為 let
的作用域只存在 block 中,迴圈等於是跑了五個 block arr[i] = function() { console.log(i) }
,每一圈都有自己的作用域,所以呼叫 arr[0]
時自然就找到印出 0 的函數。
Closure 隱藏資訊不被額外操控的時候很好用,例如:
var money = 99
function add(num) {
money += num
}
function deduct(mun) {
money -= num
}
add(1)
deduct(10)
console.log(money) // 90
// 這時你同事很壞,加了一行
money -= 90
// 就算繞過任何操作還是可以改變 money 的值
此時就可以使用 Closure:
function createWallet(init) {
var money = init
return {
add: function(num) {
money += num
},
deduct: function(num) {
money -= num
}
}, getMoney() {
return money
}
}
var myWallet = createWallet(99)
myWallet.add(1)
myWallet.deduct(10)
console.log(myWallet.getMoney()) // 90
]]>
如果只輸入 console.log(b)
會因為 b 沒有被宣告過而噴錯,但如果這樣寫:
console.log(b)
var b = 20
第一行會顯示 indefined
的結果,是因為對 JavaScript 來說,其實是:
var b
console.log(b)
b = 20
這個現象叫做 hoistion
提升,在 JS 中,只有宣告 var b
會被提升,賦值 b = 20
並不會。
function 也會提升:
test() // 123
function test() {
console.log(123)
}
值得注意的是,下列寫法會出錯:
test() // test is not a function
var test = () => {
console.log(123)
}
因為對 JS 來說,實際上提升的只有宣告,所以是這樣:
var test
test()
test = () => {
console.log(123)
}
hoisting 只會發生在自己的 scope 中,例如:
var a = 'global'
function test() {
console.log(a)
var a = 'local'
}
test()
會印出 undefined
,因為 function 內有個 hoisting,所以實際上是這樣:
var a = 'global'
function test() {
var a
console.log(a)
a = 'local'
}
test()
function
的提升會佔有優先權:console.log(a) // [Function a]
function a() {}
var a = 'a'
可以看成這樣:function a() {}
console.log(a) // [Function a]
var a = 'a'
console.log(a) // 2
var a = 1
var a = 2
function test(a) {
console.log(a) // 123
var a = 456
}
test(123)
因為上述提升後只是先定義 a 只是「我要宣告變數 a ㄛ~」沒有影響,但賦值會影響:function test(a) {
var a = undefined
console.log(a) // undefined
a = 456
}
test(123)
function test() {
console.log(a) // [Function a]
function a() {}
}
test(123)
因此可歸納出 hoisting 的優先順序:
開始之前先試著自己做做看這個題目:
var a = 1;
function test(){
console.log('1.', a);
var a = 7;
console.log('2.', a);
a++;
var a;
inner();
console.log('4.', a);
function inner(){
console.log('3.', a);
a = 30;
b = 200;
}
}
test();
console.log('5.', a);
a = 70;
console.log('6.', a);
console.log('7.', b);
我先猜答案是:
1. 1
2. 7
3. 8
4. 30
5. 30
6. 70
7. b is not defined
我們先 hoisting 成 JS 真正跑的順序好了:
var a = 1;
function test(){
var a // hoisting 上來
console.log('1.', a); // 找到上一行,undefined
a = 7;
console.log('2.', a); // 7
a++; // 此時 a = 8
var a; // 沒有影響,已經有 a 了
inner();
console.log('4.', a); // 可看下三行已經被改成 30
function inner(){
console.log('3.', a); // 本身沒有宣告,往上一層找 a = 8
a = 30; // 因為沒有用 var 宣告,因此更改到 test() 中的 a
b = 200; // 因為沒有用 var 宣告, b 變成全域變數
}
}
test();
console.log('5.', a); // 和 test scope 無關了,看全域 a = 1
a = 70;
console.log('6.', a); // 70
console.log('7.', b); // inner 的 b 是全域變數,因此是 200
因此答案是:
1. undefined
2. 7
3. 8
4. 30
5. 1
6. 70
7. 200
接著來看 ECMAScript ES3 的部分
我們一開始再粉紅色的 Global Execution Context
,之後每進入一層函式就堆高一層,結束後就抽掉退出(可以想像玩疊疊樂?或同時看很多本書,最上面的是正在看的,看完就放到一邊),最上面的表示現在所在位置。整個程式結束時會回到最下層。
每個 Execution Context 中都有一個 Variable Object
(VO) ,可以想像成是一個物件,每個變數和值都會對應到 key 和 value 。例如:
var a = 1
// 這裡的 VO 可以想成
VO: {
a: 1
}
當進入新的 Execution Context (例如一個 function )時, VO 會自動初始化。順序如下:
之後才會開始跑裡面的 code 。
回頭看剛剛那題:
var a = 1; //1
function test(){
console.log('1.', a); // 3
var a = 7; // 4
console.log('2.', a); // 5
a++; //6
var a; // 7
inner(); // 8
console.log('4.', a); // 12
function inner(){
console.log('3.', a); // 9
a = 30; // 10
b = 200; // 11
}
}
test(); // 2
console.log('5.', a); // 13
a = 70; // 14
console.log('6.', a); //15
console.log('7.', b); //16
一開始進去的時後 global VO
開始初始化:
global VO: {
test: function,
a: undefined
}
global VO
的 a 變成 1test VO
初始化:test VO: {
inner: function,
a: undefined
}
test VO
中 a 是 undefined ,輸出。test VO
的 a 變成 7。test VO
的 a 是 7,輸出。test VO
的 a 變成 8 。test VO
初始化:test VO: {
// 沒有任何參數、變數和函式,因此是空的
}
inner VO
中沒有 a ,往上找到 test VO
中的 a 是 8 ,回傳。inner VO
中沒有 a ,往上找到 test VO
改 a 的值為 30 。inner VO
中沒有 b ,往上找 test VO
,因此將 b: 200 放在 global VO
中(也就是變成全域變數)。inner() 執行結束,抽掉 inner EC
。test VO
中的值為 30 。test() 執行結束,抽掉 test EC
。global VO
的 a 為 1 (可見第一條),回傳 。global VO
的 a 改變成 70 。global VO
的 a 為 70,回傳。global VO
的 b 為 200(可見 11 條),回傳。Global EC
。先看一個情境:
console.log(a)
let a = 20
結果竟然會噴錯!難道 let 和 const 是沒有 hoisting 的嗎?!
其實 let 和 const 是有 hoisting 的,只是有一些奇怪的限制。我們先將 hoisting 後的結果寫下來:
let a
console.log(a)
a = 20
在使用 let 和 const 宣告變數的時候,在變數被賦值之前都不能被使用,因此才會噴錯。在宣告候到賦值前的區塊,有個詞叫 Temporal Dead Zone
,在區域中不能取用這個值~
如果只輸入 console.log(b)
會因為 b 沒有被宣告過而噴錯,但如果這樣寫:
console.log(b)
var b = 20
第一行會顯示 indefined
的結果,是因為對 JavaScript 來說,其實是:
var b
console.log(b)
b = 20
這個現象叫做 hoistion
提升,在 JS 中,只有宣告 var b
會被提升,賦值 b = 20
並不會。
function 也會提升:
test() // 123
function test() {
console.log(123)
}
值得注意的是,下列寫法會出錯:
test() // test is not a function
var test = () => {
console.log(123)
}
因為對 JS 來說,實際上提升的只有宣告,所以是這樣:
var test
test()
test = () => {
console.log(123)
}
hoisting 只會發生在自己的 scope 中,例如:
var a = 'global'
function test() {
console.log(a)
var a = 'local'
}
test()
會印出 undefined
,因為 function 內有個 hoisting,所以實際上是這樣:
var a = 'global'
function test() {
var a
console.log(a)
a = 'local'
}
test()
function
的提升會佔有優先權:console.log(a) // [Function a]
function a() {}
var a = 'a'
可以看成這樣:function a() {}
console.log(a) // [Function a]
var a = 'a'
console.log(a) // 2
var a = 1
var a = 2
function test(a) {
console.log(a) // 123
var a = 456
}
test(123)
因為上述提升後只是先定義 a 只是「我要宣告變數 a ㄛ~」沒有影響,但賦值會影響:function test(a) {
var a = undefined
console.log(a) // undefined
a = 456
}
test(123)
function test() {
console.log(a) // [Function a]
function a() {}
}
test(123)
因此可歸納出 hoisting 的優先順序:
開始之前先試著自己做做看這個題目:
var a = 1;
function test(){
console.log('1.', a);
var a = 7;
console.log('2.', a);
a++;
var a;
inner();
console.log('4.', a);
function inner(){
console.log('3.', a);
a = 30;
b = 200;
}
}
test();
console.log('5.', a);
a = 70;
console.log('6.', a);
console.log('7.', b);
我先猜答案是:
1. 1
2. 7
3. 8
4. 30
5. 30
6. 70
7. b is not defined
我們先 hoisting 成 JS 真正跑的順序好了:
var a = 1;
function test(){
var a // hoisting 上來
console.log('1.', a); // 找到上一行,undefined
a = 7;
console.log('2.', a); // 7
a++; // 此時 a = 8
var a; // 沒有影響,已經有 a 了
inner();
console.log('4.', a); // 可看下三行已經被改成 30
function inner(){
console.log('3.', a); // 本身沒有宣告,往上一層找 a = 8
a = 30; // 因為沒有用 var 宣告,因此更改到 test() 中的 a
b = 200; // 因為沒有用 var 宣告, b 變成全域變數
}
}
test();
console.log('5.', a); // 和 test scope 無關了,看全域 a = 1
a = 70;
console.log('6.', a); // 70
console.log('7.', b); // inner 的 b 是全域變數,因此是 200
因此答案是:
1. undefined
2. 7
3. 8
4. 30
5. 1
6. 70
7. 200
接著來看 ECMAScript ES3 的部分
我們一開始再粉紅色的 Global Execution Context
,之後每進入一層函式就堆高一層,結束後就抽掉退出(可以想像玩疊疊樂?或同時看很多本書,最上面的是正在看的,看完就放到一邊),最上面的表示現在所在位置。整個程式結束時會回到最下層。
每個 Execution Context 中都有一個 Variable Object
(VO) ,可以想像成是一個物件,每個變數和值都會對應到 key 和 value 。例如:
var a = 1
// 這裡的 VO 可以想成
VO: {
a: 1
}
當進入新的 Execution Context (例如一個 function )時, VO 會自動初始化。順序如下:
之後才會開始跑裡面的 code 。
回頭看剛剛那題:
var a = 1; //1
function test(){
console.log('1.', a); // 3
var a = 7; // 4
console.log('2.', a); // 5
a++; //6
var a; // 7
inner(); // 8
console.log('4.', a); // 12
function inner(){
console.log('3.', a); // 9
a = 30; // 10
b = 200; // 11
}
}
test(); // 2
console.log('5.', a); // 13
a = 70; // 14
console.log('6.', a); //15
console.log('7.', b); //16
一開始進去的時後 global VO
開始初始化:
global VO: {
test: function,
a: undefined
}
global VO
的 a 變成 1test VO
初始化:test VO: {
inner: function,
a: undefined
}
test VO
中 a 是 undefined ,輸出。test VO
的 a 變成 7。test VO
的 a 是 7,輸出。test VO
的 a 變成 8 。test VO
初始化:test VO: {
// 沒有任何參數、變數和函式,因此是空的
}
inner VO
中沒有 a ,往上找到 test VO
中的 a 是 8 ,回傳。inner VO
中沒有 a ,往上找到 test VO
改 a 的值為 30 。inner VO
中沒有 b ,往上找 test VO
,因此將 b: 200 放在 global VO
中(也就是變成全域變數)。inner() 執行結束,抽掉 inner EC
。test VO
中的值為 30 。test() 執行結束,抽掉 test EC
。global VO
的 a 為 1 (可見第一條),回傳 。global VO
的 a 改變成 70 。global VO
的 a 為 70,回傳。global VO
的 b 為 200(可見 11 條),回傳。Global EC
。先看一個情境:
console.log(a)
let a = 20
結果竟然會噴錯!難道 let 和 const 是沒有 hoisting 的嗎?!
其實 let 和 const 是有 hoisting 的,只是有一些奇怪的限制。我們先將 hoisting 後的結果寫下來:
let a
console.log(a)
a = 20
在使用 let 和 const 宣告變數的時候,在變數被賦值之前都不能被使用,因此才會噴錯。在宣告候到賦值前的區塊,有個詞叫 Temporal Dead Zone
,在區域中不能取用這個值~
發現之前做過的筆記有一點點重覆:
[Week2] 給自己看的 JavaScript 筆記 - 迴圈、函式、其他觀念
七種資料型態:
primitive type 原始型態
其他都是 boject 物件
原始型態都是 Immutable ,也就是不能改變。
let a = 10
a = 20
是重新賦值而非改變,改變例如:
var str = 'hello'
str.toUpperCase()
consoel.log(str) // 'hello'
上面例子中第二行雖然會回傳新字串 HELLO
,但並不會影響 str
本身。
var str = 'hello'
var newStr = str.toUpperCase()
consoel.log(newStr) // 'HELLO'
array 因為不是原始型態,因此可以被改變:
var arr = ['a']
arr.push('b')
console.log(arr) / ['a', 'b']
偵測型態
typeof
可以偵測型態:
console.log(typeof 10) // number
console.log(typeof []) // object
console.log(typeof function() {}) // function
不過有些小 bug 例如:
console.log(typeof null) // object
MDN - typeof 對此解釋如下:
自從 JavaScript 一開始出現, JavaScript 的值就總以型別標簽跟著一個值的方式表示。物件的型別標簽是 0. 而 null 這個值是使用 NULL 指標 (在大部份平台上是 0x00) 來表示. 因此, null 看起來像是一個以 0 為型別標簽的值, 並使得 typeof 傳回不甚正確的結果.
typeof
時常用在檢測錯誤上。例如 undefined 不能直接被 console.log 出來,此時就可以先用 typeof :
// a 沒有被宣告過,因此 a !== undefined 也是 true
if (typeof a !== 'undefined') {
console.log(a)
}
就可以避免噴錯了。
不過 typeof 無法檢測一個變數是不是 Array ,因此可以用:
conaole.log(Array.isArray([])) // true
這裡要注意有些比較舊的瀏覽器上沒有這個方法。
另一個檢測型態的方法:
console.log(Object.prototype.toSting.call(<欲檢測物>))
console.log(Object.prototype.toSting.call(1)) // [Object Number]
console.log(Object.prototype.toSting.call('a')) // [Object String]
console.log(Object.prototype.toSting.call(null)) // [Object Null]
=
primitive type 的賦值是直接儲存在記憶體中,因此賦值時很直觀:
var a = 10
var b = a
b = 20
console.log(a, b) // 10 20
但 object 和 array 就沒那麼簡單了:
var a = { num: 10 }
var b = a
b.num = 20
consolo.log(a, b) // { num: 20 } { num: 20 }
這四行電腦實際在做的事情如下:
{ num: 10 }
存入某個記憶體位置 0x01
中。0x01
。0x01
中的內容,將 num 改成 20 。0x01
的內容,因此找到 { num: 20 }
。不過,如果指定 b 的新值時用別的方法,結果又會不一樣了。
var a = { num: 10 }
var b = a
b = { num: 20 }
consolo.log(a, b) // { num: 10 } { num: 20 }
因為對電腦來說,這四行是在:
{ num: 10 }
存入某個記憶體位置 0x01
中。0x01
。0x02
,並將 { num: 20 }
放入。0x01
的內容,因此找到 { num: 10 }
。另外,若在判斷時使用 =
:
if (a = 20) {
consoel.log('hihi') // hihi
}
對電腦來說:
console.log(2 == '2') // true
console.log(2 === '2') //false
==
會先將兩邊的型態轉換成一樣的,但 ===
不會,因此會連型態一起檢查。因此會優先推薦使用 ===
最嚴謹,比較不會出 bug 。
值得注意的是,非 primitive type 的物件中, ===
比較的實際上不是內容,而是記憶體位置。因此:
var arr1 = [1]
var arr2 = [1]
console.log(arr1 === arr2) // false
arr2 = arr1
console.log(arr1 === arr2) // true
也因此 {} === {}
的結果會是 false 。
特殊例子
NaN
是一種數字,表示它並不是一個數字,例如:
var a = Number('hi')
console.log(typeof a) // NaN
此時來看看:
console.log(a === a) // false
NaN 不等於任何東西,包含它自己。
若要檢測某個東西是否為 NaN 可用 isNaN()
。
ES6 以前作用域的基本單位是 function ,出了 function 就失去作用。
在任何 function 外的變數被稱為全域 (global) 變數,其在所有 function 內皆適用(因為會往上找)。
var a = 20
function test() {
var a = 10
console.log(a)
}
test() // 10
console.log(a) // 找到全域變數的 a 是 20
但若第三行不是再宣告一次變數 a
而是重新賦值,結果會如下:
var a = 20
function test() {
a = 10
console.log(a)
}
test() // 10 ,還順便更改了 a 的值
console.log(a) // 10
如果在 function 內沒有使用 var
宣告,會自動被認為是全域變數:
function test() {
a = 10 // 變成全域變數
console.log(a)
}
test() // 10
console.log(a) // 10
來看個複雜一點的例子:
var a = 'global'
function test() {
var a = 'test scope a'
var b = 'test scope b'
console.log(a, b) // test scope a test scope b
function inner() {
var b = 'inner b'
console.log(a, b) // test scope a inner b
}
inner()
}
test()
在 inner()
中,因為找不到 a 這個變數,所以往上一層找到 var a = 'test scope a'
。往上找的過程稱為 scope chain
。
另外, scope 之間也不會共享變數, scope chain 和在哪裡有關,而不是在哪裡被呼叫。
var a = 'global'
function change() {
var a = 'change'
test()
}
fuction test() {
console.log(a) // global
}
change()
上面例子中,因為 test scope 中沒有定義 a ,因此會往上找到全域變數 var a = 'global'
,和 change() 中的 a 一點關係也沒有。
回到 const 和 let
var 的作用域在 function 之中,但 const 和 let 的作用域僅限 block (看到 {}
都算)。
var a = 60
if (a === 60) {
var b = 10
}
consoel.log(b) // 10
// 另一個情境
var a = 60
if (a === 60) {
let b = 10 // 只在 if 裡面可用
}
consoel.log(b) // 錯誤
另外, constance
是常數,宣告 const
時一定要給一個初始值,而且一旦給定就不能變動。
不過因為 object 的儲存方式是依賴記憶體位置,因此只要記憶體沒變就可:
conat arr = [1]
arr.push(2) // 可以
arr = [1, 2] // 不行,噴 error
]]>發現之前做過的筆記有一點點重覆:
[Week2] 給自己看的 JavaScript 筆記 - 迴圈、函式、其他觀念
七種資料型態:
primitive type 原始型態
其他都是 boject 物件
原始型態都是 Immutable ,也就是不能改變。
let a = 10
a = 20
是重新賦值而非改變,改變例如:
var str = 'hello'
str.toUpperCase()
consoel.log(str) // 'hello'
上面例子中第二行雖然會回傳新字串 HELLO
,但並不會影響 str
本身。
var str = 'hello'
var newStr = str.toUpperCase()
consoel.log(newStr) // 'HELLO'
array 因為不是原始型態,因此可以被改變:
var arr = ['a']
arr.push('b')
console.log(arr) / ['a', 'b']
偵測型態
typeof
可以偵測型態:
console.log(typeof 10) // number
console.log(typeof []) // object
console.log(typeof function() {}) // function
不過有些小 bug 例如:
console.log(typeof null) // object
MDN - typeof 對此解釋如下:
自從 JavaScript 一開始出現, JavaScript 的值就總以型別標簽跟著一個值的方式表示。物件的型別標簽是 0. 而 null 這個值是使用 NULL 指標 (在大部份平台上是 0x00) 來表示. 因此, null 看起來像是一個以 0 為型別標簽的值, 並使得 typeof 傳回不甚正確的結果.
typeof
時常用在檢測錯誤上。例如 undefined 不能直接被 console.log 出來,此時就可以先用 typeof :
// a 沒有被宣告過,因此 a !== undefined 也是 true
if (typeof a !== 'undefined') {
console.log(a)
}
就可以避免噴錯了。
不過 typeof 無法檢測一個變數是不是 Array ,因此可以用:
conaole.log(Array.isArray([])) // true
這裡要注意有些比較舊的瀏覽器上沒有這個方法。
另一個檢測型態的方法:
console.log(Object.prototype.toSting.call(<欲檢測物>))
console.log(Object.prototype.toSting.call(1)) // [Object Number]
console.log(Object.prototype.toSting.call('a')) // [Object String]
console.log(Object.prototype.toSting.call(null)) // [Object Null]
=
primitive type 的賦值是直接儲存在記憶體中,因此賦值時很直觀:
var a = 10
var b = a
b = 20
console.log(a, b) // 10 20
但 object 和 array 就沒那麼簡單了:
var a = { num: 10 }
var b = a
b.num = 20
consolo.log(a, b) // { num: 20 } { num: 20 }
這四行電腦實際在做的事情如下:
{ num: 10 }
存入某個記憶體位置 0x01
中。0x01
。0x01
中的內容,將 num 改成 20 。0x01
的內容,因此找到 { num: 20 }
。不過,如果指定 b 的新值時用別的方法,結果又會不一樣了。
var a = { num: 10 }
var b = a
b = { num: 20 }
consolo.log(a, b) // { num: 10 } { num: 20 }
因為對電腦來說,這四行是在:
{ num: 10 }
存入某個記憶體位置 0x01
中。0x01
。0x02
,並將 { num: 20 }
放入。0x01
的內容,因此找到 { num: 10 }
。另外,若在判斷時使用 =
:
if (a = 20) {
consoel.log('hihi') // hihi
}
對電腦來說:
console.log(2 == '2') // true
console.log(2 === '2') //false
==
會先將兩邊的型態轉換成一樣的,但 ===
不會,因此會連型態一起檢查。因此會優先推薦使用 ===
最嚴謹,比較不會出 bug 。
值得注意的是,非 primitive type 的物件中, ===
比較的實際上不是內容,而是記憶體位置。因此:
var arr1 = [1]
var arr2 = [1]
console.log(arr1 === arr2) // false
arr2 = arr1
console.log(arr1 === arr2) // true
也因此 {} === {}
的結果會是 false 。
特殊例子
NaN
是一種數字,表示它並不是一個數字,例如:
var a = Number('hi')
console.log(typeof a) // NaN
此時來看看:
console.log(a === a) // false
NaN 不等於任何東西,包含它自己。
若要檢測某個東西是否為 NaN 可用 isNaN()
。
ES6 以前作用域的基本單位是 function ,出了 function 就失去作用。
在任何 function 外的變數被稱為全域 (global) 變數,其在所有 function 內皆適用(因為會往上找)。
var a = 20
function test() {
var a = 10
console.log(a)
}
test() // 10
console.log(a) // 找到全域變數的 a 是 20
但若第三行不是再宣告一次變數 a
而是重新賦值,結果會如下:
var a = 20
function test() {
a = 10
console.log(a)
}
test() // 10 ,還順便更改了 a 的值
console.log(a) // 10
如果在 function 內沒有使用 var
宣告,會自動被認為是全域變數:
function test() {
a = 10 // 變成全域變數
console.log(a)
}
test() // 10
console.log(a) // 10
來看個複雜一點的例子:
var a = 'global'
function test() {
var a = 'test scope a'
var b = 'test scope b'
console.log(a, b) // test scope a test scope b
function inner() {
var b = 'inner b'
console.log(a, b) // test scope a inner b
}
inner()
}
test()
在 inner()
中,因為找不到 a 這個變數,所以往上一層找到 var a = 'test scope a'
。往上找的過程稱為 scope chain
。
另外, scope 之間也不會共享變數, scope chain 和在哪裡有關,而不是在哪裡被呼叫。
var a = 'global'
function change() {
var a = 'change'
test()
}
fuction test() {
console.log(a) // global
}
change()
上面例子中,因為 test scope 中沒有定義 a ,因此會往上找到全域變數 var a = 'global'
,和 change() 中的 a 一點關係也沒有。
回到 const 和 let
var 的作用域在 function 之中,但 const 和 let 的作用域僅限 block (看到 {}
都算)。
var a = 60
if (a === 60) {
var b = 10
}
consoel.log(b) // 10
// 另一個情境
var a = 60
if (a === 60) {
let b = 10 // 只在 if 裡面可用
}
consoel.log(b) // 錯誤
另外, constance
是常數,宣告 const
時一定要給一個初始值,而且一旦給定就不能變動。
不過因為 object 的儲存方式是依賴記憶體位置,因此只要記憶體沒變就可:
conat arr = [1]
arr.push(2) // 可以
arr = [1, 2] // 不行,噴 error
]]>
給未來求職前的自己作為回頭複習ㄉ筆記
or(如果我寫到一半發懶)
直接想像自己在面試時被問到,我會怎麼回~
如果使用者密碼直接被存入資料庫中,只要資料庫直接被偷看(像是在圖書館使用時上廁所忘了換頁),或用其他攻擊方式直接叫出密碼,使用者的資訊安全就會受到威脅,因此存在資料庫中密碼欄位的密碼最好不是原始的樣子。
雜湊和加密都是把原始的明碼輸入轉換成密碼,都是只要輸入字串和加密方法一樣,就可以得到一樣的密碼。
差別在加密是一對一,因此若知道加密規則和輸出結果,就可以回推出原本的明碼,是一對一的關係;但雜湊 (hash) 是多對一的關係,也就是雖然每次輸入都會得到同樣的輸出,但因為不止這組輸入會得到一樣的輸出,因此就算知道加密規則和結果,也無法回推原本是哪個字串,因此安全性又更高。
PHP 中雜湊的語法如下:
```
password_hash(<string>, <編碼方式>)
//可轉換成 hash ,建議存到預留 60 字元以上的欄位
password_verifty(<明文>, <hash>)
//對照兩者是否相同。
```
SQL Injection 是在輸入的字串中夾帶 SQL 的語法,又因為字串拼接設計不良,夾帶進去的內容被當成 SQL 語法的一部份執行而導致的資安漏洞。以下舉個例子:
程式碼如下:
```
$sql = sprintf("INSERT INTO comments(username, content) VALUES('%s', '%s')", $_POST['username'], $_POST['content']);
```
只要我在 content 的欄位輸入 '), ('not_me', (select password from users));#
,就會變成這樣:
$sql = INSERT INTO comments(username, content) VALUES('me', ''), ('not_me', (select password from users));#')
因為 #
後面會變成附註,因此變成新增兩條留言,第二條的 username 和 content 附註,其中內容甚至可以叫出所有用戶的資料。超危險。
解決方法:使用 SQL 內建機制拼接
$sql =
// 這邊預留空間的方法是用問號 '?'
$stmt = $conn->prepare($sql) ;
// 先準備好
$stmt->bind_param('<拼接什麼>', $<變數1>, $<變數2>) ;
// 如果要拼接兩個字串,就用 'ss' ,整數的話就是 'i'
$result = $stmt->execute()
// 執行 query
// 接著用原本的判斷式檢測是否執行成功
$result = $stmt->get_result()
//這樣才算拿到結果
XSS 全名 Cross-Site Scripting
,也就是跨網站執行 JavaScript。例如顯示留言的地方的 HTML 如下:
<div class='content'><?php echo $row['content']?><div>
這時只要使用者輸入 html 或 JavaScript 語法,例如 <script>alert('hacked')</script>
,就會直接被解讀為程式碼的一部份並被執行。
防範方法:字元跳脫
函式 htmlspecialchars()
可將 html 的特殊符號轉換成純文字。
值得注意的是,因為資料庫儲存原始資料就好,因此建議在輸出處使用。另外,因為不知道攻擊會從哪裡來,因此建議所有輸出都使用字元跳脫。
因為前端的驗證只針對瀏覽器畫面上的驗證,但除此之外還有許多漏洞,例如不透過瀏覽器發 request 給後端即可繞過。通常簡單如「是否有東西漏填」這種驗證會給前端,而和資安相關的例如身分驗證則由後端負責。
參考資料:
讓我們來談談 CSRF
[week 11] 資訊安全 - 雜湊與加密 & 常見攻擊:SQL Injection、XSS
CSRF 的全名是 是 Cross Site Request Forgery
(跨站請求偽造)。也被稱為 one-click attack 或 session riding。簡單來說就是「在不同 domain 下,偽造使用者本人發出的 request」。
平時登入網站後會得到一組 SESSION ,之後就算離開該網站,瀏覽器還是會幫你在 COOKIE 中保留 SESSION 一段時間。此時如果有個釣魚網站騙你在不知情的情況下,對該網站送出 Request (例如放在按鈕或圖片中),此時因為瀏覽器還留著你的 SESSION ,該網站以為是合法請求並接受,這樣一來釣魚網站就可以用你的身分在該網站進行操作。
不同層面的防範辦法
對使用者端來說,最好懂的就是隨時清空 SESSION,不過這就代表每次都要重新登入,很麻煩。
Server 端可以進行雙重驗證,像是輸入驗證碼就很直接,但每個動作都要輸入一次驗證碼有點麻煩,因此最好的方法是多加一層雙重認證。可以用表單中夾帶 CSRF token ,資料確認時也要保證其有被帶上才執行,產生和儲存都由 Server 端負責,但如果攻擊者先發出 request 就可破解;第二種方法是 Double Submit Cookie ,利用「cookie 只會從相同 domain 帶上來」的特性,把資料存在使用端,但只要攻擊者掌握任何 subdomain 就可破解;最後一種是由 Client 產生 token ,同時放到表單和 cookie 中。
瀏覽器方面, Chrome 也有提供 SameSite cookie 幫 cookie 加上一層驗證。
Single Page Application
簡稱 SPA ,單介面應用程式,顧名思義就是所有動作都在一個頁面上完成,不會重新導向 index.html 以外的地方。其原理是 JavaScript 用 Ajax 動態從 Server 端拿資料,再即時 render 到 client 端上。
優點:
缺點
PHP 基礎語法:
// 建立一個空陣列
$json = array();
// 在陣列中增加一個新的物件
$array_push($json, array(
"id" => 13
));
// 轉換成 json 格式
$response = json_encode($json);
// 印出來看看
echo $respons;
// 結果 [{"id": 13}]
之後前端只要讀取 PHP echo 的 JSON 格式結果就好。
串接 API 的方法有很多種,以下示範用 jQuery 實作 Ajax 的方法:
$.ajax({
method: 'GET',
url: 'https://'
}).done(function(data) {
console.log(data)
}).fail(function(err)) {
console.log(err)
})
或是
$.ajax({
method: 'GET',
url: 'https://',
success: (data) => function,
error: (err) => functuon
})
server
client render
也來借用 minw 助教提供的圖解:
jQuery 是 JavaScript 的 library ,將常用的函式包在一起。早期瀏覽器和寫法都未被統一, jQuery 可以跨瀏覽器的特性是很大的優勢,也提供了更為簡潔的語法。
vanilla js 就是原生 JavaScript 。而 jQuery 則是以此為基礎建立的 library ,使用前要先引入檔案才能使用。前端發展至今大專案已經有更好維護的框架了,不過較小的專案使用 jQuery 自由度反而較高。
Bootstrap 是一個由 HTML、CSS 和 JavaScript 寫成的前端框架,核心的設計目標是達成RWD響應式與行動優先,也就是讓你的網站排版可以自動適應螢幕大小。
使用 Bootstrap 前要先引入檔案,其中設定好各種 CSS 樣式和 JavaStript 樣式,想使用時只要更改 HTML 中的 class 即可。
雖然 Bootstrap 提供各種 UI 對不善設計的工程師是一大福音,不過也因此出現了大量相似的網頁 XD
Webpack 是一種 module bundler ,也就是可以將各種資源打包在一起,讓你可以在瀏覽器使用它。
之前使用 node.js 時,如果要將檔案外的模組(例如另一個檔案寫好一個功能後輸出)引入,可以使用 require
。但這個語法和瀏覽器不相容,相對的瀏覽器要使用 <script src='目標檔案'>
引入作為全域變數,但若一次引入不只一個檔案,可能會發生變數名稱衝突,此時就必須使用 jQuery 提供的 .conflic() 排除。
既然沒有支援就自己寫ㄅ,於是就出現各種非官方的模組化規範,直到最近出現 ES6 規範。不過 ES6 的規範支援度還是不好,例如必須開 Server 、 必須在引入時標註 type="module";
而且在想要引入其他人寫的套件時,必須將整份 node_modules
傳上去,或是在 import 輸入完整路徑,十分不好維護。
使用 Webpack 可以將所有檔案包成一個 main.js ,這樣瀏覽器只要引入就好了。更甚者它將模組的概念向外延伸至各種資源,像是 CSS 或圖片,再經過 loader 將資源打包成 .js 檔給瀏覽器使用。
不太熟悉(喂)
以下簡單列一下使用自己的模組的基本流程好ㄌ:
npm init -y // 初始化 npm
npm install webpack webpack-cli --save-dev // 安裝
設定 webpack.config.js
,最基本設定如下:
const path = require('path');
module.exports = {
mode: 'production', // 預設是production(壓縮代碼), development 指定為開發環境(未壓縮代碼)
entry: './src/index.js', // 從哪裡引入模組
output: {
filename: 'main.js', // 輸出之後放到哪裡
path: path.resolve(__dirname, 'dist'),
},
};
package.json
"scripts": {
"build": "webpack", // 之後輸入 npm run build 時就會跑 webpack ㄌ
"test": "echo \"Error: no test specified\" && exit 1"
},
require
和 export
進行模組的輸入和輸出npm run build
跑起來!另外在使用外部模組和 Loader 時,也是遵循安裝 → 更改設定檔和指令的方法。
資料來源:MDN - fetch 、 MDN - Promise
在 JavaScript 上想發非同步的 request 時,除了使用 XMLHttpRequest 以外,也可以使用 fetch 讓程式更簡潔。
最基本的語法是:
fetch('url')
之後如果要加 header 或其他設定可以用物件形式放在第二個參數中。另外,如果要 POST 的話,body
中的 content type 必須符合才行。
值得注意的是,因為 fecth 的回傳值不會 reject HTTP 的 error status ,因此之後還要額外偵測 status 才不會出現錯誤。另外, fetch 雖然可以接收跨站的 cookies ,卻不會主動傳送 cookies 。
fetch 出來的結果會以 Promise 形式出現,必須用 .then(cb)
才能取得其中內容。
Promise
是一個表示非同步運算的最終完成或失敗的物件。因此自己建立一個 Promise 必須輸入兩個函數:成功和失敗的值,而且通常只會有一個被觸發。以下是示意:
function init(resolve, reject) {
resolve(3);
reject(5);
}
const myPromise = new Promise(init)
myPromise.then((data) => {
console.log(data) // 如果成功就跑出 3
}).catch((err) => {
console.log(err) // 有錯誤的話就跑出 5
})
glup 是一套 task manager
,將任務集中管理並設定執行流程。其中 task 的類型可以有很多種,也可以自訂功能。
參考資料:使用 DATA URI 將圖片以 Base64 編碼並內崁至網頁中,加速載入速度
CSS Sprites
是為了避免每次顯示一張圖就要發一次 HTTP request ,乾脆將所有小圖放在一張大圖上一次拿進來。顯示個別小圖的原理是先顯示大圖並設定成小圖的大小、設定成不重複,再位移到指定位置。這麼做的好處是全部只要載入一張圖,降低網路載入時間;但缺點是拼圖再選取的調整比較麻煩、不易維護,也不方便 SEO 讀取。
Data URI
是一種檔案格式,其資料全部都是經過 base64 編碼之後,以文字的方式來儲存的。優點是可以減少 HTTP 請求的數量,直接寫進 HTML 或 CSS 中,不需要透過外部的檔案儲存;不過維護時若要修改就要重新編碼,無法快取且易讀性很差,對 SEO 同樣不友善。
參考資料:wiki - 極簡化
minify
是在不影響功能的情況下,移除所有非功能性必要之原始碼字元,例如換行、空白、變數和函式名稱縮短之類,在不會影響運作的前提下將檔案縮小。
uglify
則是除了壓縮外,還會混淆程式碼,讓程式變得難以被人閱讀,可作為隱藏商業邏輯的手段。
不知道(等等),以下紀錄跑 SASS 的方法,列一下在設定檔 gulpfile.js
要做的事:
const { src, dest } = require('gulp');
const sass = require('gulp-sass');
sass.compiler = require('node-sass');
function defaultTask() {
return src('src/*.scss') // 輸入這個檔案
.pipe(sass().on('error', sass.logError))
.pipe(dest('./css')) // 輸出成這個檔案
}
exports.default = defaultTask;
前端的 CSS 優化主要分成三個方向:
執行方式
top
定位不如用 transform
,因為前者定位是用 layout 會牽扯較多東西。## WEEK14
### P1 你知道虛擬空間、虛擬主機以及實體主機的差別
Dedicated Hosting
實體主機就是我們現在用的電腦的主機,如果用實體主機當伺服器的話,除了要像這週部屬時要裝好作業系統和架網路線外,維護起來也有點麻煩,尤其是一旦關機別人就進不來了 XD
Shared hosting
虛擬主機則是在遠端有台實體主機,然後切出不同的空間作為不同伺服器,但還是共用 CPU 、記憶體和硬碟,想像成宿舍有各自房間和公共空間那樣。雖然比實體主機方便維護,也較便宜,但缺乏獨立性,而且要是你鄰居習慣很差(像是流量大爆),你的伺服器就很容易不穩或當機。
Virtual Private Server
,也就是常聽到的 VPS也是大家共用一台主機,透過虛擬化技術把一台電腦硬體切成很多帳號,讓每個VPS帳號可以安裝自己的作業系統、軟體、自己管理。因為硬體成本還是大家一起均分,所以跟實體主機比起來,VPS的費用頓時也變低了~
Domain 全名是 Domain Name
,算是 IP 位置的代稱,方便人們閱讀。
資料來源:PM筆記:HTTPS、A紀錄與CNAME
A 是 Address
。可以想像成 IP 位置是詳細地址,而域名則是建築物名。買好域名後 A 就是定位這棟建築物在哪裡,之後使用者輸入域名就會自動導向 A 的位置。
CNAME 全名是 Canonical Name
,是關連名稱與地點,其會指到一個網域名稱。用建築物來比喻可能是不同的餐廳(CNAME)都在同的大樓(網域)內,那不管我和司機說想去其中哪家餐廳都會自動被載到該大樓了。
知道……吧(眼神飄忽)
參考資料:了解NoSQL不可不知的5項觀念 、 網站部署
無論是 SQL 或 NoSQL 指的都是拿來查詢資料庫的語言,而非資料庫系統。
常見如 mongodb。
### P2 你知道什麼是 Transaction 與 lock
Transaction 指的是一組一次牽涉到多個 query 的操作,實際應用例如轉帳和購物。例如交易的時後小明給小美一百塊,這意味著小明少了一百,而小美多了一百這兩個操作。
此時只有兩個人還好,但使用者一多像是搶購票券之類的就會出問題,因為多筆資料同時進行更改時可能造成互相影響,比如說只有十張票卻有一百個人搶,發生 race condition ,最後發生超賣情形。
要避免這種狀況,只要在有人交易時先把門鎖上或放個標示說:「裡面有人ㄛ~」就可以避免了,這就是資料庫的 lock 。回到資料庫,為了維持 Transaction 的一致性(consistency)和隔離性(isolation),我們可以在資料被讀取或寫入時掛上一個 lock (像是公眾廁所燈有人使用時就會亮),其他 transaction 可以決定是否要等待或依然讀取。
參考資料: 維基百科 - ACID
Transaction 是指由一系列資料庫操作組成的一個完整的邏輯過程,為了保證 Transaction 的正確性,要符合以下四個特性:
持久性 durability:交易成功之後,寫入的資料不會不見,就算系統故障也不會消失。
### P3 你知道什麼是資料庫的 View 以及使用時機
View 是一張虛擬 table ,只能讀取不能改寫,好處是方便檢視不同 table 的特定欄位,在開放資料庫給他人時也方便隱藏比較機密的欄位,不過缺點是不易維護。
CREATE VIEW view_name [(column_list)] AS
SELECT column_name(s)
FROM table_name
WHERE condition;
SQL 的 function 有分兩種,一種是內建函式例如 SUM()
,另一種則是 Stored procedure ,其設定完後可以直接取代 query 。
DELIMITER //
CREATE PROCEDURE functionName()
BEGIN
SQL query;
END //
DELIMITER ;
DELIMITER
是設定換行符號,先設定成 //
才不會和 Stored procedure 中的 query 撞到。
之後要使用 Stored procedure 就用 CALL functionName()
就可~
資料來源:維基百科 - trigger)
Trigger 的用於記錄資料庫的變動,並用 table 將變動記錄下來。
delimiter //
CREATE TRIGGER 名稱
BEFORE UPDATE FROM table名稱
FOR EACH ROW
BEGIN
要做什麼;
END //
delimiter;
同學們的自我檢測:
]]>給未來求職前的自己作為回頭複習ㄉ筆記
or(如果我寫到一半發懶)
直接想像自己在面試時被問到,我會怎麼回~
如果使用者密碼直接被存入資料庫中,只要資料庫直接被偷看(像是在圖書館使用時上廁所忘了換頁),或用其他攻擊方式直接叫出密碼,使用者的資訊安全就會受到威脅,因此存在資料庫中密碼欄位的密碼最好不是原始的樣子。
雜湊和加密都是把原始的明碼輸入轉換成密碼,都是只要輸入字串和加密方法一樣,就可以得到一樣的密碼。
差別在加密是一對一,因此若知道加密規則和輸出結果,就可以回推出原本的明碼,是一對一的關係;但雜湊 (hash) 是多對一的關係,也就是雖然每次輸入都會得到同樣的輸出,但因為不止這組輸入會得到一樣的輸出,因此就算知道加密規則和結果,也無法回推原本是哪個字串,因此安全性又更高。
PHP 中雜湊的語法如下:
```
password_hash(<string>, <編碼方式>)
//可轉換成 hash ,建議存到預留 60 字元以上的欄位
password_verifty(<明文>, <hash>)
//對照兩者是否相同。
```
SQL Injection 是在輸入的字串中夾帶 SQL 的語法,又因為字串拼接設計不良,夾帶進去的內容被當成 SQL 語法的一部份執行而導致的資安漏洞。以下舉個例子:
程式碼如下:
```
$sql = sprintf("INSERT INTO comments(username, content) VALUES('%s', '%s')", $_POST['username'], $_POST['content']);
```
只要我在 content 的欄位輸入 '), ('not_me', (select password from users));#
,就會變成這樣:
$sql = INSERT INTO comments(username, content) VALUES('me', ''), ('not_me', (select password from users));#')
因為 #
後面會變成附註,因此變成新增兩條留言,第二條的 username 和 content 附註,其中內容甚至可以叫出所有用戶的資料。超危險。
解決方法:使用 SQL 內建機制拼接
$sql =
// 這邊預留空間的方法是用問號 '?'
$stmt = $conn->prepare($sql) ;
// 先準備好
$stmt->bind_param('<拼接什麼>', $<變數1>, $<變數2>) ;
// 如果要拼接兩個字串,就用 'ss' ,整數的話就是 'i'
$result = $stmt->execute()
// 執行 query
// 接著用原本的判斷式檢測是否執行成功
$result = $stmt->get_result()
//這樣才算拿到結果
XSS 全名 Cross-Site Scripting
,也就是跨網站執行 JavaScript。例如顯示留言的地方的 HTML 如下:
<div class='content'><?php echo $row['content']?><div>
這時只要使用者輸入 html 或 JavaScript 語法,例如 <script>alert('hacked')</script>
,就會直接被解讀為程式碼的一部份並被執行。
防範方法:字元跳脫
函式 htmlspecialchars()
可將 html 的特殊符號轉換成純文字。
值得注意的是,因為資料庫儲存原始資料就好,因此建議在輸出處使用。另外,因為不知道攻擊會從哪裡來,因此建議所有輸出都使用字元跳脫。
因為前端的驗證只針對瀏覽器畫面上的驗證,但除此之外還有許多漏洞,例如不透過瀏覽器發 request 給後端即可繞過。通常簡單如「是否有東西漏填」這種驗證會給前端,而和資安相關的例如身分驗證則由後端負責。
參考資料:
讓我們來談談 CSRF
[week 11] 資訊安全 - 雜湊與加密 & 常見攻擊:SQL Injection、XSS
CSRF 的全名是 是 Cross Site Request Forgery
(跨站請求偽造)。也被稱為 one-click attack 或 session riding。簡單來說就是「在不同 domain 下,偽造使用者本人發出的 request」。
平時登入網站後會得到一組 SESSION ,之後就算離開該網站,瀏覽器還是會幫你在 COOKIE 中保留 SESSION 一段時間。此時如果有個釣魚網站騙你在不知情的情況下,對該網站送出 Request (例如放在按鈕或圖片中),此時因為瀏覽器還留著你的 SESSION ,該網站以為是合法請求並接受,這樣一來釣魚網站就可以用你的身分在該網站進行操作。
不同層面的防範辦法
對使用者端來說,最好懂的就是隨時清空 SESSION,不過這就代表每次都要重新登入,很麻煩。
Server 端可以進行雙重驗證,像是輸入驗證碼就很直接,但每個動作都要輸入一次驗證碼有點麻煩,因此最好的方法是多加一層雙重認證。可以用表單中夾帶 CSRF token ,資料確認時也要保證其有被帶上才執行,產生和儲存都由 Server 端負責,但如果攻擊者先發出 request 就可破解;第二種方法是 Double Submit Cookie ,利用「cookie 只會從相同 domain 帶上來」的特性,把資料存在使用端,但只要攻擊者掌握任何 subdomain 就可破解;最後一種是由 Client 產生 token ,同時放到表單和 cookie 中。
瀏覽器方面, Chrome 也有提供 SameSite cookie 幫 cookie 加上一層驗證。
Single Page Application
簡稱 SPA ,單介面應用程式,顧名思義就是所有動作都在一個頁面上完成,不會重新導向 index.html 以外的地方。其原理是 JavaScript 用 Ajax 動態從 Server 端拿資料,再即時 render 到 client 端上。
優點:
缺點
PHP 基礎語法:
// 建立一個空陣列
$json = array();
// 在陣列中增加一個新的物件
$array_push($json, array(
"id" => 13
));
// 轉換成 json 格式
$response = json_encode($json);
// 印出來看看
echo $respons;
// 結果 [{"id": 13}]
之後前端只要讀取 PHP echo 的 JSON 格式結果就好。
串接 API 的方法有很多種,以下示範用 jQuery 實作 Ajax 的方法:
$.ajax({
method: 'GET',
url: 'https://'
}).done(function(data) {
console.log(data)
}).fail(function(err)) {
console.log(err)
})
或是
$.ajax({
method: 'GET',
url: 'https://',
success: (data) => function,
error: (err) => functuon
})
server
client render
也來借用 minw 助教提供的圖解:
jQuery 是 JavaScript 的 library ,將常用的函式包在一起。早期瀏覽器和寫法都未被統一, jQuery 可以跨瀏覽器的特性是很大的優勢,也提供了更為簡潔的語法。
vanilla js 就是原生 JavaScript 。而 jQuery 則是以此為基礎建立的 library ,使用前要先引入檔案才能使用。前端發展至今大專案已經有更好維護的框架了,不過較小的專案使用 jQuery 自由度反而較高。
Bootstrap 是一個由 HTML、CSS 和 JavaScript 寫成的前端框架,核心的設計目標是達成RWD響應式與行動優先,也就是讓你的網站排版可以自動適應螢幕大小。
使用 Bootstrap 前要先引入檔案,其中設定好各種 CSS 樣式和 JavaStript 樣式,想使用時只要更改 HTML 中的 class 即可。
雖然 Bootstrap 提供各種 UI 對不善設計的工程師是一大福音,不過也因此出現了大量相似的網頁 XD
Webpack 是一種 module bundler ,也就是可以將各種資源打包在一起,讓你可以在瀏覽器使用它。
之前使用 node.js 時,如果要將檔案外的模組(例如另一個檔案寫好一個功能後輸出)引入,可以使用 require
。但這個語法和瀏覽器不相容,相對的瀏覽器要使用 <script src='目標檔案'>
引入作為全域變數,但若一次引入不只一個檔案,可能會發生變數名稱衝突,此時就必須使用 jQuery 提供的 .conflic() 排除。
既然沒有支援就自己寫ㄅ,於是就出現各種非官方的模組化規範,直到最近出現 ES6 規範。不過 ES6 的規範支援度還是不好,例如必須開 Server 、 必須在引入時標註 type="module";
而且在想要引入其他人寫的套件時,必須將整份 node_modules
傳上去,或是在 import 輸入完整路徑,十分不好維護。
使用 Webpack 可以將所有檔案包成一個 main.js ,這樣瀏覽器只要引入就好了。更甚者它將模組的概念向外延伸至各種資源,像是 CSS 或圖片,再經過 loader 將資源打包成 .js 檔給瀏覽器使用。
不太熟悉(喂)
以下簡單列一下使用自己的模組的基本流程好ㄌ:
npm init -y // 初始化 npm
npm install webpack webpack-cli --save-dev // 安裝
設定 webpack.config.js
,最基本設定如下:
const path = require('path');
module.exports = {
mode: 'production', // 預設是production(壓縮代碼), development 指定為開發環境(未壓縮代碼)
entry: './src/index.js', // 從哪裡引入模組
output: {
filename: 'main.js', // 輸出之後放到哪裡
path: path.resolve(__dirname, 'dist'),
},
};
package.json
"scripts": {
"build": "webpack", // 之後輸入 npm run build 時就會跑 webpack ㄌ
"test": "echo \"Error: no test specified\" && exit 1"
},
require
和 export
進行模組的輸入和輸出npm run build
跑起來!另外在使用外部模組和 Loader 時,也是遵循安裝 → 更改設定檔和指令的方法。
資料來源:MDN - fetch 、 MDN - Promise
在 JavaScript 上想發非同步的 request 時,除了使用 XMLHttpRequest 以外,也可以使用 fetch 讓程式更簡潔。
最基本的語法是:
fetch('url')
之後如果要加 header 或其他設定可以用物件形式放在第二個參數中。另外,如果要 POST 的話,body
中的 content type 必須符合才行。
值得注意的是,因為 fecth 的回傳值不會 reject HTTP 的 error status ,因此之後還要額外偵測 status 才不會出現錯誤。另外, fetch 雖然可以接收跨站的 cookies ,卻不會主動傳送 cookies 。
fetch 出來的結果會以 Promise 形式出現,必須用 .then(cb)
才能取得其中內容。
Promise
是一個表示非同步運算的最終完成或失敗的物件。因此自己建立一個 Promise 必須輸入兩個函數:成功和失敗的值,而且通常只會有一個被觸發。以下是示意:
function init(resolve, reject) {
resolve(3);
reject(5);
}
const myPromise = new Promise(init)
myPromise.then((data) => {
console.log(data) // 如果成功就跑出 3
}).catch((err) => {
console.log(err) // 有錯誤的話就跑出 5
})
glup 是一套 task manager
,將任務集中管理並設定執行流程。其中 task 的類型可以有很多種,也可以自訂功能。
參考資料:使用 DATA URI 將圖片以 Base64 編碼並內崁至網頁中,加速載入速度
CSS Sprites
是為了避免每次顯示一張圖就要發一次 HTTP request ,乾脆將所有小圖放在一張大圖上一次拿進來。顯示個別小圖的原理是先顯示大圖並設定成小圖的大小、設定成不重複,再位移到指定位置。這麼做的好處是全部只要載入一張圖,降低網路載入時間;但缺點是拼圖再選取的調整比較麻煩、不易維護,也不方便 SEO 讀取。
Data URI
是一種檔案格式,其資料全部都是經過 base64 編碼之後,以文字的方式來儲存的。優點是可以減少 HTTP 請求的數量,直接寫進 HTML 或 CSS 中,不需要透過外部的檔案儲存;不過維護時若要修改就要重新編碼,無法快取且易讀性很差,對 SEO 同樣不友善。
參考資料:wiki - 極簡化
minify
是在不影響功能的情況下,移除所有非功能性必要之原始碼字元,例如換行、空白、變數和函式名稱縮短之類,在不會影響運作的前提下將檔案縮小。
uglify
則是除了壓縮外,還會混淆程式碼,讓程式變得難以被人閱讀,可作為隱藏商業邏輯的手段。
不知道(等等),以下紀錄跑 SASS 的方法,列一下在設定檔 gulpfile.js
要做的事:
const { src, dest } = require('gulp');
const sass = require('gulp-sass');
sass.compiler = require('node-sass');
function defaultTask() {
return src('src/*.scss') // 輸入這個檔案
.pipe(sass().on('error', sass.logError))
.pipe(dest('./css')) // 輸出成這個檔案
}
exports.default = defaultTask;
前端的 CSS 優化主要分成三個方向:
執行方式
top
定位不如用 transform
,因為前者定位是用 layout 會牽扯較多東西。## WEEK14
### P1 你知道虛擬空間、虛擬主機以及實體主機的差別
Dedicated Hosting
實體主機就是我們現在用的電腦的主機,如果用實體主機當伺服器的話,除了要像這週部屬時要裝好作業系統和架網路線外,維護起來也有點麻煩,尤其是一旦關機別人就進不來了 XD
Shared hosting
虛擬主機則是在遠端有台實體主機,然後切出不同的空間作為不同伺服器,但還是共用 CPU 、記憶體和硬碟,想像成宿舍有各自房間和公共空間那樣。雖然比實體主機方便維護,也較便宜,但缺乏獨立性,而且要是你鄰居習慣很差(像是流量大爆),你的伺服器就很容易不穩或當機。
Virtual Private Server
,也就是常聽到的 VPS也是大家共用一台主機,透過虛擬化技術把一台電腦硬體切成很多帳號,讓每個VPS帳號可以安裝自己的作業系統、軟體、自己管理。因為硬體成本還是大家一起均分,所以跟實體主機比起來,VPS的費用頓時也變低了~
Domain 全名是 Domain Name
,算是 IP 位置的代稱,方便人們閱讀。
資料來源:PM筆記:HTTPS、A紀錄與CNAME
A 是 Address
。可以想像成 IP 位置是詳細地址,而域名則是建築物名。買好域名後 A 就是定位這棟建築物在哪裡,之後使用者輸入域名就會自動導向 A 的位置。
CNAME 全名是 Canonical Name
,是關連名稱與地點,其會指到一個網域名稱。用建築物來比喻可能是不同的餐廳(CNAME)都在同的大樓(網域)內,那不管我和司機說想去其中哪家餐廳都會自動被載到該大樓了。
知道……吧(眼神飄忽)
參考資料:了解NoSQL不可不知的5項觀念 、 網站部署
無論是 SQL 或 NoSQL 指的都是拿來查詢資料庫的語言,而非資料庫系統。
常見如 mongodb。
### P2 你知道什麼是 Transaction 與 lock
Transaction 指的是一組一次牽涉到多個 query 的操作,實際應用例如轉帳和購物。例如交易的時後小明給小美一百塊,這意味著小明少了一百,而小美多了一百這兩個操作。
此時只有兩個人還好,但使用者一多像是搶購票券之類的就會出問題,因為多筆資料同時進行更改時可能造成互相影響,比如說只有十張票卻有一百個人搶,發生 race condition ,最後發生超賣情形。
要避免這種狀況,只要在有人交易時先把門鎖上或放個標示說:「裡面有人ㄛ~」就可以避免了,這就是資料庫的 lock 。回到資料庫,為了維持 Transaction 的一致性(consistency)和隔離性(isolation),我們可以在資料被讀取或寫入時掛上一個 lock (像是公眾廁所燈有人使用時就會亮),其他 transaction 可以決定是否要等待或依然讀取。
參考資料: 維基百科 - ACID
Transaction 是指由一系列資料庫操作組成的一個完整的邏輯過程,為了保證 Transaction 的正確性,要符合以下四個特性:
持久性 durability:交易成功之後,寫入的資料不會不見,就算系統故障也不會消失。
### P3 你知道什麼是資料庫的 View 以及使用時機
View 是一張虛擬 table ,只能讀取不能改寫,好處是方便檢視不同 table 的特定欄位,在開放資料庫給他人時也方便隱藏比較機密的欄位,不過缺點是不易維護。
CREATE VIEW view_name [(column_list)] AS
SELECT column_name(s)
FROM table_name
WHERE condition;
SQL 的 function 有分兩種,一種是內建函式例如 SUM()
,另一種則是 Stored procedure ,其設定完後可以直接取代 query 。
DELIMITER //
CREATE PROCEDURE functionName()
BEGIN
SQL query;
END //
DELIMITER ;
DELIMITER
是設定換行符號,先設定成 //
才不會和 Stored procedure 中的 query 撞到。
之後要使用 Stored procedure 就用 CALL functionName()
就可~
資料來源:維基百科 - trigger)
Trigger 的用於記錄資料庫的變動,並用 table 將變動記錄下來。
delimiter //
CREATE TRIGGER 名稱
BEFORE UPDATE FROM table名稱
FOR EACH ROW
BEGIN
要做什麼;
END //
delimiter;
同學們的自我檢測:
]]>『我最近好廢,課程有點跟不下去了。』
「那要不要試試看去飆車?我剛回來。」
『是指物理上的嗎……?』靠北,你不說我還沒發現。
「對、對,物理上的。」
等等,物理上的飆車聽起來還是哪裡不對 XD
這是個平凡的星期二,我一如往常 coding 到十一點被咖啡廳趕走,看似一天又平安的結束了,感謝飛天大生菜的努力——
「真不想就這樣回家洗澡睡覺。」
內心卻忽然出現這樣的聲音。
確實最近學習效率又下降不少,而且明明早就不是青少年了,心情還是時常不明所以的躁動,這樣下去只會進入怠惰期吧。到時候不能跟完課找到工作,就買不到《動物森友會》了,一定要做點什麼掙扎一下。忽然升起謎之求生意志。
青少年的問題,就只能透過青少年的手段解決了——來去飆車ㄅ!
說是要飆車,但大半夜的要飆去哪?
搬來台南一年,基本上和鹽巴聚少離多。
據說因為台南人只吃糖,家家戶戶將不需要的鹽搬到七股,就成了我們今天看到的七股鹽山。如今七股可說是整個台南唯一剩下鹽的地方了,瀕臨絕種,政府為此立了〈七股野生鹽巴保護法〉,擅自濫捕或偷吃都會挨罰,還請各位小心觸法。
從台南市區到七股, Google 地圖說要 22 公里,單趟半個多小時。
半夜十二點,最適合一時興起拜訪老朋友了。
與此同時,我的騎車技術其實很差,正盤算著要是半路出了什麼意外,大家可能不好找。剛好此時收到 Slack 兩位同學的訊息,我打斷本來在討論的 coding 話題,懷抱留下遺言的心情送出:
「我現在要騎去七股。」
意思是,要是三天沒看到我,請去路上找我收屍。可以的話,順便告訴爸媽我愛他們。
交代好後事,我心無罣礙的踏上旅程。
等紅燈時收到 Wendy 同學回訊息問我是不是要去看星星,馬上向在台南生活的前輩討教,才知道原來七股有 鹽巴以外的東西 ,臨時決定改變目的地。
但果然,事情不會那麼簡單。騎著騎著身邊的車子越來越少,連路燈都開始怠惰,最後眼前出現這副景象:
再點開 Wendy 同學給的資料,發現文章中有一段:
拍星星的好地方,必定極少光害,
加上這帶道路狹小、野狗又多,從來沒來過的人,半夜獨自前往真的很危險,據說白天也很容易迷路呢
藉由小小部落格呼籲一下,星空雖然浪漫,但生命更可貴唷,請結伴同行或找老司機帶路。
開什麼玩笑,從幼稚園開始大家就叫我「愛惜生命生菜」。不只如此,我每年都當愛惜生命股長,並且好幾年當選新竹縣市愛惜生命大使,畢業都領愛惜生命獎,高中還代表台灣參加奧林匹亞愛惜生命大賽。
維持原本計劃想去鹽山,沒想到地圖叫我沿著同樣沒有路燈的路橋下走,途中還看到許多「小心野狗」的告示牌。
我習慣騎車聽音樂(危險駕駛)。這趟給自己另一個挑戰是完全不切歌,一來是沒有紅燈可以換歌,二來也可以順便熟悉一下平時 Spotify 歌單亂按喜歡的幾百首歌。
此時輪到《逃出絕命鎮》的 OST 。雖然在台灣不會忽然有野鹿衝出來,但此時撞死野狗是很有可能的,而且如果後方忽然出現酒駕的大卡車,我就成了被撞死無人聞問的那方了。剛好這時又因為怕遇到野狗不敢停下來換歌,如果可以的話,真想打爆過去某時按這首歌喜歡的我。
我還沒完成 13 週的作業,還不想死。
繃緊神經騎了漫長的幾分鐘,沒想到下一首歌是:
瞬間,這趟路完~全~不恐怖了。
想到電影《Lady Bird》的一個細節是,在女主角犯了一堆錯經歷傷害與被傷害後,進入大學反而將他從超爛前男友學到的知識和幹話化為自身社交的優勢。大概就是很溫柔的告訴我們:「成長中經歷的一切,都不會白費。」
對,不白費我童年每週在電視機前的時光了 (?)
終於回到大馬路,看到(因為涉及個資就先碼掉名字好了)另一位同學回覆:
「我家在五股,可以順便(?)」
這趟鹽巴之旅最終因為路燈與野狗始宣告失敗——請問七股的本體是狗嗎?
歷劫歸來回到市區,這段始亂終棄的旅程終究不是常態,但自此也許更可以於日常中再往前多撐一段吧。
最後用(並不算)運動後的茶葉蛋覆蓋這一回合。
]]>『我最近好廢,課程有點跟不下去了。』
「那要不要試試看去飆車?我剛回來。」
『是指物理上的嗎……?』靠北,你不說我還沒發現。
「對、對,物理上的。」
等等,物理上的飆車聽起來還是哪裡不對 XD
這是個平凡的星期二,我一如往常 coding 到十一點被咖啡廳趕走,看似一天又平安的結束了,感謝飛天大生菜的努力——
「真不想就這樣回家洗澡睡覺。」
內心卻忽然出現這樣的聲音。
確實最近學習效率又下降不少,而且明明早就不是青少年了,心情還是時常不明所以的躁動,這樣下去只會進入怠惰期吧。到時候不能跟完課找到工作,就買不到《動物森友會》了,一定要做點什麼掙扎一下。忽然升起謎之求生意志。
青少年的問題,就只能透過青少年的手段解決了——來去飆車ㄅ!
說是要飆車,但大半夜的要飆去哪?
搬來台南一年,基本上和鹽巴聚少離多。
據說因為台南人只吃糖,家家戶戶將不需要的鹽搬到七股,就成了我們今天看到的七股鹽山。如今七股可說是整個台南唯一剩下鹽的地方了,瀕臨絕種,政府為此立了〈七股野生鹽巴保護法〉,擅自濫捕或偷吃都會挨罰,還請各位小心觸法。
從台南市區到七股, Google 地圖說要 22 公里,單趟半個多小時。
半夜十二點,最適合一時興起拜訪老朋友了。
與此同時,我的騎車技術其實很差,正盤算著要是半路出了什麼意外,大家可能不好找。剛好此時收到 Slack 兩位同學的訊息,我打斷本來在討論的 coding 話題,懷抱留下遺言的心情送出:
「我現在要騎去七股。」
意思是,要是三天沒看到我,請去路上找我收屍。可以的話,順便告訴爸媽我愛他們。
交代好後事,我心無罣礙的踏上旅程。
等紅燈時收到 Wendy 同學回訊息問我是不是要去看星星,馬上向在台南生活的前輩討教,才知道原來七股有 鹽巴以外的東西 ,臨時決定改變目的地。
但果然,事情不會那麼簡單。騎著騎著身邊的車子越來越少,連路燈都開始怠惰,最後眼前出現這副景象:
再點開 Wendy 同學給的資料,發現文章中有一段:
拍星星的好地方,必定極少光害,
加上這帶道路狹小、野狗又多,從來沒來過的人,半夜獨自前往真的很危險,據說白天也很容易迷路呢
藉由小小部落格呼籲一下,星空雖然浪漫,但生命更可貴唷,請結伴同行或找老司機帶路。
開什麼玩笑,從幼稚園開始大家就叫我「愛惜生命生菜」。不只如此,我每年都當愛惜生命股長,並且好幾年當選新竹縣市愛惜生命大使,畢業都領愛惜生命獎,高中還代表台灣參加奧林匹亞愛惜生命大賽。
維持原本計劃想去鹽山,沒想到地圖叫我沿著同樣沒有路燈的路橋下走,途中還看到許多「小心野狗」的告示牌。
我習慣騎車聽音樂(危險駕駛)。這趟給自己另一個挑戰是完全不切歌,一來是沒有紅燈可以換歌,二來也可以順便熟悉一下平時 Spotify 歌單亂按喜歡的幾百首歌。
此時輪到《逃出絕命鎮》的 OST 。雖然在台灣不會忽然有野鹿衝出來,但此時撞死野狗是很有可能的,而且如果後方忽然出現酒駕的大卡車,我就成了被撞死無人聞問的那方了。剛好這時又因為怕遇到野狗不敢停下來換歌,如果可以的話,真想打爆過去某時按這首歌喜歡的我。
我還沒完成 13 週的作業,還不想死。
繃緊神經騎了漫長的幾分鐘,沒想到下一首歌是:
瞬間,這趟路完~全~不恐怖了。
想到電影《Lady Bird》的一個細節是,在女主角犯了一堆錯經歷傷害與被傷害後,進入大學反而將他從超爛前男友學到的知識和幹話化為自身社交的優勢。大概就是很溫柔的告訴我們:「成長中經歷的一切,都不會白費。」
對,不白費我童年每週在電視機前的時光了 (?)
終於回到大馬路,看到(因為涉及個資就先碼掉名字好了)另一位同學回覆:
「我家在五股,可以順便(?)」
這趟鹽巴之旅最終因為路燈與野狗始宣告失敗——請問七股的本體是狗嗎?
歷劫歸來回到市區,這段始亂終棄的旅程終究不是常態,但自此也許更可以於日常中再往前多撐一段吧。
最後用(並不算)運動後的茶葉蛋覆蓋這一回合。
]]>之前的心得可以看 這篇 ,上次結束後到現在差不多就是一個月,儘管如此我還是落後(謝囉,三四週的我自己),因此這次也是簡單附上筆記和自我檢討簡答,並集中隨便紀錄這一個月的學習心得。
[Week6] 給自己看的 HTML 基礎
這篇其實根本也沒整理完,就意識到「這不是可以悠閒整理筆記的時刻了」,於是果斷放棄這個月所有的筆記。
[week9] 給自己看的 PHP 和 MySQL 溝通
上述策略除了因為第一次學 PHP 和 MySQL ,又有聽說這個禮拜的作業很難,乾脆選感覺最基本的實作記錄下來。個人認為這份筆記的 CP 值非常高,之後課程教到類似功能前,都會先暫停影片看著這份筆記刻出來,熟練到最後真正實作作業時連筆記都不用打開了。
r3:0 異世界網站挑戰 Lv. 11 ~ 15 攻略
純粹只是還沒有人做過,所以想做一做順便證明我有破關(
P1 你知道 HTML 是在做什麼的
HTML 全名是 Hyper Text Markup Language 超文本標記語言,它並不是一個程式語言。主要用於規劃網頁架構,通過標籤定義網頁的原始內容。
P1 你知道如何使用有語意的(semantic)標籤
語意化標籤的目的是為了讓標籤(Tag)更具意義,以加強文件的結構化,讓搜尋引擎更清楚了解。例如早期分段都只用 <div>
,現在卻多了 <header>
、 <main>
和 <footer>
之類的,雖然效果顯示上一樣,不過更方便回頭 debug 或溝通。
P1 你知道基本 SEO 的概念
我忘記了,面試前回頭看。
P1 你知道 CSS 是什麼
資料來源:MDN
跟 HTML 一樣,CSS 既非標準程式語言,也不是標記語言, 而是一種風格頁面語言(style sheet language):它能讓你在 HTML 文件中的元素(element)上套用不同的頁面樣式(style)。
P1 你知道 inline、block 跟 inline-block 的區別
參考資料: 前端基礎:CSS盒模型(box model)
block
表示什麼屬性都可以調,是 <div>
、 <h1>
、 <p>
等標籤的預設屬性,同時也表示元素本身會占滿整行。
inline
就可和其他元素並排,是 <span>
和 <a>
的預設屬性,但無法調整寬高和上下邊距,padding 的上下可以調整,但不會影響到其他元素。
但如果想要並排又想調整上下,就可以使用 inline-block
,它也是 <botton>
、 <input>
和 <select>
的預設屬性。值得注意的是,使用 inline-block 時元素和元素中的空白也會被顯示,若想刪掉只要刪掉空白或在空白處使用附註連接就好。
P1 你知道什麼是 box model
參考資料: 前端基礎:CSS盒模型(box model)
盒模型是指在 CSS 中,將每個元素視為一個盒子(BOX),再針對該盒子做調整。由內至外依序是:content → padding → border → margin。
值得注意的是,因為預設上除了 content 以外的都是往外長,因此若調整外面三個會影響整體大小,解決方法之一是用減法,例如就把 width 設成 56 ,但這樣一來要多算一次很麻煩,而且要是 PM 忽然想把 border 設成 13 怎麼辦。因此會採取另一個屬性: box-sizing:border
,設定 width 和 height 時就會將 padding 和 border 一起算進來了~
P1 你知道 position 的所有屬性及其差別
大概知道。可以的話之後來整理 FLEXBOX FROGGY 和補充影片的筆記。
P2 你知道 :hover, :before, :after
:hover
是滑鼠點上去之後的效果。
:before
是放在元素之前的東西和效果。
:after
是放再元素之後的東西和笑果。
設置 before 和 after 可減少重複性設定同樣內容。
P2 你知道 :nth-child 的各種用法
同上兩題。
P2 你熟悉 CSS selector,可以輕鬆選到想選到的元素
同上嗚嗚。
P1 你知道 JavaScript 跑在網頁上跟跑在 Node.js 上差在哪裡
資料來源:看起來是同學的筆記
因為執行環境不同,可以執行的 function 會不同。 JavaScripts 本身有規範一些可以用的東西,例如 var 宣告變數或 else if 進行判斷。但也有一些不是本身規範的,就看各個執行環境各自支援什麼了,比如說兩者雖然都可以跑 console.log
但其實是各自提供的這樣。
P1 你知道 DOM 是什麼
資料來源:Day03-深入理解網頁架構:DOM
DOM 的全名是 Document Oject Model
文件物件模型,就是將 HTML 內各種標籤定義成物件,方便和 JavaScript 溝通。被定義的物件們會成為樹狀結構例如下圖:
透過 DOM ,我們可以使用 JavaScript 改變網頁介面、監聽事件並做出反應,以及和伺服器交換資料。
P1 你知道如何用 JavaScript 操控 DOM 物件
進度岌岌可危,列最常用的就好:
element = document.querySelector(<css selector>)
P1 你知道如何幫一個按鈕加上 event listener
<元素>.addEventListener('事件', callbackfunction)
P1 你知道捕獲與冒泡是什麼
事件傳遞依照順序會經過三個階段:
P1 你知道什麼是事件代理(delegation)
想像要製作一個 todolist ,如果一條一條監聽,程式碼就會變得很複雜,而且也無法替新增的 item 增加監聽。
與此同時,當我們點到 target 時,也會觸發到上層的元素。因此我們可以利用事件傳遞機制,進行事件代理(event delegation),解決以上問題。
例如在 todolist 中,我們只要將監聽器掛在 item 們的上一層,當監聽器偵測到再往下看是誰被點擊並做出反應就好,這樣新增的 item 也可以被監聽到。
P2 你知道怎麼用 JavaScript 更改元素的 style
<選到元素>.style.<屬性>=<預改變結果>
要注意屬性不能使用特殊符號如 -
,因此可以用:
<選到元素>.style[margin-top]=10px;
P2 你知道 preventDefault 與 stopPropagation 的差異
event.preventDefault()
是阻止預設行為發生。預設行為例如 subnit 被按下去預設就會送出表單,或是 a 按下去就會觸發超連結。
event.stopPropagation()
則是阻止事件傳遞,和傳遞順序有關,如果在哪個階段加上 .stopPropagation()
就不會再傳遞給下個節點,但該節點的事件還是會被觸發,如果全都要阻止可用 e.stopImmediatePropagation()
。
參考資料:同源政策
P1 你知道什麼是 API
API 全名為 Application Programming Interface
,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。
舉例來說好了,今天你進到一家拉麵店,要如何和廚房說你要什麼品項?因為你已經來過很多次了,所以走到拉麵販賣機前點餐,而 這個販賣機就是 API 。你和廚房可以透過販賣機溝通(使用 API),你開店的時候別人也可以從販賣機點餐(提供 API)。
P1 你知道什麼是 Ajax
Ajax 的全名是 Asynchronous JavaScript and XML
,任何和伺服器非同步交換資料都可以叫做 Ajax 。(早期是用 XML 交換,但現在比較常用 JSON)
一般來說程式碼由上往下按照順序跑,如果中間某行跑太久,後面就必須要等。用去拉麵店點餐舉例,同步就是點完餐(送完 request)後,必須站在原地排隊等餐點(response),非同步則是點完後可以拿張號碼牌回座位先做其他事情,等餐點到了再去拿就可以了。
在 JavaScript 中,可以使用 回呼函式(callback function)
來達成。回呼函式是指能藉由參數通往另一個函式的函式,因此只要設定拿到 response 後再呼叫這個函式,就不用傻傻待在原地等。
P1 你知道從網頁前端呼叫 API 與在自己電腦上寫程式呼叫的差異
從網頁前端發送 request ,因為多了一層安全性考量,因此會受到同源政策(確認雙方都願意交換資料)的規範。但第四周的 node.js 直接送到 server ,不會經過瀏覽器,也就沒有問題。
P1 你知道什麼是同源政策(Same-origin policy)
同源政策(Same-origin policy)規範了哪寫資源可以跨源存取,哪些會受到限制。其中同源的意思是相同網域、通訊協定和連接埠號,基本上兩者若非同源,則要額外設定 CORS 。
P1 你知道如何存取跨網域的資源(CORS)
想要開啟跨來源請求,必須在 Response Header 設定 Access-Control-Allow-Origin
,規範哪些網域可存取。在 request 這邊又分為簡單請求和預檢請求。
P1 你知道什麼是 JSON
JSON 全名 JavaScript Object Notation ,以純文字的方法儲存資料,樣子和 JS 中物件很像,只是不管是 key 還是 value 都要用雙引號 "
框起來,而整體是個字串。
P2 你知道什麼是 JSONP 及其原理
JSONP 是 JSON with Padding
。
因為考量安全性,透過瀏覽器取得 API 必須遵守同源政策(Same-origin policy,CORS),也就是說除非特別設定,否則不同網域之間無法分享 API (會發 request ,但 response 會被瀏覽器擋下來不給 JavaScript)。
不過有些標籤不被此規範,例如 <img>
或 <scipt>
。JSONP 就是將資料包進 function 中並放在 <scipt>
下,其他人就可以直接使用。
P1 你知道 PHP 是什麼
PHP(Hypertext Preprocessor)是一種可以結合資料庫的動態網頁開發語言。動態網頁和靜態網頁的差別,在於靜態網頁是直接將檔案丟給瀏覽器,但動態網頁則是先由伺服器跑完結果後,再將結果傳給瀏覽器。
P1 你知道前端與後端的差別
使用者可以看的到的部分都是前端,像是新增網頁和設計;後端則是負責和資料庫溝通。
用 Google 表單類比的話,我們在填寫表單時回答的問題是前端負責;如果設計者針對大家的回應開一個試算表,那麼和這個試算表溝通就是後端的工作。
當然大部分的功能需要前後端合力才能達成,例如用搜尋引擎時,前端負責接收我們輸入的關鍵字,交給後端去資料庫中尋找,並回傳結果給前端,最後由前端顯示結果。
P1 你知道什麼是資料庫
一般來說網頁的資料會被存在一個檔案中,但當網頁一大就難管理,而資料庫系統就是專門處理資料的程式,提供專門的語法操作資料庫。
P1 你了解基本的 SQL 語法,包括 Select、Insert Into、Delete 與 Update / 你能夠寫出基本的 CRUD 應用
左轉筆記 給自己看的 PHP 和 MySQL 溝通
P1 你知道什麼是 Session / 你知道什麼是 Cookie / 你知道 Session 與 Cookie 的差別
瀏覽器每次發出 Request 的時候都是無狀態的,也就是說除非特別標記,否則伺服器不會記得對方上次的紀錄(例如登入資訊)。而 Session
就是為了處理這個問題出現的,其中 Cookie 就是一種 Session 的方法。
Cookie
的資料保存方式有點像集點卡,資料由使用者(瀏覽器)端保存,每次消費(發 Request)帶給店家(伺服器)就可使用點數。
實際上 Cookie 是個文字檔,以下流程用登陸資訊為例,第一次登入成功後伺服器就會透過 Response Header 給瀏覽器一份 Cookie 。之後存在 Cookie 裡的登陸資料由瀏覽器保管,下次對同個伺服器發出 Request 時自動帶上 Request Header 中,只要還沒過期而且符合設定的 Domain ,伺服器就知道是上次那個使用者了。
相對的,Session
的資料存在伺服器端,再給瀏覽器一組 token 存在 Cookie 中。就像是電子會員卡,每次會員只要去消費,出示卡片或 ID ,店家就可以從資料庫中得到資料。這樣做的好處是使用者不容易竄改資料,比較安全。
故事開始於在 NGO 工作的朋友的一則邀約:
「我們下週和這裡有合作的活動。你最近也在學程式,要不要來看看?」
隨訊息附上好想工作室的活動連結。
八月底在台北的實體聚會自由交流時間,我一開始超尷尬不知道要找誰聊天,緊張地傳訊息給朋友——「找長得最好看的啊 XD」靠北喔,都什麼時候還在跟我講幹話。好險後來跑幾趟根本沒上的廁所後,開始可以正常和人類說話了 XD 最後離開前得知原來除了志揚助教以外,課程內的其他學生也有被其他學程式的管道拒絕過的經驗。
細數某個年紀前的我嚮往的角色,幾乎都是主角,連最喜歡的顏色也一定要是紅色。年幼的我,太早就知道要崇拜雖然還是有煩惱,但問題還是都會迎刃而解,而且被大家所愛著的人們。
隨便舉個例,主角就絕對不會被好想工作室拒絕。
逐漸體認到自己不可能成為主角,或其實只是放棄成為主角後,我開始著迷於任何不平滑的東西。關注並蒐集這些瑕疵,會讓我感到溫暖。一方面是知道自己不孤單,另一方面我平時習慣懷疑自己的自我懷疑(既然會懷疑,應該就表示不夠喜歡,或是自己不夠堅強了吧)。所以得知別人失敗掙扎卻持續努力的故事,對我來說可能是暗黑版雞湯吧——原來沒自信也是正常的,不是只有特定樣子才能成為自己想要的模樣——並由此產生更多動力。
因此我非常非常感謝不只是這次實體聚會,而是曾經讓我看到些微破綻卻又閃閃發光的人們。
得到了那麼多,我又該以什麼回饋呢?想來想去可能只有把類似的故事說出來了。以下,我會盡力不帶給各位情緒負擔的講述事情經過:
我決定要學習程式設計後,曾經報名過好想工作室的 Backend Camp ,面試完後得到回家作業要架設環境。但我前前後後做了一個禮拜才達成,因此被委婉拒絕了,對方希望我能調整好心態。
之所以之後完全閉口不談,最主要還是因為不知道現在的我,到底「調整好心態」了沒。(不是,先讓我講其他東西,最後會把這部分收回來請先不要關掉 orz)
從上次寫完複習心得並宣稱要追進度到現在,其實也差不多才過了一個月,得知這個訊息的我大驚,現在這是進入精神時光屋了嗎 XD (是說原來精神時光屋是外面的時間才過幾秒,外面感覺像是好幾千年,我之前一直以為是反過來 www)
原來之前 Podcast 說的:「想說要努力找個平衡,結果調整調整,回過神來課程就結束了。」是真的!因為一直有新的學習刺激,反而沒辦法停下來休息,尤其我又落後兩週多。(順帶一提,雖然非常怨懟之前三四週因為腰痛就擺爛的我,但說不定這個有點落後的距離反而是適合我的 XD)
我記得 Podcast 的結論是,面對不斷變動的外在,必須保持動態平衡。果然,擁有彈性是非常重要的。
連對自己心態的評價也必須保持彈性。
像是,我真的喜歡程式設計嗎?
面試好想工作室的時候,對方非常非常在意這個點,他們說不希望浪費彼此的時間才發現我不喜歡程式設計。我也蠻同意的,畢竟誰都希望和有熱情的人共事。
但我其實不知道自己到底喜不喜歡,我無法做出連我自己都無法確定的承諾。
退幾步看,到底怎麼樣算是喜歡一件事情?問了很多人之後歸納出幾點操作型定義:純粹做的時候會很開心、就算沒有其他好處也會去做、會猶豫這件事就代表不喜歡。彷彿「喜歡」是純粹不帶任何目的,而且自然而然,只差沒有不證自明了。
——那我應該沒有喜歡過任何東西吧。
長期要求自己應該要對萬事萬物保有開放性和好奇心,得出這個結論後我消沉了好一陣子,無法釋懷自己其實是個沒熱情的人。就連現在看到討論區很認真討論學到的技術的老師同學和助教,只要稍微鬆懈沒有他們那麼投入,就會馬上把自己打入「你看看你,也沒有那麼喜歡這個啊」的範圍。
一直到很後來才知道,這種事情不是非黑即白。
體會到這件事,是因為最近把《排球少年》的動畫撿回來。第一個瞬間是清水學姊說,她一開始也不是非常喜歡排球,是邊做球經邊喜歡上的;另外是超沒幹勁的月島問木兔「排球有趣嗎?」被反嗆:「那只是你不夠強而已。等你能夠做到(下略境界),就是你喜歡排球的起點。」
原來這樣子也可以稱作喜歡嗎?原來除了「熱情就寫在基因裡面,因此就算沒有成就感也可以堅持下去」以外,還有很多很多種喜歡的樣子嗎!
懶的轉場了(喂),來說點實際的體悟,反正我目前體會到三種策略:
國小的時候非常熱衷於奇摩部落格和無名小站的 CSS 裝飾(天啊其實是黑歷史,好險他們現在都關了……),因此第六週學的時候一直有種「同學你有點眼熟」的既視感 XD
相較前幾週的 JS ,這週的入門門檻確實大大降低了,但切版真的沒有盡頭,好像永遠都有更好的方法,這時候就要勇敢大喊「我就爛!」然後趕快進入下一個工作。另一個比較困擾的是命名,因為兩邊的命名會互相影響,中途要改名就會把自己弄得很混亂,另外如果和檢討影片的命名不同對照起來也會有點抖,也常常不知道這個區塊該叫甚麼名字,好想成為命名大師喔可惡。最後為了錯亂,我會先在紙上畫好每個區塊叫什麼,並且規定自己一口氣切好全部的版,第六週和第九週作業都試了這個方法,覺得腦袋比較清晰,而且一次做完感覺很爽。
進入 PHP 後,因為沒有用任何框架,大家都在喊變得超複雜。但我覺得這種完全不依靠其他人(雖然程式設計照理來說依靠了其他很多人,是個漂流到荒島上就毫無用處的專長,但先忽略這部分啦 XD)獨自架起留言板的感覺真的很爽(詞彙貧乏)
看文章 白話 Session 與 Cookie:從經營雜貨店開始 的雜貨店舉例時腦中警鈴大響:事情哪有那麼簡單!?
年初在補習班做行政櫃台的兼職,最苦手的工作之一就是下課時看到家長來接,要廣播提醒學生下樓。因為我也是超級大臉盲和金魚腦,就做了各種 excel 表格和筆記幫助我對照學生的名字、年級、今天補什麼和教室分機號碼,但最後卻被說不如多詢問幾次記在腦袋裡,要能做到不靠任何東西記起來。
雖然現在回想起來陰影面積還是有點大,但反正每個人都有擅長和不擅長的事。這份工作給我的幾個體悟:比起一口氣和四百個學生都有點認識,我比較喜歡且擅長只和少少幾個人深交;再來就是我就是不擅長只用腦袋記名字和認臉,現在才會需要在這裡學程式設計吧。現在想想當初用表格整理學生資料方便查詢,是否也有點像用程式解決問題。
不只是為了私人困擾,在學習的時候知道自己「幹嘛學這個」對我來說也是蠻重要的。比如說 API 對我來說就一直是有點浮在空中的東西,一直到上次去小樹屋才聽到助教說 API 之後會用在前後端溝通,才覺得比較踏實一點。
第九週的留言板架起來後很快樂的貼給朋友,朋友問:「你是買榜嗎?怎麼下面那麼多人說生菜好可愛?」
我:才沒有,因為這是我做的,我就把預設內容改成「生菜好可愛!」了。不然你以為我幹嘛學寫程式 XD
朋友:????好喔。
現在的我真的調整好了嗎?
我還是不知道,反正現在的我就是這個樣子,你們都看到了。我現在學得蠻快樂的,還想繼續再走一段路。
我打下「可惜我那天有事,改天來去看看~」,送出。
]]>之前的心得可以看 這篇 ,上次結束後到現在差不多就是一個月,儘管如此我還是落後(謝囉,三四週的我自己),因此這次也是簡單附上筆記和自我檢討簡答,並集中隨便紀錄這一個月的學習心得。
[Week6] 給自己看的 HTML 基礎
這篇其實根本也沒整理完,就意識到「這不是可以悠閒整理筆記的時刻了」,於是果斷放棄這個月所有的筆記。
[week9] 給自己看的 PHP 和 MySQL 溝通
上述策略除了因為第一次學 PHP 和 MySQL ,又有聽說這個禮拜的作業很難,乾脆選感覺最基本的實作記錄下來。個人認為這份筆記的 CP 值非常高,之後課程教到類似功能前,都會先暫停影片看著這份筆記刻出來,熟練到最後真正實作作業時連筆記都不用打開了。
r3:0 異世界網站挑戰 Lv. 11 ~ 15 攻略
純粹只是還沒有人做過,所以想做一做順便證明我有破關(
P1 你知道 HTML 是在做什麼的
HTML 全名是 Hyper Text Markup Language 超文本標記語言,它並不是一個程式語言。主要用於規劃網頁架構,通過標籤定義網頁的原始內容。
P1 你知道如何使用有語意的(semantic)標籤
語意化標籤的目的是為了讓標籤(Tag)更具意義,以加強文件的結構化,讓搜尋引擎更清楚了解。例如早期分段都只用 <div>
,現在卻多了 <header>
、 <main>
和 <footer>
之類的,雖然效果顯示上一樣,不過更方便回頭 debug 或溝通。
P1 你知道基本 SEO 的概念
我忘記了,面試前回頭看。
P1 你知道 CSS 是什麼
資料來源:MDN
跟 HTML 一樣,CSS 既非標準程式語言,也不是標記語言, 而是一種風格頁面語言(style sheet language):它能讓你在 HTML 文件中的元素(element)上套用不同的頁面樣式(style)。
P1 你知道 inline、block 跟 inline-block 的區別
參考資料: 前端基礎:CSS盒模型(box model)
block
表示什麼屬性都可以調,是 <div>
、 <h1>
、 <p>
等標籤的預設屬性,同時也表示元素本身會占滿整行。
inline
就可和其他元素並排,是 <span>
和 <a>
的預設屬性,但無法調整寬高和上下邊距,padding 的上下可以調整,但不會影響到其他元素。
但如果想要並排又想調整上下,就可以使用 inline-block
,它也是 <botton>
、 <input>
和 <select>
的預設屬性。值得注意的是,使用 inline-block 時元素和元素中的空白也會被顯示,若想刪掉只要刪掉空白或在空白處使用附註連接就好。
P1 你知道什麼是 box model
參考資料: 前端基礎:CSS盒模型(box model)
盒模型是指在 CSS 中,將每個元素視為一個盒子(BOX),再針對該盒子做調整。由內至外依序是:content → padding → border → margin。
值得注意的是,因為預設上除了 content 以外的都是往外長,因此若調整外面三個會影響整體大小,解決方法之一是用減法,例如就把 width 設成 56 ,但這樣一來要多算一次很麻煩,而且要是 PM 忽然想把 border 設成 13 怎麼辦。因此會採取另一個屬性: box-sizing:border
,設定 width 和 height 時就會將 padding 和 border 一起算進來了~
P1 你知道 position 的所有屬性及其差別
大概知道。可以的話之後來整理 FLEXBOX FROGGY 和補充影片的筆記。
P2 你知道 :hover, :before, :after
:hover
是滑鼠點上去之後的效果。
:before
是放在元素之前的東西和效果。
:after
是放再元素之後的東西和笑果。
設置 before 和 after 可減少重複性設定同樣內容。
P2 你知道 :nth-child 的各種用法
同上兩題。
P2 你熟悉 CSS selector,可以輕鬆選到想選到的元素
同上嗚嗚。
P1 你知道 JavaScript 跑在網頁上跟跑在 Node.js 上差在哪裡
資料來源:看起來是同學的筆記
因為執行環境不同,可以執行的 function 會不同。 JavaScripts 本身有規範一些可以用的東西,例如 var 宣告變數或 else if 進行判斷。但也有一些不是本身規範的,就看各個執行環境各自支援什麼了,比如說兩者雖然都可以跑 console.log
但其實是各自提供的這樣。
P1 你知道 DOM 是什麼
資料來源:Day03-深入理解網頁架構:DOM
DOM 的全名是 Document Oject Model
文件物件模型,就是將 HTML 內各種標籤定義成物件,方便和 JavaScript 溝通。被定義的物件們會成為樹狀結構例如下圖:
透過 DOM ,我們可以使用 JavaScript 改變網頁介面、監聽事件並做出反應,以及和伺服器交換資料。
P1 你知道如何用 JavaScript 操控 DOM 物件
進度岌岌可危,列最常用的就好:
element = document.querySelector(<css selector>)
P1 你知道如何幫一個按鈕加上 event listener
<元素>.addEventListener('事件', callbackfunction)
P1 你知道捕獲與冒泡是什麼
事件傳遞依照順序會經過三個階段:
P1 你知道什麼是事件代理(delegation)
想像要製作一個 todolist ,如果一條一條監聽,程式碼就會變得很複雜,而且也無法替新增的 item 增加監聽。
與此同時,當我們點到 target 時,也會觸發到上層的元素。因此我們可以利用事件傳遞機制,進行事件代理(event delegation),解決以上問題。
例如在 todolist 中,我們只要將監聽器掛在 item 們的上一層,當監聽器偵測到再往下看是誰被點擊並做出反應就好,這樣新增的 item 也可以被監聽到。
P2 你知道怎麼用 JavaScript 更改元素的 style
<選到元素>.style.<屬性>=<預改變結果>
要注意屬性不能使用特殊符號如 -
,因此可以用:
<選到元素>.style[margin-top]=10px;
P2 你知道 preventDefault 與 stopPropagation 的差異
event.preventDefault()
是阻止預設行為發生。預設行為例如 subnit 被按下去預設就會送出表單,或是 a 按下去就會觸發超連結。
event.stopPropagation()
則是阻止事件傳遞,和傳遞順序有關,如果在哪個階段加上 .stopPropagation()
就不會再傳遞給下個節點,但該節點的事件還是會被觸發,如果全都要阻止可用 e.stopImmediatePropagation()
。
參考資料:同源政策
P1 你知道什麼是 API
API 全名為 Application Programming Interface
,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。
舉例來說好了,今天你進到一家拉麵店,要如何和廚房說你要什麼品項?因為你已經來過很多次了,所以走到拉麵販賣機前點餐,而 這個販賣機就是 API 。你和廚房可以透過販賣機溝通(使用 API),你開店的時候別人也可以從販賣機點餐(提供 API)。
P1 你知道什麼是 Ajax
Ajax 的全名是 Asynchronous JavaScript and XML
,任何和伺服器非同步交換資料都可以叫做 Ajax 。(早期是用 XML 交換,但現在比較常用 JSON)
一般來說程式碼由上往下按照順序跑,如果中間某行跑太久,後面就必須要等。用去拉麵店點餐舉例,同步就是點完餐(送完 request)後,必須站在原地排隊等餐點(response),非同步則是點完後可以拿張號碼牌回座位先做其他事情,等餐點到了再去拿就可以了。
在 JavaScript 中,可以使用 回呼函式(callback function)
來達成。回呼函式是指能藉由參數通往另一個函式的函式,因此只要設定拿到 response 後再呼叫這個函式,就不用傻傻待在原地等。
P1 你知道從網頁前端呼叫 API 與在自己電腦上寫程式呼叫的差異
從網頁前端發送 request ,因為多了一層安全性考量,因此會受到同源政策(確認雙方都願意交換資料)的規範。但第四周的 node.js 直接送到 server ,不會經過瀏覽器,也就沒有問題。
P1 你知道什麼是同源政策(Same-origin policy)
同源政策(Same-origin policy)規範了哪寫資源可以跨源存取,哪些會受到限制。其中同源的意思是相同網域、通訊協定和連接埠號,基本上兩者若非同源,則要額外設定 CORS 。
P1 你知道如何存取跨網域的資源(CORS)
想要開啟跨來源請求,必須在 Response Header 設定 Access-Control-Allow-Origin
,規範哪些網域可存取。在 request 這邊又分為簡單請求和預檢請求。
P1 你知道什麼是 JSON
JSON 全名 JavaScript Object Notation ,以純文字的方法儲存資料,樣子和 JS 中物件很像,只是不管是 key 還是 value 都要用雙引號 "
框起來,而整體是個字串。
P2 你知道什麼是 JSONP 及其原理
JSONP 是 JSON with Padding
。
因為考量安全性,透過瀏覽器取得 API 必須遵守同源政策(Same-origin policy,CORS),也就是說除非特別設定,否則不同網域之間無法分享 API (會發 request ,但 response 會被瀏覽器擋下來不給 JavaScript)。
不過有些標籤不被此規範,例如 <img>
或 <scipt>
。JSONP 就是將資料包進 function 中並放在 <scipt>
下,其他人就可以直接使用。
P1 你知道 PHP 是什麼
PHP(Hypertext Preprocessor)是一種可以結合資料庫的動態網頁開發語言。動態網頁和靜態網頁的差別,在於靜態網頁是直接將檔案丟給瀏覽器,但動態網頁則是先由伺服器跑完結果後,再將結果傳給瀏覽器。
P1 你知道前端與後端的差別
使用者可以看的到的部分都是前端,像是新增網頁和設計;後端則是負責和資料庫溝通。
用 Google 表單類比的話,我們在填寫表單時回答的問題是前端負責;如果設計者針對大家的回應開一個試算表,那麼和這個試算表溝通就是後端的工作。
當然大部分的功能需要前後端合力才能達成,例如用搜尋引擎時,前端負責接收我們輸入的關鍵字,交給後端去資料庫中尋找,並回傳結果給前端,最後由前端顯示結果。
P1 你知道什麼是資料庫
一般來說網頁的資料會被存在一個檔案中,但當網頁一大就難管理,而資料庫系統就是專門處理資料的程式,提供專門的語法操作資料庫。
P1 你了解基本的 SQL 語法,包括 Select、Insert Into、Delete 與 Update / 你能夠寫出基本的 CRUD 應用
左轉筆記 給自己看的 PHP 和 MySQL 溝通
P1 你知道什麼是 Session / 你知道什麼是 Cookie / 你知道 Session 與 Cookie 的差別
瀏覽器每次發出 Request 的時候都是無狀態的,也就是說除非特別標記,否則伺服器不會記得對方上次的紀錄(例如登入資訊)。而 Session
就是為了處理這個問題出現的,其中 Cookie 就是一種 Session 的方法。
Cookie
的資料保存方式有點像集點卡,資料由使用者(瀏覽器)端保存,每次消費(發 Request)帶給店家(伺服器)就可使用點數。
實際上 Cookie 是個文字檔,以下流程用登陸資訊為例,第一次登入成功後伺服器就會透過 Response Header 給瀏覽器一份 Cookie 。之後存在 Cookie 裡的登陸資料由瀏覽器保管,下次對同個伺服器發出 Request 時自動帶上 Request Header 中,只要還沒過期而且符合設定的 Domain ,伺服器就知道是上次那個使用者了。
相對的,Session
的資料存在伺服器端,再給瀏覽器一組 token 存在 Cookie 中。就像是電子會員卡,每次會員只要去消費,出示卡片或 ID ,店家就可以從資料庫中得到資料。這樣做的好處是使用者不容易竄改資料,比較安全。
故事開始於在 NGO 工作的朋友的一則邀約:
「我們下週和這裡有合作的活動。你最近也在學程式,要不要來看看?」
隨訊息附上好想工作室的活動連結。
八月底在台北的實體聚會自由交流時間,我一開始超尷尬不知道要找誰聊天,緊張地傳訊息給朋友——「找長得最好看的啊 XD」靠北喔,都什麼時候還在跟我講幹話。好險後來跑幾趟根本沒上的廁所後,開始可以正常和人類說話了 XD 最後離開前得知原來除了志揚助教以外,課程內的其他學生也有被其他學程式的管道拒絕過的經驗。
細數某個年紀前的我嚮往的角色,幾乎都是主角,連最喜歡的顏色也一定要是紅色。年幼的我,太早就知道要崇拜雖然還是有煩惱,但問題還是都會迎刃而解,而且被大家所愛著的人們。
隨便舉個例,主角就絕對不會被好想工作室拒絕。
逐漸體認到自己不可能成為主角,或其實只是放棄成為主角後,我開始著迷於任何不平滑的東西。關注並蒐集這些瑕疵,會讓我感到溫暖。一方面是知道自己不孤單,另一方面我平時習慣懷疑自己的自我懷疑(既然會懷疑,應該就表示不夠喜歡,或是自己不夠堅強了吧)。所以得知別人失敗掙扎卻持續努力的故事,對我來說可能是暗黑版雞湯吧——原來沒自信也是正常的,不是只有特定樣子才能成為自己想要的模樣——並由此產生更多動力。
因此我非常非常感謝不只是這次實體聚會,而是曾經讓我看到些微破綻卻又閃閃發光的人們。
得到了那麼多,我又該以什麼回饋呢?想來想去可能只有把類似的故事說出來了。以下,我會盡力不帶給各位情緒負擔的講述事情經過:
我決定要學習程式設計後,曾經報名過好想工作室的 Backend Camp ,面試完後得到回家作業要架設環境。但我前前後後做了一個禮拜才達成,因此被委婉拒絕了,對方希望我能調整好心態。
之所以之後完全閉口不談,最主要還是因為不知道現在的我,到底「調整好心態」了沒。(不是,先讓我講其他東西,最後會把這部分收回來請先不要關掉 orz)
從上次寫完複習心得並宣稱要追進度到現在,其實也差不多才過了一個月,得知這個訊息的我大驚,現在這是進入精神時光屋了嗎 XD (是說原來精神時光屋是外面的時間才過幾秒,外面感覺像是好幾千年,我之前一直以為是反過來 www)
原來之前 Podcast 說的:「想說要努力找個平衡,結果調整調整,回過神來課程就結束了。」是真的!因為一直有新的學習刺激,反而沒辦法停下來休息,尤其我又落後兩週多。(順帶一提,雖然非常怨懟之前三四週因為腰痛就擺爛的我,但說不定這個有點落後的距離反而是適合我的 XD)
我記得 Podcast 的結論是,面對不斷變動的外在,必須保持動態平衡。果然,擁有彈性是非常重要的。
連對自己心態的評價也必須保持彈性。
像是,我真的喜歡程式設計嗎?
面試好想工作室的時候,對方非常非常在意這個點,他們說不希望浪費彼此的時間才發現我不喜歡程式設計。我也蠻同意的,畢竟誰都希望和有熱情的人共事。
但我其實不知道自己到底喜不喜歡,我無法做出連我自己都無法確定的承諾。
退幾步看,到底怎麼樣算是喜歡一件事情?問了很多人之後歸納出幾點操作型定義:純粹做的時候會很開心、就算沒有其他好處也會去做、會猶豫這件事就代表不喜歡。彷彿「喜歡」是純粹不帶任何目的,而且自然而然,只差沒有不證自明了。
——那我應該沒有喜歡過任何東西吧。
長期要求自己應該要對萬事萬物保有開放性和好奇心,得出這個結論後我消沉了好一陣子,無法釋懷自己其實是個沒熱情的人。就連現在看到討論區很認真討論學到的技術的老師同學和助教,只要稍微鬆懈沒有他們那麼投入,就會馬上把自己打入「你看看你,也沒有那麼喜歡這個啊」的範圍。
一直到很後來才知道,這種事情不是非黑即白。
體會到這件事,是因為最近把《排球少年》的動畫撿回來。第一個瞬間是清水學姊說,她一開始也不是非常喜歡排球,是邊做球經邊喜歡上的;另外是超沒幹勁的月島問木兔「排球有趣嗎?」被反嗆:「那只是你不夠強而已。等你能夠做到(下略境界),就是你喜歡排球的起點。」
原來這樣子也可以稱作喜歡嗎?原來除了「熱情就寫在基因裡面,因此就算沒有成就感也可以堅持下去」以外,還有很多很多種喜歡的樣子嗎!
懶的轉場了(喂),來說點實際的體悟,反正我目前體會到三種策略:
國小的時候非常熱衷於奇摩部落格和無名小站的 CSS 裝飾(天啊其實是黑歷史,好險他們現在都關了……),因此第六週學的時候一直有種「同學你有點眼熟」的既視感 XD
相較前幾週的 JS ,這週的入門門檻確實大大降低了,但切版真的沒有盡頭,好像永遠都有更好的方法,這時候就要勇敢大喊「我就爛!」然後趕快進入下一個工作。另一個比較困擾的是命名,因為兩邊的命名會互相影響,中途要改名就會把自己弄得很混亂,另外如果和檢討影片的命名不同對照起來也會有點抖,也常常不知道這個區塊該叫甚麼名字,好想成為命名大師喔可惡。最後為了錯亂,我會先在紙上畫好每個區塊叫什麼,並且規定自己一口氣切好全部的版,第六週和第九週作業都試了這個方法,覺得腦袋比較清晰,而且一次做完感覺很爽。
進入 PHP 後,因為沒有用任何框架,大家都在喊變得超複雜。但我覺得這種完全不依靠其他人(雖然程式設計照理來說依靠了其他很多人,是個漂流到荒島上就毫無用處的專長,但先忽略這部分啦 XD)獨自架起留言板的感覺真的很爽(詞彙貧乏)
看文章 白話 Session 與 Cookie:從經營雜貨店開始 的雜貨店舉例時腦中警鈴大響:事情哪有那麼簡單!?
年初在補習班做行政櫃台的兼職,最苦手的工作之一就是下課時看到家長來接,要廣播提醒學生下樓。因為我也是超級大臉盲和金魚腦,就做了各種 excel 表格和筆記幫助我對照學生的名字、年級、今天補什麼和教室分機號碼,但最後卻被說不如多詢問幾次記在腦袋裡,要能做到不靠任何東西記起來。
雖然現在回想起來陰影面積還是有點大,但反正每個人都有擅長和不擅長的事。這份工作給我的幾個體悟:比起一口氣和四百個學生都有點認識,我比較喜歡且擅長只和少少幾個人深交;再來就是我就是不擅長只用腦袋記名字和認臉,現在才會需要在這裡學程式設計吧。現在想想當初用表格整理學生資料方便查詢,是否也有點像用程式解決問題。
不只是為了私人困擾,在學習的時候知道自己「幹嘛學這個」對我來說也是蠻重要的。比如說 API 對我來說就一直是有點浮在空中的東西,一直到上次去小樹屋才聽到助教說 API 之後會用在前後端溝通,才覺得比較踏實一點。
第九週的留言板架起來後很快樂的貼給朋友,朋友問:「你是買榜嗎?怎麼下面那麼多人說生菜好可愛?」
我:才沒有,因為這是我做的,我就把預設內容改成「生菜好可愛!」了。不然你以為我幹嘛學寫程式 XD
朋友:????好喔。
現在的我真的調整好了嗎?
我還是不知道,反正現在的我就是這個樣子,你們都看到了。我現在學得蠻快樂的,還想繼續再走一段路。
我打下「可惜我那天有事,改天來去看看~」,送出。
]]>遊戲連結:r3:0 異世界網站挑戰
因為之前的關卡已經有人 寫過了 ,因此這篇就只寫最近更新的 11 ~ 15 關。
用右鍵打開檢查後,發現有個 API 因為同源政策串接失敗,因此我們要避開瀏覽器,使用 Node.js 得到 API 。
const request = require('request');
request.post({uri:'https://glacial-everglades-11859.herokuapp.com/api.php'}, function(err, res, body) {
console.log(body);
})
執行後就可以得到 token 前往下一關了 XD
點開網址後會出現很像 PTT 風格的討論版,上面的「管理員登入」讓人卡好久,後來才發現他不是 submit 只是普通文字,超惡趣味 (?)
真正的機關在 .js 檔裡面:
id 不等於 888888 才會送出 request,那 id=888888 的那條訊息到底有甚麼呢?我們用 Node.js 發出 request 看看:
const request = require('request');
request('http://r30challenge.herokuapp.com/news_api.php?id=888888', function(err, res, body) {
console.log(body);
})
出現了!
能看到這則留言的你,想必就是天選之人吧! {fakeituntilyoumakeit} 拯救這個世界吧!
點開瀏覽器的 Response Header ,會發現 Set-cookie 那攔寫 Set-Cookie: token=do_you_really_know_how_to_set_cookie?; Comment=real_token_is:{you_are_cookie_master}
輸入就可以破關了~
基本上就是變向的猜數字遊戲了,反應時間越長表示越接近標準答案。
不知道要做什麼的時候,只要打開 Devtool 就可以找到線索!果然 .php 檔中找到一段被隱藏的程式碼:
<!-- secret logic
function isTokenValid($token) {
$h = date('H');
$m = date('i');
$a = $h * $m + 42;
$count = 0;
for($i = 0; $i < 8; $i++) {
$count += ord($token[$i]) - 65;
}
if ($count <= 100) {
return false;
}
return $a % $count === 0;
}
-->
從上述整理出以下線索:
$count
,必須大於一百。$a
是送出時間的小時(24 小時寫法)乘以分鐘再加上 42 ,必須可以整除 $count
。最後我設定在五分鐘後 16 點 35 送出,如此一來 $a 就等於 602 ,其大於一百的因數只有 301 。英文字母中 ASCII 最大的 z 是 122:
122 - 65 = 57
301 / 57 = 5.2(有五個 z)
301 % 57 = 16
剩下三個可以放 2 個 ASCII 為 73 的 I 和一個 65 的 A , 最後成功在時間內送出 ?token=zzzzzIIA
。
我還是回去當我的工程師好了(#
沒有啦,老實說我卡最久的是第十一關。一來是發現接下來的路沒有攻略可以偷看,二來是作賊心虛,看到 API 無論如何先害怕慌張一下,而且一直覺得 POST 應該就是要新增什麼東西,因此就算有 url 了,找不到要 POST 的東西又讓我更慌張,最後還是看到第十週心得偷偷去問 Nicolakacha 同學才解開,真的是十分感謝(合十)
這個系列都好有趣,滿足我市面上優質的免費解謎遊戲不夠的困擾,尤其這次完全透過螢幕感受到製作者的中二之力迎面而來 XD
如上面所說,我前十關偶爾會偷看別人的解答,是因為這幾關比較新才還沒有人製作攻略。不過完全靠自己解出來的關卡爽度就是不一樣,因此我也不是很確定這篇文章對其他人到底是好是壞,但總之我玩得很爽,希望能幫到你:)
遊戲連結:r3:0 異世界網站挑戰
因為之前的關卡已經有人 寫過了 ,因此這篇就只寫最近更新的 11 ~ 15 關。
用右鍵打開檢查後,發現有個 API 因為同源政策串接失敗,因此我們要避開瀏覽器,使用 Node.js 得到 API 。
const request = require('request');
request.post({uri:'https://glacial-everglades-11859.herokuapp.com/api.php'}, function(err, res, body) {
console.log(body);
})
執行後就可以得到 token 前往下一關了 XD
點開網址後會出現很像 PTT 風格的討論版,上面的「管理員登入」讓人卡好久,後來才發現他不是 submit 只是普通文字,超惡趣味 (?)
真正的機關在 .js 檔裡面:
id 不等於 888888 才會送出 request,那 id=888888 的那條訊息到底有甚麼呢?我們用 Node.js 發出 request 看看:
const request = require('request');
request('http://r30challenge.herokuapp.com/news_api.php?id=888888', function(err, res, body) {
console.log(body);
})
出現了!
能看到這則留言的你,想必就是天選之人吧! {fakeituntilyoumakeit} 拯救這個世界吧!
點開瀏覽器的 Response Header ,會發現 Set-cookie 那攔寫 Set-Cookie: token=do_you_really_know_how_to_set_cookie?; Comment=real_token_is:{you_are_cookie_master}
輸入就可以破關了~
基本上就是變向的猜數字遊戲了,反應時間越長表示越接近標準答案。
不知道要做什麼的時候,只要打開 Devtool 就可以找到線索!果然 .php 檔中找到一段被隱藏的程式碼:
<!-- secret logic
function isTokenValid($token) {
$h = date('H');
$m = date('i');
$a = $h * $m + 42;
$count = 0;
for($i = 0; $i < 8; $i++) {
$count += ord($token[$i]) - 65;
}
if ($count <= 100) {
return false;
}
return $a % $count === 0;
}
-->
從上述整理出以下線索:
$count
,必須大於一百。$a
是送出時間的小時(24 小時寫法)乘以分鐘再加上 42 ,必須可以整除 $count
。最後我設定在五分鐘後 16 點 35 送出,如此一來 $a 就等於 602 ,其大於一百的因數只有 301 。英文字母中 ASCII 最大的 z 是 122:
122 - 65 = 57
301 / 57 = 5.2(有五個 z)
301 % 57 = 16
剩下三個可以放 2 個 ASCII 為 73 的 I 和一個 65 的 A , 最後成功在時間內送出 ?token=zzzzzIIA
。
我還是回去當我的工程師好了(#
沒有啦,老實說我卡最久的是第十一關。一來是發現接下來的路沒有攻略可以偷看,二來是作賊心虛,看到 API 無論如何先害怕慌張一下,而且一直覺得 POST 應該就是要新增什麼東西,因此就算有 url 了,找不到要 POST 的東西又讓我更慌張,最後還是看到第十週心得偷偷去問 Nicolakacha 同學才解開,真的是十分感謝(合十)
這個系列都好有趣,滿足我市面上優質的免費解謎遊戲不夠的困擾,尤其這次完全透過螢幕感受到製作者的中二之力迎面而來 XD
如上面所說,我前十關偶爾會偷看別人的解答,是因為這幾關比較新才還沒有人製作攻略。不過完全靠自己解出來的關卡爽度就是不一樣,因此我也不是很確定這篇文章對其他人到底是好是壞,但總之我玩得很爽,希望能幫到你:)
第一次學 PHP 和 MySQL ,本篇會簡單記錄觀念,最主要會實作如何使用 PHP 和 MySQL 連線的基礎,也就是 CURD(Create、Read、Update 和 Delete)。既是給初學者看,也方便自己之後回頭參考。
MySQL 是一種關聯式資料庫。
一般來說網頁的資料會被存在一個檔案中,但當網頁一大就難管理,而資料庫系統就是專門處理資料的程式,提供專門的語法操作資料庫。
本文使用的系統 MariaDB 是 MySQL 的分支,我們可以選擇使用 CLI 和資料庫溝通,也可以用 GUI 例如 phpmyadmin 。
因為有人覺得比較好分辨,以下基礎語法都是大寫,但實際執行時小寫也可以。 table name 的反引號也可以省略。
INSERT INTO `<table name>` ('<key1>', '<key2>') VALUES('value1', 'value2');
UPDATE `<table name>` SET 'key1'='value1', 'key1'='value1' WHERE 條件
要是沒設定條件,會直接影響到全部。
SELECT key1, key2 FROM `<table name>` WHERE 條件;
*
選取符合條件的資料的所有欄位。id as name
,就是暫時將欄位名改名。DELETE FROM `<table name>` WHERE 條件
但很多時候我們點選刪除並不是真的將資料刪除,而是將 is-delete 的攔位改成 true ,之後網站再只呈現沒被刪掉的就好了。
若網頁讀取 .css 檔案,順序是瀏覽器 request 丟給 Server 後, Server 直接將找到的檔案丟回給瀏覽器。但動態網頁不同, Server 收到瀏覽器的 Request 後,找到 .php 檔先執行完後,才將結果 response 給瀏覽器。不過根據 Server 設定部圖, .php 檔也有可能是靜態網頁,而其中差別在 Server 是否有執行。
本文送出資料的方式是使用表單,也就是在 .php 中加入這段語法:
<form method='方法' action='存取目的'>
query string
,例如 ?key1=value1&key2=value2
。所以也可以直接在 url 加上 query string 也能達成一樣的效果。$_GET
中 ,需要提取值時就用 $_GET['key']
。isset($_GET['key'])
判斷是否有輸入值。$_POST['key']
提取資料。empty()
來判斷是否有輸入值。<?php
/* 基礎資料設定通常最後會隱藏起來,
可在 .gitignore 加入這個檔案*/
$server_name = 'localhost'; //伺服器名稱
$username = 'v61265'; //帳號
$password = 'v61265'; //密碼
$db_name = 'v61265'; //資料庫名稱
/* 四個參數依序放:伺服器名稱、帳號、密碼、資料庫名稱 */
$conn = new mysqli($server_name, $username, $password, $db_name);
/* 有問題就回傳錯誤資訊,
die() 顯示完後程式就不會繼續往下跑,
也可以 echo 完後 exit(); */
/* 存取物件屬性用 -> ,對應 JS 的 . */
if ($conn-> onnect_error) {
die('資料庫連線錯誤' . $conn->connect_error);
}
/* 設定檔案格式和時區 */
$conn->query('SET NAMES UTF8');
$conn->query('SET time_zone= "+8:00"');
?>
這個檔案只會放連線資訊,其他檔案要用時要引入:
require_once('conn.php);
步驟:
<?php
/* 和資料庫連線 */
require_once('conn.php');
/* table 'user' 中的資料全部存到變數 $result */
$result = $conn->query("SELECT * FROM user");
/* 如果 $result 為假,回傳錯誤訊息 */
if (!$result) {
die($conn->error);
}
/* 先執行完 $row = $result->fetch_assoc() 才判斷真假,
全部拿完的話 $row 為 null ,即可跳脫迴圈。*/
while ($row = $result->fetch_assoc()) {
echo "id:" . $row['id'] . '<br>';
echo "username" . $row['users'] . '<br>';
}
?>
->fetch_assoc()
可以拿到第一筆資料,第二次出現時可以拿到第二筆資料。此類推讓每個值都跑一次就可以顯示所有資料。
<?php
require_once('conn.php');
/* 如果沒有輸入就提示 */
if (empty($_POST['username'])) {
die('請輸入 username');
}
/*得到 usernam 的值和 sql 語法*/
$username = $_POST['username'];
$sql = sprintf(
"insert into user(username) values('%s')",
$username
);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
/* 重新導向 index.php */
header("Location: index.php");
?>
sprintf(string, variable)
可以想成像 ES6 的 Template Literals,前面放文字,如果遇到需要放變數的字就用 %s 、數字就用 %d ,後面依序放入變數。"insert into user(username) values(' " . %s . "')"
->query("SELECT * FROM user ORDER")
。最後加上 ASC
是由小排到大, DESC
則是大到小。邏輯:
如果 id 不存在,會執行成功但影響 0 行
可以用 $conn->affected_rows
看影響到幾列,藉此判斷是否成功。
<?php
require_once('conn.php');
/* 沒拿到 get 就顯示錯誤 */
if(empty($_GET['id'])) {
die('刪除失敗');
}
/* 設定 id 和指令 */
$id = $_GET['id'];
$sql = sprintf(
"DELETE FROM user WHERE id=%d", $id
);
echo $sql . '<br>';
/* query 執行刪除 */
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
/* 有資料被影響就顯示成功,否則查無資料 */
if ($conn->affected_rows >= 1) {
echo '刪除成功';
} else {
echo '查無資料';
}
?>
步驟:
<?php
require_once('conn.php');
if (empty($_POST['id']) || empty($_POST['username'])) {
die('請輸入 id 與 username');
}
$id = $_POST['id'];
$username = $_POST['username'];
$sql = sprintf(
"UPDATE user SET username='%s' WHERE id=%d",
$username,
$id
);
echo $sql . '<br>';
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
header("Location: index.php");
?>
第一次學 PHP 和 MySQL ,本篇會簡單記錄觀念,最主要會實作如何使用 PHP 和 MySQL 連線的基礎,也就是 CURD(Create、Read、Update 和 Delete)。既是給初學者看,也方便自己之後回頭參考。
MySQL 是一種關聯式資料庫。
一般來說網頁的資料會被存在一個檔案中,但當網頁一大就難管理,而資料庫系統就是專門處理資料的程式,提供專門的語法操作資料庫。
本文使用的系統 MariaDB 是 MySQL 的分支,我們可以選擇使用 CLI 和資料庫溝通,也可以用 GUI 例如 phpmyadmin 。
因為有人覺得比較好分辨,以下基礎語法都是大寫,但實際執行時小寫也可以。 table name 的反引號也可以省略。
INSERT INTO `<table name>` ('<key1>', '<key2>') VALUES('value1', 'value2');
UPDATE `<table name>` SET 'key1'='value1', 'key1'='value1' WHERE 條件
要是沒設定條件,會直接影響到全部。
SELECT key1, key2 FROM `<table name>` WHERE 條件;
*
選取符合條件的資料的所有欄位。id as name
,就是暫時將欄位名改名。DELETE FROM `<table name>` WHERE 條件
但很多時候我們點選刪除並不是真的將資料刪除,而是將 is-delete 的攔位改成 true ,之後網站再只呈現沒被刪掉的就好了。
若網頁讀取 .css 檔案,順序是瀏覽器 request 丟給 Server 後, Server 直接將找到的檔案丟回給瀏覽器。但動態網頁不同, Server 收到瀏覽器的 Request 後,找到 .php 檔先執行完後,才將結果 response 給瀏覽器。不過根據 Server 設定部圖, .php 檔也有可能是靜態網頁,而其中差別在 Server 是否有執行。
本文送出資料的方式是使用表單,也就是在 .php 中加入這段語法:
<form method='方法' action='存取目的'>
query string
,例如 ?key1=value1&key2=value2
。所以也可以直接在 url 加上 query string 也能達成一樣的效果。$_GET
中 ,需要提取值時就用 $_GET['key']
。isset($_GET['key'])
判斷是否有輸入值。$_POST['key']
提取資料。empty()
來判斷是否有輸入值。<?php
/* 基礎資料設定通常最後會隱藏起來,
可在 .gitignore 加入這個檔案*/
$server_name = 'localhost'; //伺服器名稱
$username = 'v61265'; //帳號
$password = 'v61265'; //密碼
$db_name = 'v61265'; //資料庫名稱
/* 四個參數依序放:伺服器名稱、帳號、密碼、資料庫名稱 */
$conn = new mysqli($server_name, $username, $password, $db_name);
/* 有問題就回傳錯誤資訊,
die() 顯示完後程式就不會繼續往下跑,
也可以 echo 完後 exit(); */
/* 存取物件屬性用 -> ,對應 JS 的 . */
if ($conn-> onnect_error) {
die('資料庫連線錯誤' . $conn->connect_error);
}
/* 設定檔案格式和時區 */
$conn->query('SET NAMES UTF8');
$conn->query('SET time_zone= "+8:00"');
?>
這個檔案只會放連線資訊,其他檔案要用時要引入:
require_once('conn.php);
步驟:
<?php
/* 和資料庫連線 */
require_once('conn.php');
/* table 'user' 中的資料全部存到變數 $result */
$result = $conn->query("SELECT * FROM user");
/* 如果 $result 為假,回傳錯誤訊息 */
if (!$result) {
die($conn->error);
}
/* 先執行完 $row = $result->fetch_assoc() 才判斷真假,
全部拿完的話 $row 為 null ,即可跳脫迴圈。*/
while ($row = $result->fetch_assoc()) {
echo "id:" . $row['id'] . '<br>';
echo "username" . $row['users'] . '<br>';
}
?>
->fetch_assoc()
可以拿到第一筆資料,第二次出現時可以拿到第二筆資料。此類推讓每個值都跑一次就可以顯示所有資料。
<?php
require_once('conn.php');
/* 如果沒有輸入就提示 */
if (empty($_POST['username'])) {
die('請輸入 username');
}
/*得到 usernam 的值和 sql 語法*/
$username = $_POST['username'];
$sql = sprintf(
"insert into user(username) values('%s')",
$username
);
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
/* 重新導向 index.php */
header("Location: index.php");
?>
sprintf(string, variable)
可以想成像 ES6 的 Template Literals,前面放文字,如果遇到需要放變數的字就用 %s 、數字就用 %d ,後面依序放入變數。"insert into user(username) values(' " . %s . "')"
->query("SELECT * FROM user ORDER")
。最後加上 ASC
是由小排到大, DESC
則是大到小。邏輯:
如果 id 不存在,會執行成功但影響 0 行
可以用 $conn->affected_rows
看影響到幾列,藉此判斷是否成功。
<?php
require_once('conn.php');
/* 沒拿到 get 就顯示錯誤 */
if(empty($_GET['id'])) {
die('刪除失敗');
}
/* 設定 id 和指令 */
$id = $_GET['id'];
$sql = sprintf(
"DELETE FROM user WHERE id=%d", $id
);
echo $sql . '<br>';
/* query 執行刪除 */
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
/* 有資料被影響就顯示成功,否則查無資料 */
if ($conn->affected_rows >= 1) {
echo '刪除成功';
} else {
echo '查無資料';
}
?>
步驟:
<?php
require_once('conn.php');
if (empty($_POST['id']) || empty($_POST['username'])) {
die('請輸入 id 與 username');
}
$id = $_POST['id'];
$username = $_POST['username'];
$sql = sprintf(
"UPDATE user SET username='%s' WHERE id=%d",
$username,
$id
);
echo $sql . '<br>';
$result = $conn->query($sql);
if (!$result) {
die($conn->error);
}
header("Location: index.php");
?>
雖然目前進度有點落後,不過因為是第一次那麼有系統性的學習 HTML 和 CSS。因此這篇筆記的目的是,讓未來的自己想不起來「咦這個功能叫什麼名字」的時候可以回頭查看。因為目前只看完 EF101 ,也當作再複習一次,比較安心。
更新紀錄:
2020/08/09 一小時挑戰 => 延續至 08/10
HTML 全名是 Hyper Text Markup Language 超文本標記語言,它並不是一個程式語言。主要用於規劃網頁架構,通過標籤定義網頁的原始內容,例如:這行字是標題,這幾行是一塊,這行字是按鈕。
<!DOCYTPE HTML>
:通常會加在檔案最前面,表示我要用最新標準格式,可加可不加。
<html>
:將整個網頁包起來。
<head>
:放基本資訊。
<body>
:網頁主要內容。
因此一個基本的 HTML 架構會長這樣:
<html>
<head>
</head>
<body>
</body>
</html>
HTML 的標籤都是由一組包起來的,例如 <html>
(開)和 </html>
(合)就是一組,其中就是該標籤作用處。有些標籤也可將開合寫在一起,例如 header 中指定編碼方式的 <meta charset="utf8" />
就是一個例子。
<title>
是另一個常見於放在 head 的標籤,可設定該網頁的標題。在瀏覽器看起來就會如下:
另外,若要 加註解
的話,可用 <!--註解註解-->
,如此內容便不會顯示於網頁上。
語意化標籤的目的是為了讓標籤(Tag)更具意義,以加強文件的結構化,讓搜尋引擎更清楚了解。例如早期分段都只用 <div>
,現在卻多了 <header>
、 <main>
和 <footer>
之類的,雖然效果顯示上一樣,不過更方便回頭 debug 或溝通。
HTML 最早就是為了顯示文字,最常見的兩種為:
<h1>
~ <h6>
: 有點類似 Markdown中文字前面加上 #
~ ######
,數字越大字就越大。
<p>
:一個文字段落。
另外因為內容會忽略前後的空格,因此若要保留完整輸入格式,可用 <pre>
包起來。另外加入 <br/>
可強制換行。
排版的詳細好處會在 CSS 感受到。
<div>
:包住一組文字,並且自動換行。
<span>
:包住一組文字,但不會自動換行,通常是在段落內的文字。
<img src="url"/>
屬性:
alt="替代文字"
:當圖片連結失效,原本位置會顯示替代文字。
title="文字"
:滑鼠移上去會顯示的文字。
<li>
:個別包住清單中的每一條。
<ul>
:包住沒有排序的。
<ol>
:則是有排序的。
例如在 body 輸入:
<ul>
<li>ul是</li>
<li>沒有排序的清單</li>
</ul>
<ol>
<li>ol包住</li>
<li>有排序的清單</li>
</ol>
就會變成這樣:
<table>
:框住整個表格。
<tr>
:框出橫的 row 。
<td>
:框出直的 colmn 。
<th>
:和 <td>
很像,可用於標題。
例如:
<table>
<tr>
<th>編號</th>
<th>姓名</th>
<th>成績</th>
</tr>
<tr>
<td>001</td>
<td>小明</td>
<td>30</td>
</tr>
<tr>
<td>002</td>
<td>小美</td>
<td>92</td>
</tr>
</table>
會出現:
編號 | 姓名 | 成績 |
---|---|---|
001 | 小明 | 30 |
002 | 小美 | 92 |
a
是 anchor 的意思,通常搭配 href
(hypertext referance) 指向目的地。
<!-- 超連結 -->
<a href="https://www.coderbridge.com/@v61265">我的主頁</a>
<!-- 像維基百科那樣連到自己頁面的其他地方 -->
<a id='123'>目標內容</a>
<a href='#123'>連結文字:到目標內容去</a>
會出現:
我的主頁
連結文字:到目標內容去
另外 a 有個屬性 target
:
target:_self
:直接連結到網頁,沒設定的話就預設這種。
target:_blank
:在新分頁開啟網頁。
<iframe src="url" width=100px height=20% />
<form>
<!--輸入框-->
<input type="text" />
<!--送出按鈕-->
<input type="submit" value="上面的字" />
<!--密碼框(會自動碼掉輸入)-->
<input type="password"/>
<!--輸入信箱(自動偵測有沒有@)-->
<input type="email"/>
<!--輸入日期-->
<input type="date"/>
<!--單選-->
<input type="radio" name="群組名稱" id="選項名"/><label for="選項名">選項描述(點這個字也可以選到)</label>
<!--多選-->
<input type="checkbox"/>
</form>
雖然目前進度有點落後,不過因為是第一次那麼有系統性的學習 HTML 和 CSS。因此這篇筆記的目的是,讓未來的自己想不起來「咦這個功能叫什麼名字」的時候可以回頭查看。因為目前只看完 EF101 ,也當作再複習一次,比較安心。
更新紀錄:
2020/08/09 一小時挑戰 => 延續至 08/10
HTML 全名是 Hyper Text Markup Language 超文本標記語言,它並不是一個程式語言。主要用於規劃網頁架構,通過標籤定義網頁的原始內容,例如:這行字是標題,這幾行是一塊,這行字是按鈕。
<!DOCYTPE HTML>
:通常會加在檔案最前面,表示我要用最新標準格式,可加可不加。
<html>
:將整個網頁包起來。
<head>
:放基本資訊。
<body>
:網頁主要內容。
因此一個基本的 HTML 架構會長這樣:
<html>
<head>
</head>
<body>
</body>
</html>
HTML 的標籤都是由一組包起來的,例如 <html>
(開)和 </html>
(合)就是一組,其中就是該標籤作用處。有些標籤也可將開合寫在一起,例如 header 中指定編碼方式的 <meta charset="utf8" />
就是一個例子。
<title>
是另一個常見於放在 head 的標籤,可設定該網頁的標題。在瀏覽器看起來就會如下:
另外,若要 加註解
的話,可用 <!--註解註解-->
,如此內容便不會顯示於網頁上。
語意化標籤的目的是為了讓標籤(Tag)更具意義,以加強文件的結構化,讓搜尋引擎更清楚了解。例如早期分段都只用 <div>
,現在卻多了 <header>
、 <main>
和 <footer>
之類的,雖然效果顯示上一樣,不過更方便回頭 debug 或溝通。
HTML 最早就是為了顯示文字,最常見的兩種為:
<h1>
~ <h6>
: 有點類似 Markdown中文字前面加上 #
~ ######
,數字越大字就越大。
<p>
:一個文字段落。
另外因為內容會忽略前後的空格,因此若要保留完整輸入格式,可用 <pre>
包起來。另外加入 <br/>
可強制換行。
排版的詳細好處會在 CSS 感受到。
<div>
:包住一組文字,並且自動換行。
<span>
:包住一組文字,但不會自動換行,通常是在段落內的文字。
<img src="url"/>
屬性:
alt="替代文字"
:當圖片連結失效,原本位置會顯示替代文字。
title="文字"
:滑鼠移上去會顯示的文字。
<li>
:個別包住清單中的每一條。
<ul>
:包住沒有排序的。
<ol>
:則是有排序的。
例如在 body 輸入:
<ul>
<li>ul是</li>
<li>沒有排序的清單</li>
</ul>
<ol>
<li>ol包住</li>
<li>有排序的清單</li>
</ol>
就會變成這樣:
<table>
:框住整個表格。
<tr>
:框出橫的 row 。
<td>
:框出直的 colmn 。
<th>
:和 <td>
很像,可用於標題。
例如:
<table>
<tr>
<th>編號</th>
<th>姓名</th>
<th>成績</th>
</tr>
<tr>
<td>001</td>
<td>小明</td>
<td>30</td>
</tr>
<tr>
<td>002</td>
<td>小美</td>
<td>92</td>
</tr>
</table>
會出現:
編號 | 姓名 | 成績 |
---|---|---|
001 | 小明 | 30 |
002 | 小美 | 92 |
a
是 anchor 的意思,通常搭配 href
(hypertext referance) 指向目的地。
<!-- 超連結 -->
<a href="https://www.coderbridge.com/@v61265">我的主頁</a>
<!-- 像維基百科那樣連到自己頁面的其他地方 -->
<a id='123'>目標內容</a>
<a href='#123'>連結文字:到目標內容去</a>
會出現:
我的主頁
連結文字:到目標內容去
另外 a 有個屬性 target
:
target:_self
:直接連結到網頁,沒設定的話就預設這種。
target:_blank
:在新分頁開啟網頁。
<iframe src="url" width=100px height=20% />
<form>
<!--輸入框-->
<input type="text" />
<!--送出按鈕-->
<input type="submit" value="上面的字" />
<!--密碼框(會自動碼掉輸入)-->
<input type="password"/>
<!--輸入信箱(自動偵測有沒有@)-->
<input type="email"/>
<!--輸入日期-->
<input type="date"/>
<!--單選-->
<input type="radio" name="群組名稱" id="選項名"/><label for="選項名">選項描述(點這個字也可以選到)</label>
<!--多選-->
<input type="checkbox"/>
</form>
程式導師計畫第四期從 6/12 開始,到今天已經第七週了,此篇「第幾週」並不是根據實際時間,而是以 課綱 為主。這篇同時作為第五週的作業,整理加入計畫後至今的心得。
內含ㄉ技能
每週ㄉ速記
[DAY4] 初學Command Line
[DAY8] 初學 Git (上)
[DAY9] 初學 Git (下)
去年大學畢業終於被體制拋出,因為興趣太廣泛不知道要選哪個,乾脆毫無規劃的隻身一人到台南,以為隨便找份工作,過了一年什麼都不做,就會忽然知道自己要做什麼了。結論是我果然還太天真了,不只工作難找,畢業滿一年的今天,除了每天都有甜爆的美食,我依然是那個沒有方向的自己。
前幾天接到很久沒聯絡的學妹電話,說她找到了一份還不錯的工作,並問我在幹嘛。
「目前沒有工作啊。」我小海豹化。
然後不意外的被唸——都 23 歲,了是時候該為未來做打算了吧。
「……啊,但是我在學程式設計ㄛ。」
『三小?你不是心理系的嗎?』對欸,我都忘記這個設定了。
關於為什麼要學程式,這半年的契機太羞恥了請允許我先跳過。不過其實也不是最近裝水時才突發奇想,退一萬步也是高中裝水想到,早在升大學時就有想過填資工系了,最後因為いろいろ原因沒去。(請放過我留一些下次寫心得時可以講的故事 XD)
總之,這半年決定要學程式設計後,又跌跌撞撞了一段路,加上求職和就業等失敗經驗,送出履歷前大概是我對自己未來最絕望了時刻。就連履歷送出去前,都被幫忙看履歷的朋友看雖。當初想如果沒上我就試試看北科大的 Coding365 ,再不行就乖乖考個研究所好了,反正以我現在的能力出社會只會被打爆。終於,在一年內被拒絕無數次之後,居然收到 Huli 的信說我進了???
在快樂的像是(至少是我想像中的)蜜月期劈哩啪啦註冊完所有帳號後,用心發了自我介紹以及和同學社交(但又不能積極到被發現是怪咖qq),我馬上面臨第一個困難——如何上線上課程。
這個問題聽起來很白癡,不就跟到學校上課一樣嗎?但環境不同真的差超多,而且想偷懶的時候只要按暫停就可以去摸魚,超級考驗自制力的。尤其 CML 和 Git 雖然大家都說很帥很像駭客,不過實際上就是成就感超低的啊。這個問題到我將速度調快到一點五倍才暫時被改善(所以後來直播時是我第一次聽 Huli 用一倍速說話XD)。最嚴重的問題是,我該如何做筆記和寫每日進度?爬前幾屆同學的筆記,這已經不只是程式設計的計畫了,根本是筆記整理和幹話訓練班吧(???)
欸爆字數了,第二週心得待續好ㄌ
git pull <欲抓連結>
可將網路上的資料抓下來,與之相反的 git pull <branch>
可將資料傳上去。pull request
可於上傳資料後,打開 GitHub 最上方就可以送出了。[Week2] 給自己看的 JavaScript 筆記 -運算
[Week2] 給自己看的 JavaScript 筆記-變數
[Week2] 給自己看的 JavaScript 筆記 - 迴圈、函式、其他觀念
延續上一週,到底要如何寫出好看的筆記,最好是可以被助教勾選「值得參考」的呢?我花了超多時間想把筆記做得精美,寫了幾篇後發現 CP 值爆低,而且老師和助教都開始呼籲大家不要只顧著做筆記,推進進度比較重要。因此開始告訴自己 先求有再求好 ,雖然快了一點但效果還是有點差,這週給自己「幾小時挑戰」然後發現還是都超過了,因此之後幾週的策略就改成 沒有筆記也沒關係。
雖然立志待業中所以每天要學 8 小時,但每天至少有兩三個小時花在自我懷疑和逃避。Git 對我來說是全新的東西,在還沒看作業說明前,我完全不知道還要追多遠才能跟上大家的進度,因此焦慮又更深了,「我根本就不適合程式設計,放棄好了,才剛開始就被這種挫折打敗的我,根本就不喜歡寫程式吧,我只是碰到問題就逃避的廢物——」之類的鬼打牆不斷消耗自己,又更無法學習了。
另一個很焦慮的點是,現在社會的傳統想像中人們應該要超級精通一個東西,但如果每個人的興趣像遊戲的技能點,我明顯就是點的很分散的那種人,因此好擔心對程式的熱忱不如我想像中高,甚至沒有到達客觀標準。
這個選擇真的是對的嗎?
然後我想到前幾天朋友告訴我他唸超難畢業又前途模糊的研究所,並不是因為「想要成為什麼樣的人」,而是「想看看可以走到哪裡」。這個想法讓我感到舒緩一點,也許就像平板撐一樣,再多堅持幾秒,因為我很想知道,跨過這些障礙之後,我能看到怎麼樣不同的風景。
我想看看那邊的風景。
練習 LIOJ 時有一題卡了好幾天,用了各種方法都卡住,才決定在 spectrum 上提問。後來發現只是錯一個字母 orz 後來在每日進度吐槽這件事, JAS0N 同學特別來向我澄清他其實有給我線索,才發現原來我看起來在怪他 qq 但其實大部分在和別人聊天的過程(尤其是訊息聊天),我都在擔心「要如何讓對方覺得跟我說話/幫我解答是值得的」。所以在此特別再感謝一次不知道會不會看到的 JAS0N 和 Huli 大大(合十)
回傳
output,數學中的 f(x)=y
中的 y 就是回傳值。在 Javascript 中用 return 表示,如果沒有 return 則預設回傳值為 undefined
。function print(a) {
return a;
}
print('hello')
// 會回傳 hello ,但不會顯示任何東西
console.log(print('hello'))
// 印出 hello
相對的, console.log()
只是單純將內容 輸出
。
function print(a) {
console.log(a);
}
print('hello')
// 印出 hello
console.log(print('hello'))
// undefined
截至這週為止,我都只落後一兩天。一開始的契機可能只是腰痛到站不好。醫生說不要搬重物,但我忽然發現缺乏運動的我的身體,本身就是重物 QQ 導致我只能在床上工作,而且變得好嗜睡,又因為連續一週沒有休息,太緊繃往回彈進度就推得很慢,有些日子甚至只學三四個小時。進度一落後壓力就變大,根據壓力曲線,壓力大到一個程度效率會降低,如此形成惡性循環。
沒有推進太多進度得時候,我也就不趕寫太多每日心得。後來發現拖延是太想要完美,既然這樣那就乾脆就先捨棄一部份的完美繼續往前好了——明明前幾個禮拜就有覺悟了,要實踐果然好難。
這週起大家陸陸續續開始約實體聚會了,因為在台南無法參加,我也開了兩次螢幕共享,效果都還不錯。後來 zangwang 同學傳訊息跟我說,《注意力協定》一書中提到類似的效果,這有點像是大聲說「我要來努力了!」,之後如果跟大家說「抱歉啊我是個沒有毅力的人,先去玩一下動物森友會。」其實是有壓力的,雖然也不是完全不能這樣做(畢竟人生是自己的),但羞恥心之類的會讓偷懶這件事要付出的代價變高(而且比起之後進度落後,這個代價是要先付出的會比較有感覺),因此可以用契約提高專注力。
另外好像不小心習慣偷懶了,直接進入倦怠期,尤其越來越沒有幹勁寫每日進度(要怎樣一直產出高質量心的辣——於是我又落入完美主義拖延的困境)。在心得上提到這件事, Wozski 同學(嗚嗚嗚同學人都好好)就來私訊我說,不妨將學習當作習慣,如果今天很不想做的話就從五分鐘開始,再慢慢延長,重點不是今天做了多好(甚至多好),而是讓自己習慣每天做這件事情。我之前一直以為運動或被單字才有習慣可言,沒想到光是持續跟完課就是一個毅力上的挑戰了。
str.length
或 arr.length
:回傳字串或陣列的長度。Number(str)
:將字串轉成數字。arr.join()
:將東西加入陣列中。[Week4] JS 實作串接 API(一)
[Week4] JS 實作串接 API(二)
[Week4] JS 實作串接 API(三)
因為之前偷偷學過一點 Python,這週終於又回到自己完全沒概念的東西了,懷抱忐忑的心情點開課程影片——咦,居然那麼好懂嗎?從傳紙條範例開始,每個概念幾乎都不太難,課綱為何要為了這種東西花一整週——然後才在作業找到答案:廢話,那是因為你還沒開始實作啊!
寫作業的過程艱難到我認為必須將其記錄成筆記,不然幾乎每一題都要先拆解答再看,超心虛的。比如說:為何 request 要放 (err, res, body)
?又或者 post 之後回傳的 body 是什麼?這些感覺好像是約定俗成的東西,都必須一個個去重新摸索。因為幾乎是看著解答寫,讓我超級超級心虛,還好之後有 HTTP-challenge 才比較扎實。
這週的定番果然還是焦慮以及失敗經驗被召喚,不過透過重新檢視這些經驗,似乎又讓自己稍微從這些困擾當時自己的記憶中被釋放,例如重追《排球少年》第二季,主角們好幾次太過著急撞到彼此,讓我想到大學打系排時撞過一次學姊,還因此留下至今還會復發的舊傷,當時我超級無敵愧疚。不過現在回想好像根本就不用那麼愧疚,也許是我太害怕失敗了,上次也看到類似研究說台灣人怕失敗的程度超高,相較之下看著少年們不斷犯錯就覺得好舒壓。
另外第二季新經理是個小時候話劇只演過村人 B 等不起眼角色的人,也沒有什麼值得投入熱情之處,猶豫時甚至還被母親說:「抱著半吊子的心態,加入認真奮鬥的人中,是很失禮的。」這個部分最後當然是被熱血笨蛋主角們感動了,但最讓我有共鳴的是同為球經的學姐說,她自己一開始也不是非常喜歡排球,有些東西是開始做之後慢慢喜歡上的。我覺得這個策略十分有道理,目前決定試試看。
這周開始買了咖啡廳月票,希望能將跟課變成習慣。看了 Huli 說的跳關,我本來還打算在這篇放用 curl 解 HTTP-challenge 和這四週的挑戰題,不過目前繼續往前進才是好的策略。就這樣啦,我要進入第六週了,掰掰。
安全(secure)
。localhost
是指「這台電腦」的主機名稱。而 127.0.0.1
則是 localhost 預設的 IP 位置。GET
可以得到資訊, POST
則是傳送資料(和 PATCH 不同, POST 會覆蓋舊的資料)。Request URL
放目標網址。Request Method
放 HTTP Method。Status Code
表示連接狀態。Application Programming Interface
,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。這個販賣機就是 API
。你和廚房可以透過販賣機溝通(使用 API),你開店的時候別人也可以從販賣機點餐(提供 API)。GET
請求資料、 DELETE
刪除資料、POST
新增資料、PATCH
修改資料。你知道基本的 HTTP statud code,像是 200、301、400、404、500
status code 分成幾種:
# 後話
好啦我先去趕進度了,看大家在討論切版切版卻不知道是什麼,覺得好孤單。看大家慢慢開始想最後幾週可以做的東西,目前的想法是想幫 社團 架一個網站~
程式導師計畫第四期從 6/12 開始,到今天已經第七週了,此篇「第幾週」並不是根據實際時間,而是以 課綱 為主。這篇同時作為第五週的作業,整理加入計畫後至今的心得。
內含ㄉ技能
每週ㄉ速記
[DAY4] 初學Command Line
[DAY8] 初學 Git (上)
[DAY9] 初學 Git (下)
去年大學畢業終於被體制拋出,因為興趣太廣泛不知道要選哪個,乾脆毫無規劃的隻身一人到台南,以為隨便找份工作,過了一年什麼都不做,就會忽然知道自己要做什麼了。結論是我果然還太天真了,不只工作難找,畢業滿一年的今天,除了每天都有甜爆的美食,我依然是那個沒有方向的自己。
前幾天接到很久沒聯絡的學妹電話,說她找到了一份還不錯的工作,並問我在幹嘛。
「目前沒有工作啊。」我小海豹化。
然後不意外的被唸——都 23 歲,了是時候該為未來做打算了吧。
「……啊,但是我在學程式設計ㄛ。」
『三小?你不是心理系的嗎?』對欸,我都忘記這個設定了。
關於為什麼要學程式,這半年的契機太羞恥了請允許我先跳過。不過其實也不是最近裝水時才突發奇想,退一萬步也是高中裝水想到,早在升大學時就有想過填資工系了,最後因為いろいろ原因沒去。(請放過我留一些下次寫心得時可以講的故事 XD)
總之,這半年決定要學程式設計後,又跌跌撞撞了一段路,加上求職和就業等失敗經驗,送出履歷前大概是我對自己未來最絕望了時刻。就連履歷送出去前,都被幫忙看履歷的朋友看雖。當初想如果沒上我就試試看北科大的 Coding365 ,再不行就乖乖考個研究所好了,反正以我現在的能力出社會只會被打爆。終於,在一年內被拒絕無數次之後,居然收到 Huli 的信說我進了???
在快樂的像是(至少是我想像中的)蜜月期劈哩啪啦註冊完所有帳號後,用心發了自我介紹以及和同學社交(但又不能積極到被發現是怪咖qq),我馬上面臨第一個困難——如何上線上課程。
這個問題聽起來很白癡,不就跟到學校上課一樣嗎?但環境不同真的差超多,而且想偷懶的時候只要按暫停就可以去摸魚,超級考驗自制力的。尤其 CML 和 Git 雖然大家都說很帥很像駭客,不過實際上就是成就感超低的啊。這個問題到我將速度調快到一點五倍才暫時被改善(所以後來直播時是我第一次聽 Huli 用一倍速說話XD)。最嚴重的問題是,我該如何做筆記和寫每日進度?爬前幾屆同學的筆記,這已經不只是程式設計的計畫了,根本是筆記整理和幹話訓練班吧(???)
欸爆字數了,第二週心得待續好ㄌ
git pull <欲抓連結>
可將網路上的資料抓下來,與之相反的 git pull <branch>
可將資料傳上去。pull request
可於上傳資料後,打開 GitHub 最上方就可以送出了。[Week2] 給自己看的 JavaScript 筆記 -運算
[Week2] 給自己看的 JavaScript 筆記-變數
[Week2] 給自己看的 JavaScript 筆記 - 迴圈、函式、其他觀念
延續上一週,到底要如何寫出好看的筆記,最好是可以被助教勾選「值得參考」的呢?我花了超多時間想把筆記做得精美,寫了幾篇後發現 CP 值爆低,而且老師和助教都開始呼籲大家不要只顧著做筆記,推進進度比較重要。因此開始告訴自己 先求有再求好 ,雖然快了一點但效果還是有點差,這週給自己「幾小時挑戰」然後發現還是都超過了,因此之後幾週的策略就改成 沒有筆記也沒關係。
雖然立志待業中所以每天要學 8 小時,但每天至少有兩三個小時花在自我懷疑和逃避。Git 對我來說是全新的東西,在還沒看作業說明前,我完全不知道還要追多遠才能跟上大家的進度,因此焦慮又更深了,「我根本就不適合程式設計,放棄好了,才剛開始就被這種挫折打敗的我,根本就不喜歡寫程式吧,我只是碰到問題就逃避的廢物——」之類的鬼打牆不斷消耗自己,又更無法學習了。
另一個很焦慮的點是,現在社會的傳統想像中人們應該要超級精通一個東西,但如果每個人的興趣像遊戲的技能點,我明顯就是點的很分散的那種人,因此好擔心對程式的熱忱不如我想像中高,甚至沒有到達客觀標準。
這個選擇真的是對的嗎?
然後我想到前幾天朋友告訴我他唸超難畢業又前途模糊的研究所,並不是因為「想要成為什麼樣的人」,而是「想看看可以走到哪裡」。這個想法讓我感到舒緩一點,也許就像平板撐一樣,再多堅持幾秒,因為我很想知道,跨過這些障礙之後,我能看到怎麼樣不同的風景。
我想看看那邊的風景。
練習 LIOJ 時有一題卡了好幾天,用了各種方法都卡住,才決定在 spectrum 上提問。後來發現只是錯一個字母 orz 後來在每日進度吐槽這件事, JAS0N 同學特別來向我澄清他其實有給我線索,才發現原來我看起來在怪他 qq 但其實大部分在和別人聊天的過程(尤其是訊息聊天),我都在擔心「要如何讓對方覺得跟我說話/幫我解答是值得的」。所以在此特別再感謝一次不知道會不會看到的 JAS0N 和 Huli 大大(合十)
回傳
output,數學中的 f(x)=y
中的 y 就是回傳值。在 Javascript 中用 return 表示,如果沒有 return 則預設回傳值為 undefined
。function print(a) {
return a;
}
print('hello')
// 會回傳 hello ,但不會顯示任何東西
console.log(print('hello'))
// 印出 hello
相對的, console.log()
只是單純將內容 輸出
。
function print(a) {
console.log(a);
}
print('hello')
// 印出 hello
console.log(print('hello'))
// undefined
截至這週為止,我都只落後一兩天。一開始的契機可能只是腰痛到站不好。醫生說不要搬重物,但我忽然發現缺乏運動的我的身體,本身就是重物 QQ 導致我只能在床上工作,而且變得好嗜睡,又因為連續一週沒有休息,太緊繃往回彈進度就推得很慢,有些日子甚至只學三四個小時。進度一落後壓力就變大,根據壓力曲線,壓力大到一個程度效率會降低,如此形成惡性循環。
沒有推進太多進度得時候,我也就不趕寫太多每日心得。後來發現拖延是太想要完美,既然這樣那就乾脆就先捨棄一部份的完美繼續往前好了——明明前幾個禮拜就有覺悟了,要實踐果然好難。
這週起大家陸陸續續開始約實體聚會了,因為在台南無法參加,我也開了兩次螢幕共享,效果都還不錯。後來 zangwang 同學傳訊息跟我說,《注意力協定》一書中提到類似的效果,這有點像是大聲說「我要來努力了!」,之後如果跟大家說「抱歉啊我是個沒有毅力的人,先去玩一下動物森友會。」其實是有壓力的,雖然也不是完全不能這樣做(畢竟人生是自己的),但羞恥心之類的會讓偷懶這件事要付出的代價變高(而且比起之後進度落後,這個代價是要先付出的會比較有感覺),因此可以用契約提高專注力。
另外好像不小心習慣偷懶了,直接進入倦怠期,尤其越來越沒有幹勁寫每日進度(要怎樣一直產出高質量心的辣——於是我又落入完美主義拖延的困境)。在心得上提到這件事, Wozski 同學(嗚嗚嗚同學人都好好)就來私訊我說,不妨將學習當作習慣,如果今天很不想做的話就從五分鐘開始,再慢慢延長,重點不是今天做了多好(甚至多好),而是讓自己習慣每天做這件事情。我之前一直以為運動或被單字才有習慣可言,沒想到光是持續跟完課就是一個毅力上的挑戰了。
str.length
或 arr.length
:回傳字串或陣列的長度。Number(str)
:將字串轉成數字。arr.join()
:將東西加入陣列中。[Week4] JS 實作串接 API(一)
[Week4] JS 實作串接 API(二)
[Week4] JS 實作串接 API(三)
因為之前偷偷學過一點 Python,這週終於又回到自己完全沒概念的東西了,懷抱忐忑的心情點開課程影片——咦,居然那麼好懂嗎?從傳紙條範例開始,每個概念幾乎都不太難,課綱為何要為了這種東西花一整週——然後才在作業找到答案:廢話,那是因為你還沒開始實作啊!
寫作業的過程艱難到我認為必須將其記錄成筆記,不然幾乎每一題都要先拆解答再看,超心虛的。比如說:為何 request 要放 (err, res, body)
?又或者 post 之後回傳的 body 是什麼?這些感覺好像是約定俗成的東西,都必須一個個去重新摸索。因為幾乎是看著解答寫,讓我超級超級心虛,還好之後有 HTTP-challenge 才比較扎實。
這週的定番果然還是焦慮以及失敗經驗被召喚,不過透過重新檢視這些經驗,似乎又讓自己稍微從這些困擾當時自己的記憶中被釋放,例如重追《排球少年》第二季,主角們好幾次太過著急撞到彼此,讓我想到大學打系排時撞過一次學姊,還因此留下至今還會復發的舊傷,當時我超級無敵愧疚。不過現在回想好像根本就不用那麼愧疚,也許是我太害怕失敗了,上次也看到類似研究說台灣人怕失敗的程度超高,相較之下看著少年們不斷犯錯就覺得好舒壓。
另外第二季新經理是個小時候話劇只演過村人 B 等不起眼角色的人,也沒有什麼值得投入熱情之處,猶豫時甚至還被母親說:「抱著半吊子的心態,加入認真奮鬥的人中,是很失禮的。」這個部分最後當然是被熱血笨蛋主角們感動了,但最讓我有共鳴的是同為球經的學姐說,她自己一開始也不是非常喜歡排球,有些東西是開始做之後慢慢喜歡上的。我覺得這個策略十分有道理,目前決定試試看。
這周開始買了咖啡廳月票,希望能將跟課變成習慣。看了 Huli 說的跳關,我本來還打算在這篇放用 curl 解 HTTP-challenge 和這四週的挑戰題,不過目前繼續往前進才是好的策略。就這樣啦,我要進入第六週了,掰掰。
安全(secure)
。localhost
是指「這台電腦」的主機名稱。而 127.0.0.1
則是 localhost 預設的 IP 位置。GET
可以得到資訊, POST
則是傳送資料(和 PATCH 不同, POST 會覆蓋舊的資料)。Request URL
放目標網址。Request Method
放 HTTP Method。Status Code
表示連接狀態。Application Programming Interface
,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。這個販賣機就是 API
。你和廚房可以透過販賣機溝通(使用 API),你開店的時候別人也可以從販賣機點餐(提供 API)。GET
請求資料、 DELETE
刪除資料、POST
新增資料、PATCH
修改資料。你知道基本的 HTTP statud code,像是 200、301、400、404、500
status code 分成幾種:
# 後話
好啦我先去趕進度了,看大家在討論切版切版卻不知道是什麼,覺得好孤單。看大家慢慢開始想最後幾週可以做的東西,目前的想法是想幫 社團 架一個網站~
本篇從 程式導師作業第四週作業 出發學習 JS 串接 API 實作。
前兩篇記錄於此:
[Week4] JS 實作串接 API(一)
[Week4] JS 實作串接 API(二)
你的好麻吉小立是一個很愛到處旅遊的人,在前一陣子才靠著便宜的 bug 機票以及特價的商務艙玩遍了許多地方。不過小立一直有個困擾,那就是他希望了解更多跟國家有關的知識,因此他來請你幫忙寫一個搜尋國家資訊的小程式。
這個程式很簡單,只要輸入國家的英文名字,就能夠查詢符合的國家的資訊,會輸出以下幾項:
- 國家名稱
- 首都
- 使用的貨幣名稱
- 電話國碼
const request = require('request');
const args = process.argv;
const API_ENDPOINT = 'https://restcountries.eu/rest/v2';
const name = args[2];
if (!name) {
return console.log('請輸入國家名稱');
}
request(`${API_ENDPOINT}/name/${name}`, (err, res, body) => {
if (err) {
return console.log('抓取失敗', err);
}
const data = JSON.parse(body);
if (data.status === 404) {
return console.log('找不到國家資訊')
}
for (let i = 0; i < data.length; i++) {
console.log('============')
console.log('國家:' + data[i].name);
console.log('首都:' + data[i].capital);
console.log('貨幣:' + data[i].currencies[0].code);
console.log('國碼:' + data[i].callingCodes[0]);
}
})
data.status
可回傳 HTTP 的狀態碼。(因為 API 中有個 key 就叫 status )。const data
只會在 request 函式中作用,因此要將最後印下來的作業於其中完成,否則會無法找到 data 。
參考資料:
【第十五天】突發任務:Twitch API
之前幫秋秋鞋做完那個小程式以後,你會寫程式的消息似乎就傳開了,有一位 Twitch 平台實況主果凍跑來聯繫你,想請你幫忙做個東西。
事情是這樣的,他原本是 LOL 的玩家,但因為某些原因帳號被 ban 掉了,為了維持實況的熱度,需要去找其他遊戲來玩,可是他又不知道哪些遊戲比較熱門,會有比較多人觀看。
因此,他寫請你寫一個小程式,能夠去撈取 Twitch 上面受歡迎的遊戲,他就能夠參考這個列表來決定要實況哪個遊戲。
由於你偶爾也會看他的實況,所以你欣然接受了這個挑戰,準備來串串看真實世界的 API。
請參考 Twitch API v5 的文件,寫一隻程式去呼叫 Twitch API,並拿到「最受歡迎的遊戲列表(Get Top Games)」,並依序印出目前觀看人數跟遊戲名稱。
在這個作業中,你必須自己看懂 Twitch API 的文件,知道怎麼去申請一個 Application 拿到 ClientID,並且在 API 文件當中找到對的那一個 API(Get Top Games),而且務必記得要在 request header 中帶上 ClientID 跟另一個參數 Accept,值是:application/vnd.twitchtv.v5+json。
還有一件事情要提醒大家,Twitch API 有兩個版本,一個是最新版(New Twitch API,代號 Helix),一個是舊版的(Twitch API v5,代號 kraken),我們這次要串接的是舊版的,不要搞錯版本囉。
可參考文章:【第十五天】突發任務:Twitch API
request({
method: 'GET',
url: <url>,
headers: {
'參數': 值,
}
}, function(error,response,body){
// 其他內容
})
request ({
method: 'GET',
url: apiUrl,
headers: {
'Client-ID': clientID,
'Accept': 'application/vnd.twitchtv.v5+json',
}
}, (err, res, body) => {
console.log(body)
})
整理得到的 JSON 後得到:
此時要做的事情:
參考資料:MDN
for...of
可將陣列的值依序取出:
for (variable of iterable) {
//statements
}
結果 ESlint 不建議 for...of
,哈哈哈哈哈。
還是想用可參考: javascript - ESLint提示“無限制的語法迭代器/生成器需要regenerator-runtime”
const request = require('request');
const apiUrl = 'https://api.twitch.tv/kraken/games/top';
const clientID = 不給你複製我的ID;
request({
method: 'GET',
url: apiUrl,
headers: {
'Client-ID': clientID,
Accept: 'application/vnd.twitchtv.v5+json',
},
}, (err, res, body) => {
if (err) return console.log('抓取失敗');
const data = JSON.parse(body).top;
for (const i of data) {
console.log(i.viewers, i.game.name);
}
return true;
});
沒什麼關聯但記錄一下:curl 的用法
參考資料:
zoeaeen13 同學的作業
API 到底是什麼? 用白話文帶你認識
HTTP 狀態碼
API 全名為 Application Programming Interface
,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。
舉例來說好了,今天你進到一家拉麵店,要如何和廚房說你要什麼品項?因為你已經來過很多次了,所以走到拉麵販賣機前點餐,而 這個販賣機就是 API
。你和廚房可以透過販賣機溝通(使用 API),你開店的時候別人也可以從販賣機點餐(提供 API)。
讓我們整理一下:
status code
表示一個 HTTP 要求是否已經被完成,代表 API 層的執行狀態。
status code 大約分成幾種:
以下列出課程沒提到的 HTTP status code:
201 Created
:請求成功且新的資源成功被創建,通常用於 POST 或一些 PUT 請求後的回應。
400 Bad Request
:此回應意味伺服器因為收到無效語法,而無法理解請求。
403 Forbidden
:用戶端並無訪問權限,例如未被授權,所以伺服器拒絕給予應有的回應。不同於 401,伺服端知道用戶端的身份。
參考資料:
[Week4] JS 實作串接 API(一)
[Week4] JS 實作串接 API(二)
Base URL: https://v61265.com
說明 | Method | path | 參數 | 範例 |
---|---|---|---|---|
獲取所有餐廳資料 | GET | /restaurants | _limit:限制回傳資料數量 | /restaurants?_limit=5 |
獲取單一餐廳資料 | GET | /restaurants/:id | 無 | /restaurant/10 |
新增餐廳 | POST | /restaurants | name: 店名 | 無 |
刪除餐廳 | DELETE | /reataurants/:id | 無 | 無 |
更改餐廳資訊 | PATCH | /restaurants/:id | name: 店名 | 無 |
const request = require('request');
request('https://v61265.com/reataurants', (err, res, body) => {
// 這裡是你要的內容
});
const request = require('request')
request(`https://v61265.com/restaurants/${id}`, (err, res, body) => {
//這裡是你要的內容
});
const request = require('request');
request.post( {
url: 'https://v61265.com/restaurants',
form: {
name //新餐廳名稱
},
}, (err, res, body) => {
//這裡是你想要的內容
});
const request = require('request');
request.delete(`https://v61265.com/restaurants/${id}`, (err, res, body) => {
//這裡可以加其他東西
});
const request = require('request');
request.patch( {
url: `https://v61265.com/restaurants/${id}`,
form: { name }
}, (err,res,body) => {
//可以加其他東西
});
最後附上有時間可以回頭看的: [原來後端要知道] 如何寫 API 文件? #Apiary #API Blueprint # Markdown
]]>本篇從 程式導師作業第四週作業 出發學習 JS 串接 API 實作。
前兩篇記錄於此:
[Week4] JS 實作串接 API(一)
[Week4] JS 實作串接 API(二)
你的好麻吉小立是一個很愛到處旅遊的人,在前一陣子才靠著便宜的 bug 機票以及特價的商務艙玩遍了許多地方。不過小立一直有個困擾,那就是他希望了解更多跟國家有關的知識,因此他來請你幫忙寫一個搜尋國家資訊的小程式。
這個程式很簡單,只要輸入國家的英文名字,就能夠查詢符合的國家的資訊,會輸出以下幾項:
- 國家名稱
- 首都
- 使用的貨幣名稱
- 電話國碼
const request = require('request');
const args = process.argv;
const API_ENDPOINT = 'https://restcountries.eu/rest/v2';
const name = args[2];
if (!name) {
return console.log('請輸入國家名稱');
}
request(`${API_ENDPOINT}/name/${name}`, (err, res, body) => {
if (err) {
return console.log('抓取失敗', err);
}
const data = JSON.parse(body);
if (data.status === 404) {
return console.log('找不到國家資訊')
}
for (let i = 0; i < data.length; i++) {
console.log('============')
console.log('國家:' + data[i].name);
console.log('首都:' + data[i].capital);
console.log('貨幣:' + data[i].currencies[0].code);
console.log('國碼:' + data[i].callingCodes[0]);
}
})
data.status
可回傳 HTTP 的狀態碼。(因為 API 中有個 key 就叫 status )。const data
只會在 request 函式中作用,因此要將最後印下來的作業於其中完成,否則會無法找到 data 。
參考資料:
【第十五天】突發任務:Twitch API
之前幫秋秋鞋做完那個小程式以後,你會寫程式的消息似乎就傳開了,有一位 Twitch 平台實況主果凍跑來聯繫你,想請你幫忙做個東西。
事情是這樣的,他原本是 LOL 的玩家,但因為某些原因帳號被 ban 掉了,為了維持實況的熱度,需要去找其他遊戲來玩,可是他又不知道哪些遊戲比較熱門,會有比較多人觀看。
因此,他寫請你寫一個小程式,能夠去撈取 Twitch 上面受歡迎的遊戲,他就能夠參考這個列表來決定要實況哪個遊戲。
由於你偶爾也會看他的實況,所以你欣然接受了這個挑戰,準備來串串看真實世界的 API。
請參考 Twitch API v5 的文件,寫一隻程式去呼叫 Twitch API,並拿到「最受歡迎的遊戲列表(Get Top Games)」,並依序印出目前觀看人數跟遊戲名稱。
在這個作業中,你必須自己看懂 Twitch API 的文件,知道怎麼去申請一個 Application 拿到 ClientID,並且在 API 文件當中找到對的那一個 API(Get Top Games),而且務必記得要在 request header 中帶上 ClientID 跟另一個參數 Accept,值是:application/vnd.twitchtv.v5+json。
還有一件事情要提醒大家,Twitch API 有兩個版本,一個是最新版(New Twitch API,代號 Helix),一個是舊版的(Twitch API v5,代號 kraken),我們這次要串接的是舊版的,不要搞錯版本囉。
可參考文章:【第十五天】突發任務:Twitch API
request({
method: 'GET',
url: <url>,
headers: {
'參數': 值,
}
}, function(error,response,body){
// 其他內容
})
request ({
method: 'GET',
url: apiUrl,
headers: {
'Client-ID': clientID,
'Accept': 'application/vnd.twitchtv.v5+json',
}
}, (err, res, body) => {
console.log(body)
})
整理得到的 JSON 後得到:
此時要做的事情:
參考資料:MDN
for...of
可將陣列的值依序取出:
for (variable of iterable) {
//statements
}
結果 ESlint 不建議 for...of
,哈哈哈哈哈。
還是想用可參考: javascript - ESLint提示“無限制的語法迭代器/生成器需要regenerator-runtime”
const request = require('request');
const apiUrl = 'https://api.twitch.tv/kraken/games/top';
const clientID = 不給你複製我的ID;
request({
method: 'GET',
url: apiUrl,
headers: {
'Client-ID': clientID,
Accept: 'application/vnd.twitchtv.v5+json',
},
}, (err, res, body) => {
if (err) return console.log('抓取失敗');
const data = JSON.parse(body).top;
for (const i of data) {
console.log(i.viewers, i.game.name);
}
return true;
});
沒什麼關聯但記錄一下:curl 的用法
參考資料:
zoeaeen13 同學的作業
API 到底是什麼? 用白話文帶你認識
HTTP 狀態碼
API 全名為 Application Programming Interface
,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。
舉例來說好了,今天你進到一家拉麵店,要如何和廚房說你要什麼品項?因為你已經來過很多次了,所以走到拉麵販賣機前點餐,而 這個販賣機就是 API
。你和廚房可以透過販賣機溝通(使用 API),你開店的時候別人也可以從販賣機點餐(提供 API)。
讓我們整理一下:
status code
表示一個 HTTP 要求是否已經被完成,代表 API 層的執行狀態。
status code 大約分成幾種:
以下列出課程沒提到的 HTTP status code:
201 Created
:請求成功且新的資源成功被創建,通常用於 POST 或一些 PUT 請求後的回應。
400 Bad Request
:此回應意味伺服器因為收到無效語法,而無法理解請求。
403 Forbidden
:用戶端並無訪問權限,例如未被授權,所以伺服器拒絕給予應有的回應。不同於 401,伺服端知道用戶端的身份。
參考資料:
[Week4] JS 實作串接 API(一)
[Week4] JS 實作串接 API(二)
Base URL: https://v61265.com
說明 | Method | path | 參數 | 範例 |
---|---|---|---|---|
獲取所有餐廳資料 | GET | /restaurants | _limit:限制回傳資料數量 | /restaurants?_limit=5 |
獲取單一餐廳資料 | GET | /restaurants/:id | 無 | /restaurant/10 |
新增餐廳 | POST | /restaurants | name: 店名 | 無 |
刪除餐廳 | DELETE | /reataurants/:id | 無 | 無 |
更改餐廳資訊 | PATCH | /restaurants/:id | name: 店名 | 無 |
const request = require('request');
request('https://v61265.com/reataurants', (err, res, body) => {
// 這裡是你要的內容
});
const request = require('request')
request(`https://v61265.com/restaurants/${id}`, (err, res, body) => {
//這裡是你要的內容
});
const request = require('request');
request.post( {
url: 'https://v61265.com/restaurants',
form: {
name //新餐廳名稱
},
}, (err, res, body) => {
//這裡是你想要的內容
});
const request = require('request');
request.delete(`https://v61265.com/restaurants/${id}`, (err, res, body) => {
//這裡可以加其他東西
});
const request = require('request');
request.patch( {
url: `https://v61265.com/restaurants/${id}`,
form: { name }
}, (err,res,body) => {
//可以加其他東西
});
最後附上有時間可以回頭看的: [原來後端要知道] 如何寫 API 文件? #Apiary #API Blueprint # Markdown
]]>原本以為上次就已經是最後一次幫忙,沒想到秋秋鞋還是又跑來找你了。他說他想要更多功能,他想把這整個書籍資料庫當作自己的來用,必須能夠顯示前 20 本書的資料、刪除、新增以及修改書本,這樣他就可以管理自己的書籍了。
(跟 hw1 不同,之前是 10 本,這次要顯示 20 本)
雖然你很想問他說為什麼不用 Excel 就好,但你問不出口,再加上你最近剛學程式需要練習的機會,於是你就答應了。
請閱讀開頭給的 API 文件並串接,用 node.js 寫出一個程式並接受參數,輸出相對應的結果,範例如下:node hw2.js list // 印出前二十本書的 id 與書名 node hw2.js read 1 // 輸出 id 為 1 的書籍 node hw2.js delete 1 // 刪除 id 為 1 的書籍 node hw2.js create "I love coding" // 新增一本名為 I love coding 的書 node hw2.js update 1 "new name" // 更新 id 為 1 的書名為 new name
參考資料
使用 process
可以取得指令的參數。 process.argv
可以得到啟動 Node.js 時 command line 的參數。
例如若 hw1.js 中有 console.log(process.argv)
,再於 command line 輸入 node hw1.js 123
,會得到一個陣列,分別指向 node 、 hw1 和 123 。
找到第二三個參數
const request = require('request');
const apiUrl = 'https://lidemy-book-store.herokuapp.com';
const action = process.argv[2]
const param = process.argv[3]
用 switch 區分功能
switch (action) {
case 'list':
listBooks();
break;
case 'read':
readBooks(param);
break;
case 'delete':
deleteBooks(param);
break;
case 'create':
createBooks(param);
break;
case 'update':
updateBooks(param, process.argv[4]);
break;
default:
console.log('Available commands: list, read, delete, create and update');
}
list 功能
這個上一題練習過了:
function listBooks() {
request(`${apiUrl}/books?_limit=20`, (err, res, body) => {
if (err) return console.log('抓取失敗', err);
let data
try {
data = JSON.parse(body)
} catch (e) {
return console.log(e);
}
for (let i = 0; i < data.length; i += 1) {
console.log(data[i].id, data[i].name);
}
});
}
read 功能
從上面的函式修改:
function readBooks(id) {
request(`${apiUrl}/books/${id}`, (err, res, body) => {
if (err) return console.log('抓取失敗', err);
const data = JSON.parse(body);
return console.log(data)
});
}
參考資料
request.delete()
可以刪除資料,例如:
request.delete(
'https://reqres.in/api/users/2',
function (error, response, body) {
console.log(response.statusCode)
});
上方因為資料已經被刪掉了,因此會印出 204 。
這題中,刪除的語法是這樣:
function deleteBooks(id) {
request.delete(`${apiUrl}/books/${id}`, (err) => {
if (err) return console.log('刪除失敗');
return console.log('刪除成功');
});
}
刪除完後, err
照理來說會變成 null (false) 。
request.post()
可以增加資料,最常見的形式為:
request.post(
{url,
form: {key:'value'}
},
function(err,httpResponse,body){ /* ... */ })
這題中,新增的語法如下:
function createBooks(name) {
request.post({url: `${apiUrl}/books`, form: {name,}}, (err) => {
if (err) return console.log('新增失敗', err);
return console.log('新增成功')
});
}
request.patch()
則可以修改資料,使用形式為:
request.patch({
url,
form: {
//欲修改內容
},
}, function)
這題中寫法如下:
function updateBooks(id, newName) {
request.patch({url: `${apiUrl}/books/${id}`, form: {newName}}, (err) => {
if (err) return console.log('更新失敗', err);
return console.log('更新成功');
});
}
]]>原本以為上次就已經是最後一次幫忙,沒想到秋秋鞋還是又跑來找你了。他說他想要更多功能,他想把這整個書籍資料庫當作自己的來用,必須能夠顯示前 20 本書的資料、刪除、新增以及修改書本,這樣他就可以管理自己的書籍了。
(跟 hw1 不同,之前是 10 本,這次要顯示 20 本)
雖然你很想問他說為什麼不用 Excel 就好,但你問不出口,再加上你最近剛學程式需要練習的機會,於是你就答應了。
請閱讀開頭給的 API 文件並串接,用 node.js 寫出一個程式並接受參數,輸出相對應的結果,範例如下:node hw2.js list // 印出前二十本書的 id 與書名 node hw2.js read 1 // 輸出 id 為 1 的書籍 node hw2.js delete 1 // 刪除 id 為 1 的書籍 node hw2.js create "I love coding" // 新增一本名為 I love coding 的書 node hw2.js update 1 "new name" // 更新 id 為 1 的書名為 new name
參考資料
使用 process
可以取得指令的參數。 process.argv
可以得到啟動 Node.js 時 command line 的參數。
例如若 hw1.js 中有 console.log(process.argv)
,再於 command line 輸入 node hw1.js 123
,會得到一個陣列,分別指向 node 、 hw1 和 123 。
找到第二三個參數
const request = require('request');
const apiUrl = 'https://lidemy-book-store.herokuapp.com';
const action = process.argv[2]
const param = process.argv[3]
用 switch 區分功能
switch (action) {
case 'list':
listBooks();
break;
case 'read':
readBooks(param);
break;
case 'delete':
deleteBooks(param);
break;
case 'create':
createBooks(param);
break;
case 'update':
updateBooks(param, process.argv[4]);
break;
default:
console.log('Available commands: list, read, delete, create and update');
}
list 功能
這個上一題練習過了:
function listBooks() {
request(`${apiUrl}/books?_limit=20`, (err, res, body) => {
if (err) return console.log('抓取失敗', err);
let data
try {
data = JSON.parse(body)
} catch (e) {
return console.log(e);
}
for (let i = 0; i < data.length; i += 1) {
console.log(data[i].id, data[i].name);
}
});
}
read 功能
從上面的函式修改:
function readBooks(id) {
request(`${apiUrl}/books/${id}`, (err, res, body) => {
if (err) return console.log('抓取失敗', err);
const data = JSON.parse(body);
return console.log(data)
});
}
參考資料
request.delete()
可以刪除資料,例如:
request.delete(
'https://reqres.in/api/users/2',
function (error, response, body) {
console.log(response.statusCode)
});
上方因為資料已經被刪掉了,因此會印出 204 。
這題中,刪除的語法是這樣:
function deleteBooks(id) {
request.delete(`${apiUrl}/books/${id}`, (err) => {
if (err) return console.log('刪除失敗');
return console.log('刪除成功');
});
}
刪除完後, err
照理來說會變成 null (false) 。
request.post()
可以增加資料,最常見的形式為:
request.post(
{url,
form: {key:'value'}
},
function(err,httpResponse,body){ /* ... */ })
這題中,新增的語法如下:
function createBooks(name) {
request.post({url: `${apiUrl}/books`, form: {name,}}, (err) => {
if (err) return console.log('新增失敗', err);
return console.log('新增成功')
});
}
request.patch()
則可以修改資料,使用形式為:
request.patch({
url,
form: {
//欲修改內容
},
}, function)
這題中寫法如下:
function updateBooks(id, newName) {
request.patch({url: `${apiUrl}/books/${id}`, form: {newName}}, (err) => {
if (err) return console.log('更新失敗', err);
return console.log('更新成功');
});
}
]]>
緩慢看完 [NET101] 網路基礎概論(搭配 JS 實作練習) 後覺得不太扎實,果然一寫到作業就掛掉了。這幾篇會用 第四週作業 來整理 JavaScript 實際串接 API 時會用到的語法和觀念。
API 全名為 Application Programming Interface,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。想知道更多可以看 從拉麵店的販賣機理解什麼是 API
API 文件
Base URL: https://lidemy-book-store.herokuapp.com
說明 | Method | path | 參數 | 範例 |
---|---|---|---|---|
獲取所有書籍 | GET | /books | <_limit:限制回傳資料數量 | /books?<_limit=5 |
獲取單一書籍 | GET | /books/:id | 無 | /books/10 |
新增書籍 | POST | /books | name:書名 | 無 |
刪除書籍 | DELETE | /books/:id | 無 | 無 |
更改書籍資訊 | PATCH | /books/:id | name: 書名 | 無 |
有一天,住你隔壁的鄰居秋秋鞋跑來按門鈴,說有事想要找你討論,他最近在做一個知識型的 YouTube 頻道,可是快要沒有靈感了。
這時,他想到一個好主意!他只要能夠看到書店提供的書籍相關資訊,就可以從中汲取靈感,之後就可以發想相關題材,頻道就不會一直不更新了。
身為秋秋鞋的好朋友,這個重責大任當然就交給你了!
請閱讀開頭給的 API 文件並串接,用 node.js 寫出一個程式,執行後會在 console 列出前十本書籍的 id 以及書名。
順帶一提,叫做秋秋鞋是因為他很喜歡秋天。
首先,我們要下載 request 的 npm:
npm install requst --save
request 的基本用法為:
request(input, init)
通常 input 為目標網址,例如官網上提供的範本為:
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('head:', response.headers);
console.log('body:', body);
});
剛抓下來的資料會以 JSON 呈現。 JSON 全名 JavaScript Object Notation
,和 JS 中物件很像,只是不管是 key 還是 value 都要用雙引號 "
框起來,而整體是個字串。
JSON 轉物件
參考資料: MDN
JSON.parse()
可以將 JSON 轉成物件,第一個參數放欲轉換的 JSON ,後面可以指示如何選擇結果。例如
const json = {
"name" : "jason",
"age" : "23",
"phone" : "0922"
}
// 排除 age
const obj = JSON.parse(json, (key, value) => {
if (key === 'age') return undefined
return value
})
//{name: "jason", phone: "0922"}
物件轉 JSON
參考資料: MDN
與此相對的, JSON.stringify()
可將物件轉換成 JSON 格式。第一個參數一樣放物件,後面則是結果的轉換標準。
const obj = {
name: "jason",
age: "23",
phone: "0922"
}
// 排除 age
const json = JSON.stringify(obj, (key, value) => {
if (key === 'age') return undefined
return value
})
//{"name": "jason", "phone": "0922"} <= 這是字串
參考資料: MDN
try...catch
可用於程式中可能出錯處,主動捕捉錯誤,並防止整個程式因錯誤而停擺。用法如下:
try {
// 預期可能會發生錯誤的程式碼
} catch (e) {
// try 區塊有拋出錯誤時,則執行這裡的程式碼
} finally {
// finally 區塊的程式碼一定會在最後被執行
// 可以省略 finally 區塊
}
因為實在太菜了,直接看答案怎麼寫好了:
const request = require('request');
const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com';
request(`${API_ENDPOINT}/books?_limit=10`, (err, res, body) => {
if (err) {
return console.log('抓取失敗', err);
}
let data
try {
data = JSON.parse(body);
} catch(err) {
console.log(err);
return
}
for (let i = 0; i < data.length; i += 1) {
console.log(`${data[i].id} ${data[i].name}`);
}
})
筆記:
?
可以加參數,如上方 _limit=10
就是限制只抓取十筆。但似乎是照 id 抓而非出現順序?資料來源:
API 基礎
[第六週] API 基礎-
實際串接 API、資料格式: JSON、API Method 風格: RESTful
緩慢看完 [NET101] 網路基礎概論(搭配 JS 實作練習) 後覺得不太扎實,果然一寫到作業就掛掉了。這幾篇會用 第四週作業 來整理 JavaScript 實際串接 API 時會用到的語法和觀念。
API 全名為 Application Programming Interface,中文翻譯為「應用程式介面」。簡單來說就是交換資料的管道。想知道更多可以看 從拉麵店的販賣機理解什麼是 API
API 文件
Base URL: https://lidemy-book-store.herokuapp.com
說明 | Method | path | 參數 | 範例 |
---|---|---|---|---|
獲取所有書籍 | GET | /books | <_limit:限制回傳資料數量 | /books?<_limit=5 |
獲取單一書籍 | GET | /books/:id | 無 | /books/10 |
新增書籍 | POST | /books | name:書名 | 無 |
刪除書籍 | DELETE | /books/:id | 無 | 無 |
更改書籍資訊 | PATCH | /books/:id | name: 書名 | 無 |
有一天,住你隔壁的鄰居秋秋鞋跑來按門鈴,說有事想要找你討論,他最近在做一個知識型的 YouTube 頻道,可是快要沒有靈感了。
這時,他想到一個好主意!他只要能夠看到書店提供的書籍相關資訊,就可以從中汲取靈感,之後就可以發想相關題材,頻道就不會一直不更新了。
身為秋秋鞋的好朋友,這個重責大任當然就交給你了!
請閱讀開頭給的 API 文件並串接,用 node.js 寫出一個程式,執行後會在 console 列出前十本書籍的 id 以及書名。
順帶一提,叫做秋秋鞋是因為他很喜歡秋天。
首先,我們要下載 request 的 npm:
npm install requst --save
request 的基本用法為:
request(input, init)
通常 input 為目標網址,例如官網上提供的範本為:
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('head:', response.headers);
console.log('body:', body);
});
剛抓下來的資料會以 JSON 呈現。 JSON 全名 JavaScript Object Notation
,和 JS 中物件很像,只是不管是 key 還是 value 都要用雙引號 "
框起來,而整體是個字串。
JSON 轉物件
參考資料: MDN
JSON.parse()
可以將 JSON 轉成物件,第一個參數放欲轉換的 JSON ,後面可以指示如何選擇結果。例如
const json = {
"name" : "jason",
"age" : "23",
"phone" : "0922"
}
// 排除 age
const obj = JSON.parse(json, (key, value) => {
if (key === 'age') return undefined
return value
})
//{name: "jason", phone: "0922"}
物件轉 JSON
參考資料: MDN
與此相對的, JSON.stringify()
可將物件轉換成 JSON 格式。第一個參數一樣放物件,後面則是結果的轉換標準。
const obj = {
name: "jason",
age: "23",
phone: "0922"
}
// 排除 age
const json = JSON.stringify(obj, (key, value) => {
if (key === 'age') return undefined
return value
})
//{"name": "jason", "phone": "0922"} <= 這是字串
參考資料: MDN
try...catch
可用於程式中可能出錯處,主動捕捉錯誤,並防止整個程式因錯誤而停擺。用法如下:
try {
// 預期可能會發生錯誤的程式碼
} catch (e) {
// try 區塊有拋出錯誤時,則執行這裡的程式碼
} finally {
// finally 區塊的程式碼一定會在最後被執行
// 可以省略 finally 區塊
}
因為實在太菜了,直接看答案怎麼寫好了:
const request = require('request');
const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com';
request(`${API_ENDPOINT}/books?_limit=10`, (err, res, body) => {
if (err) {
return console.log('抓取失敗', err);
}
let data
try {
data = JSON.parse(body);
} catch(err) {
console.log(err);
return
}
for (let i = 0; i < data.length; i += 1) {
console.log(`${data[i].id} ${data[i].name}`);
}
})
筆記:
?
可以加參數,如上方 _limit=10
就是限制只抓取十筆。但似乎是照 id 抓而非出現順序?資料來源:
API 基礎
[第六週] API 基礎-
實際串接 API、資料格式: JSON、API Method 風格: RESTful
2020/07/08 看完影片更新,剩下 Babel 的部分睡醒再更新
ES6 是指 ECMAScript 第六版,於 2015 年發布,因此又稱 ES2015 。
ECMAScript
是一套程式碼的標準和規範, Javascript 就是依據此實作的,因此可直接使用於 JavaScipt 。
定義變數時,除了在前面加 var
以外,也可以使用 let
和 const
。
const
用於宣告 常數
,因此一旦宣告該常數的內容便無法更動。
相對的, let
和 ver
就可以改變。
const PI = 3.14
let a = 123
PI = 3.1415926 //會出錯
a =456 //沒有問題
此處的重複宣告是指不能改變 記憶體位置
,因此上方例子中的數字無法被改變。但若改變物件內容不會影響到記憶體位置,因此沒有問題。
作用域 (scope) 指的是變數的生存範圍。當你輸入 console.log(a)
時, JavaScript 會往上找是否有宣告過 a ,並回傳該處宣告的值。例如:
function hello(){
var a = 123
console.log(a) //123
}
console.log(a) //undefined,因為 function 在不同層, a 的作用域無法到此。
var
的作用域在函數內(若不是在函數內,就是整個文件內),但可能會產生變數範圍太大互相干擾出錯的狀況,例如:
for (var i = 0; i < 5; i++) {
console.log('*')
}
console.log(i) //5
因此 let
和 const
的範圍縮小到一個 block 中( {
和 }
包起來處),可以避免很多問題。
Template 是樣板的意思。Template Literals 用於字串拼接,不用再只用 +
和 ,
,而是用 ``
框出文字。這種方法可用於多行字串拼接:
let a = `hi
abc
www`
console.log(a)
//hi
//abc
//www
也可在其中使用 ${變數}
。例如:
let name = you
let hi = 'hi, ${you}'
console.log(hi) // 'hi, you'
提取陣列中的值時,最傳統的做法如下:
const arr = [1, 2, 3]
let one = arr[0]
let two = arr[1]
let three = arr[2]
也可以使用 Destructuring 的寫法:
const arr = [1, 2, 3, 4]
let [one, two, three] = arr
console.log(two) //2
同樣的也可以用在物件中:
const obj = {
a : 1,
b : 2,
c : 3,
}
// let {key} = obj ,就是 let obj.key = value
let {a, b, c} = obj
console.log(b) //2
知道規則之後,當然可以花式解構物件:
const obj = {
a : {
b : 'c'
}
}
let {a{b}} = obj
console.log(b) //c
當然也可以和函數合併
function des({a, b}) {
console.log(a)
}
des({a: 1, b: 2}) //1
展開運算子的寫法是在陣列或物件的名稱前方加上 ...
,以下為基本範例:
let arr1 = [1, 2, 3]
let arr2 = [4, arr1] // [4, [1, 2, 3]]
let arr3 = [4, ..arr] // [4, 1, 2, 3]
因為展開運算子就是把陣列展開,因此也可以和函式合併使用,例如:
function sum(a, b, c) {
return a + b + c
}
let arr = [1, 2, 3]
console.log(sum(...arr)) // 6
當然要展開物件也可以:
let obj1 = {a:1, b:2, c:3}
let obj2 = {...obj1, d:4}
console.log(obj2) // {a:1, b:2, c:3, d:4}
展開運算子也可以用於複製陣列和物件,但和直接指定不同,差別如下:
let arr1 = [1, 2, 3]
let arr2 = arr1 // [1, 2, 3]
let arr3 = [...arr1] // [1, 2, 3]
console.log(arr1 === arr2) // true,指向同個記憶體位置
console.log(arr1 === arr3) // fales ,因為記憶體位置不同
反向展開可以用於解構時,將剩餘的東西放進另一個陣列。例如:
let arr = [1, 2, 3, 4]
let [first, ...rest] = arr
console.log(rest) // [2, 3, 4]
let [one, ...test, 4] = arr // 會出現錯誤,因為只能至於最後。
當然也可以和函式合併使用:
function test(...number) {
return number
}
console.log(test(1, 2, 3)) // [1, 2, 3]
預設值可於函式或解構中使用,當沒輸入時,則會自動帶入該值。例如:
function test(a = 2, b) {
return [a, b]
}
console.log(test()) // [2, undefined]
let arr = [1, 2, 3]
let [a, b, c, d = 7] = arr
console.log(a, b, c, d) // 1 2 3 7
箭頭函式簡化程式碼,提高可讀性。例如:
var sum = function (a, b) {
return a + b
}
//改成
let sum = (a, b) => {
return a + b
}
//還可以簡化
let sum = (a , b) => { return a + b }
//把大括號拿掉
let sum = (a , b) => a + b
//只有一個參數時可以不用加括號,但若沒有參數則一定要括號。
和 require 以及 export 一樣, import 和 export 可以讓我們跨檔案提取函式和變數。但因為功能比較新,需要依靠 babel 。最簡單的寫法如下:
export function add(a, b){
return a + b
}
//另一個檔案//
import {add} from '文件名稱'
如果想要 import 文件裡所有變數,可直接使用:
import * as (新名稱) from '文件名稱'
//取用時
(新名稱).(變數名稱)()
當然也可以用預設值:
export default function add(a, b){
return a + b
}
//另一個檔案//
import add from '文件名稱'
// 其實就是
import {default} from '文件名稱'
Babel 是一個 JavaScript 的翻譯器,目的是將比較新的語法轉換為舊的,讓你隨時跟上潮流。
首先要安裝 Babel :
npm install babel-loader @babel/core @babel/preset-env --save-dev
2020/07/08 看完影片更新,剩下 Babel 的部分睡醒再更新
ES6 是指 ECMAScript 第六版,於 2015 年發布,因此又稱 ES2015 。
ECMAScript
是一套程式碼的標準和規範, Javascript 就是依據此實作的,因此可直接使用於 JavaScipt 。
定義變數時,除了在前面加 var
以外,也可以使用 let
和 const
。
const
用於宣告 常數
,因此一旦宣告該常數的內容便無法更動。
相對的, let
和 ver
就可以改變。
const PI = 3.14
let a = 123
PI = 3.1415926 //會出錯
a =456 //沒有問題
此處的重複宣告是指不能改變 記憶體位置
,因此上方例子中的數字無法被改變。但若改變物件內容不會影響到記憶體位置,因此沒有問題。
作用域 (scope) 指的是變數的生存範圍。當你輸入 console.log(a)
時, JavaScript 會往上找是否有宣告過 a ,並回傳該處宣告的值。例如:
function hello(){
var a = 123
console.log(a) //123
}
console.log(a) //undefined,因為 function 在不同層, a 的作用域無法到此。
var
的作用域在函數內(若不是在函數內,就是整個文件內),但可能會產生變數範圍太大互相干擾出錯的狀況,例如:
for (var i = 0; i < 5; i++) {
console.log('*')
}
console.log(i) //5
因此 let
和 const
的範圍縮小到一個 block 中( {
和 }
包起來處),可以避免很多問題。
Template 是樣板的意思。Template Literals 用於字串拼接,不用再只用 +
和 ,
,而是用 ``
框出文字。這種方法可用於多行字串拼接:
let a = `hi
abc
www`
console.log(a)
//hi
//abc
//www
也可在其中使用 ${變數}
。例如:
let name = you
let hi = 'hi, ${you}'
console.log(hi) // 'hi, you'
提取陣列中的值時,最傳統的做法如下:
const arr = [1, 2, 3]
let one = arr[0]
let two = arr[1]
let three = arr[2]
也可以使用 Destructuring 的寫法:
const arr = [1, 2, 3, 4]
let [one, two, three] = arr
console.log(two) //2
同樣的也可以用在物件中:
const obj = {
a : 1,
b : 2,
c : 3,
}
// let {key} = obj ,就是 let obj.key = value
let {a, b, c} = obj
console.log(b) //2
知道規則之後,當然可以花式解構物件:
const obj = {
a : {
b : 'c'
}
}
let {a{b}} = obj
console.log(b) //c
當然也可以和函數合併
function des({a, b}) {
console.log(a)
}
des({a: 1, b: 2}) //1
展開運算子的寫法是在陣列或物件的名稱前方加上 ...
,以下為基本範例:
let arr1 = [1, 2, 3]
let arr2 = [4, arr1] // [4, [1, 2, 3]]
let arr3 = [4, ..arr] // [4, 1, 2, 3]
因為展開運算子就是把陣列展開,因此也可以和函式合併使用,例如:
function sum(a, b, c) {
return a + b + c
}
let arr = [1, 2, 3]
console.log(sum(...arr)) // 6
當然要展開物件也可以:
let obj1 = {a:1, b:2, c:3}
let obj2 = {...obj1, d:4}
console.log(obj2) // {a:1, b:2, c:3, d:4}
展開運算子也可以用於複製陣列和物件,但和直接指定不同,差別如下:
let arr1 = [1, 2, 3]
let arr2 = arr1 // [1, 2, 3]
let arr3 = [...arr1] // [1, 2, 3]
console.log(arr1 === arr2) // true,指向同個記憶體位置
console.log(arr1 === arr3) // fales ,因為記憶體位置不同
反向展開可以用於解構時,將剩餘的東西放進另一個陣列。例如:
let arr = [1, 2, 3, 4]
let [first, ...rest] = arr
console.log(rest) // [2, 3, 4]
let [one, ...test, 4] = arr // 會出現錯誤,因為只能至於最後。
當然也可以和函式合併使用:
function test(...number) {
return number
}
console.log(test(1, 2, 3)) // [1, 2, 3]
預設值可於函式或解構中使用,當沒輸入時,則會自動帶入該值。例如:
function test(a = 2, b) {
return [a, b]
}
console.log(test()) // [2, undefined]
let arr = [1, 2, 3]
let [a, b, c, d = 7] = arr
console.log(a, b, c, d) // 1 2 3 7
箭頭函式簡化程式碼,提高可讀性。例如:
var sum = function (a, b) {
return a + b
}
//改成
let sum = (a, b) => {
return a + b
}
//還可以簡化
let sum = (a , b) => { return a + b }
//把大括號拿掉
let sum = (a , b) => a + b
//只有一個參數時可以不用加括號,但若沒有參數則一定要括號。
和 require 以及 export 一樣, import 和 export 可以讓我們跨檔案提取函式和變數。但因為功能比較新,需要依靠 babel 。最簡單的寫法如下:
export function add(a, b){
return a + b
}
//另一個檔案//
import {add} from '文件名稱'
如果想要 import 文件裡所有變數,可直接使用:
import * as (新名稱) from '文件名稱'
//取用時
(新名稱).(變數名稱)()
當然也可以用預設值:
export default function add(a, b){
return a + b
}
//另一個檔案//
import add from '文件名稱'
// 其實就是
import {default} from '文件名稱'
Babel 是一個 JavaScript 的翻譯器,目的是將比較新的語法轉換為舊的,讓你隨時跟上潮流。
首先要安裝 Babel :
npm install babel-loader @babel/core @babel/preset-env --save-dev
[Week2] 給自己看的 JavaScript 筆記 -運算
[Week2] 給自己看的 JavaScript 筆記
更新紀錄:
2020/07/01 一小時挑戰
2020/07/02 一小時挑戰之延長賽
迴圈可用於處理重複進行的事情,最常見的形式為:
while(條件){
指令
}
因此順序為先檢查條件是否為 true ,若為 true 則進入迴圈執行指令,結束後再回到第一行檢查條件,如此往復。
另一種經典的寫法如下:
for(初始值; 條件; 每圈結束後的指令){
指令
}
例如我想要印出一到十,就可以寫:
for (i = 1; i <= 10; i++){
console.log(i)
}
數學上的函式為 f(x) = y
,將 x 輸入進去就可以得到 y 。 JavaScript 中的函式寫法如下:
function 名稱(參數){
指令
return 回傳值 //若沒設定則自動回傳 undefined
其中可以放其他的 function。若要查看函式的特性可以這樣:
function hi(){
return 'hello'
}
console.log(hi) // [finction: hi]
console.log(hi()) // 'hello'
函式也不一定要取名字,例如:
function transform(arr, func){
var result = []
for(i = 0; i < arr.length, i++){
result = func(arr[i])
}
return result
}
console.log(transform([1, 3, 5], function(x){
return x * 2
}) //[2, 6, 10]
fuction add(a, b){
return a + b
}
console.log(add(2, 3)) // 5
上面例子中, a 和 b 就是函式的 參數
,而 2 和 3 則是 引數
。可以在函式中使用 console.log(arguments)
查看引數。引數會以物件形式出現,例如{'0': 2, '1': 3}。
函式再讀取引數時,對電腦來說是先複製一份才執行,因此所有變動都不會影響到外面。例如:
var n = 10
function double(x){
return x * 2
}
double(n) // 20
console.log(n) // 10
不過物件和陣列可能會改變,和記憶體位置有關,可看本篇文章最後的「其他觀念」。
如果不需要知道結果(比如說只是要 console.log 某個東西)可以不用 return,此時會預設 return undefine 。
另外, return 後的東西都不會執行。
寫法 | 用途 | 範例 |
---|---|---|
Number(str) | 字串轉數字 | var num = Number('11') //11 |
parseInt(str, 進位制) | 字串轉整數,通常使用十進位 | var num = parseInt(2, 10) //10 |
parseFloat(str) | 字串轉小數 | var num = parseFloat('3.2') //3.2 |
num.toFixed(幾位) | 取到小數點後幾位四捨五入 | var num = 2.78.toFixed(1) //2.8 |
Number.MAX_VALUE | 顯示 JavaScript 能記憶最大的數,再大會不精準 | var max = Number.MAX_VALUE //1.7976931348623157e+308 |
Number.MIN_VALUE | 顯示 JavaScript 能記憶最小的數,再小會不精準 | var min = Number.MIN_VALUE //5e-324 |
Math.PI | 圓周率 | var pi = Math.PI //3.141592653589793 |
Math.ceil(num) | 無條件進位 | var ceil = Math.ceil(10.1) //11 |
Math.floar(num) | 無條件捨去 | var floor = Math.floor(10.1) //10 |
Math.round(num) | 四捨五入法 | var round = Math.round(10.1) //10 |
Math.sqrt(num) | 開根號 | var sqrt = Math.sqrt(144) //12 |
Math.pow(num, times) | num 的 times 次方 | var pow = Math.pow(3, 3) //27 |
Math.random() | 隨機生產 0 <= n < 1 | var pow = Math.random() //0.84 |
以上常數皆用大寫表示。
數字轉字串有兩種做法:
var a = 12
a = a.toString() //'12'
var b = 43
b = b + '' //'43',因為字串加數字等於字串
概念:以下所有的位置都是由 0 開始計算,而非 1 。
str.toUpperCase(a)
:將str的第a個字(從零開始)轉為大寫。
str.toLowerCase(a)
:將str的第a個字(從零開始)轉為小寫。
var a = 'hello'.toUpperCase(2) //'heLlo'
var b = 'HELLO'.toUpperCase(0)` //'hELLO'
ASCII 是將字串用數字存起來的形式,每個字母都可以對應到一個數字,且大小寫不同,例如 A 對應到 65 、 B 對應到 66 、 a 對應到 97 。
str.charCodeAt(a)
:得到字串第 a 位置的 ASCII。
String.fromCharCode(num)
:得到數字 num 在 ASCII 上對應到的字母。
運用 ASCII 可將數字轉大小寫,例如:
var str = 'a'
var temp = str.charCodeAt(a) //97
temp -= 32 //65
str = String.fromCharCode(temp) //'A'
若將數字比大小,則是轉換為 ASCII 再比。因為字母的 ASCII 有連貫,因此若要測試字母是否為大寫,可以用 str >= 'A' && str <= 'Z'
。
str.indexOf('key')
: key第一個字在str的位置。若有兩個以上,則回傳最前面的。若找不到key,會回傳負數。
str.replace('key', new)
:將 str 中的 key 換成 new。一樣只會換第一個,若想要全部替換可使用正規表達式。詳細可參考 MDN 。
var a = 'hello'.indexOf('el') // 1
var b = 'hello'.replace('l', 'i') // 'heilo'
str.spilt(a)
:以 a 為標準將字串拆開,並變成陣列。
str.strim()
:去掉字串前後的空格。
str[i]
:叫出字串位置 i 的字。
var a = 'hello world'
var spilt = a.spilt(' ') // ['hello', 'world']
var b = a[4] //'o'
arr.join(a)
:用 a 將陣列接起來,回傳字串。
var a = ['hello', 'i', 'am']
a = a.join('!') // 'hello!i!am'
arr.map(function)
:讓串列中每個元素都跑過函式。
var a = [1, 3, 5, 7, 9]
a = a.map(function(i){
return i+1
})
console.log(a) // [2, 4, 6, 8, 10]
arr.filter(function)
:串列通過函式回傳 true 的會留下。
var a = [1, 2, 3, 4, 5]
a = a.fliter(function(i){
return i % 2 === 0
})
console.log(a) // [2, 4]
只要還是陣列, .map 和 .fliter 可以一起使用,也可以無限往後加。
arr.slice(a, b)
: 回傳陣列的第 a 個元素到第 b-1 個數。
var a = [1, 2, 3, 4, 5]
a = a.slice(2, 4) //[3, 4]
arr.splice(start, remove, new)
:在第 start 的位置,刪除 remove 個元素,並插入 new 為新的元素。(會改變陣列本身)
var a = [1, 2, 3, 4, 5]
a.splice(1, 0, 9) //[1, 9, 2, 3, 4, 5]
a.splice(3, 1, 'hi') //[1, 9, 2, 'hi', 4, 5]
arr.sort(function)
:將陣列的元素由小排到大。
var a = ['apple', 'zipper', 'cute']
a.sort() //['apple', 'cute', 'zipper']
var b = [22, 18, 9]
b.sort() //[18, 22, 9]
上面數字的排序只是看最前面的數字,要真正幫數字排序可使用函數。概念為若回傳值 <= 0 不調換,但若 > 0 則互換。詳細可參考MDN因此若要由小排到大,可用:
b.sort(function(a, b){
return a - b
})
此時若要由小排到大,改成 b - a
即可。
arr.reduce(function)
:可用於累積加總,例如求陣列總和:
var a = [22, 18, 9]
b.reduce(function(acc, cur){
return acc + cur
})
其實就是將上次執行的成果(如 22 + 18)存到 acc ,再執行下一個( acc + 9)。這個方法可以避免外面再多存一個初始值。
詳細觀念可參考:
從博物館寄物櫃理解變數儲存模型
Immutability 為何重要?淺談 immutable.js 和 seamless-immutable
可先看看這幾題:
var a = ([] === [])
var b = ([1] === [1])
var c = ({} === {})
var d = ({a:1} === {a:1})
以上四個全部都是 false!
因為物件儲存的方式是先將物件放再記憶體位置內,再紀錄記憶體位置。左邊的 []
和右邊的 []
其實是存在不同記憶體位置,等式判斷時是看記憶體位置,因此才會得出 false。
另外也有這種狀況:
var a = [1]
var b = a
b[1] = 2
console.log(a, b) // [1,2], [1,2]
此時因為 b 的記憶體位置直接對應到 a 的,因此修改到 b 的內容就會修改到 a。但如果:
var a = [1]
var b = a
b = [1, 2]
console.log(a, b) // [1], [1,2]
上面直接將 b 指定到新的記憶體位置,因此 a 不會受到影響。
不可變是指在創建變數、賦值後便不可改變,若對其有任何變更(例如:新增、修改、刪除),就會回傳一個新值。例如:
var a = 'hello' //放在記憶體 0x01
a = 'change' //開新的記憶體 0x02
a.toUpperCase(0) // 只有 return 沒有改變 a
console.log(a) // 'change'
所有的字串都是不可變的,有些內建函式可以改變陣列,因此使用前可先看 說明書 才不會產生奇怪的 bug。
最後花了總共三個小時才完成這篇(躺)果然記筆記吃力不討好,不過相信未來的我會感謝自己的(?)
]]>[Week2] 給自己看的 JavaScript 筆記 -運算
[Week2] 給自己看的 JavaScript 筆記
更新紀錄:
2020/07/01 一小時挑戰
2020/07/02 一小時挑戰之延長賽
迴圈可用於處理重複進行的事情,最常見的形式為:
while(條件){
指令
}
因此順序為先檢查條件是否為 true ,若為 true 則進入迴圈執行指令,結束後再回到第一行檢查條件,如此往復。
另一種經典的寫法如下:
for(初始值; 條件; 每圈結束後的指令){
指令
}
例如我想要印出一到十,就可以寫:
for (i = 1; i <= 10; i++){
console.log(i)
}
數學上的函式為 f(x) = y
,將 x 輸入進去就可以得到 y 。 JavaScript 中的函式寫法如下:
function 名稱(參數){
指令
return 回傳值 //若沒設定則自動回傳 undefined
其中可以放其他的 function。若要查看函式的特性可以這樣:
function hi(){
return 'hello'
}
console.log(hi) // [finction: hi]
console.log(hi()) // 'hello'
函式也不一定要取名字,例如:
function transform(arr, func){
var result = []
for(i = 0; i < arr.length, i++){
result = func(arr[i])
}
return result
}
console.log(transform([1, 3, 5], function(x){
return x * 2
}) //[2, 6, 10]
fuction add(a, b){
return a + b
}
console.log(add(2, 3)) // 5
上面例子中, a 和 b 就是函式的 參數
,而 2 和 3 則是 引數
。可以在函式中使用 console.log(arguments)
查看引數。引數會以物件形式出現,例如{'0': 2, '1': 3}。
函式再讀取引數時,對電腦來說是先複製一份才執行,因此所有變動都不會影響到外面。例如:
var n = 10
function double(x){
return x * 2
}
double(n) // 20
console.log(n) // 10
不過物件和陣列可能會改變,和記憶體位置有關,可看本篇文章最後的「其他觀念」。
如果不需要知道結果(比如說只是要 console.log 某個東西)可以不用 return,此時會預設 return undefine 。
另外, return 後的東西都不會執行。
寫法 | 用途 | 範例 |
---|---|---|
Number(str) | 字串轉數字 | var num = Number('11') //11 |
parseInt(str, 進位制) | 字串轉整數,通常使用十進位 | var num = parseInt(2, 10) //10 |
parseFloat(str) | 字串轉小數 | var num = parseFloat('3.2') //3.2 |
num.toFixed(幾位) | 取到小數點後幾位四捨五入 | var num = 2.78.toFixed(1) //2.8 |
Number.MAX_VALUE | 顯示 JavaScript 能記憶最大的數,再大會不精準 | var max = Number.MAX_VALUE //1.7976931348623157e+308 |
Number.MIN_VALUE | 顯示 JavaScript 能記憶最小的數,再小會不精準 | var min = Number.MIN_VALUE //5e-324 |
Math.PI | 圓周率 | var pi = Math.PI //3.141592653589793 |
Math.ceil(num) | 無條件進位 | var ceil = Math.ceil(10.1) //11 |
Math.floar(num) | 無條件捨去 | var floor = Math.floor(10.1) //10 |
Math.round(num) | 四捨五入法 | var round = Math.round(10.1) //10 |
Math.sqrt(num) | 開根號 | var sqrt = Math.sqrt(144) //12 |
Math.pow(num, times) | num 的 times 次方 | var pow = Math.pow(3, 3) //27 |
Math.random() | 隨機生產 0 <= n < 1 | var pow = Math.random() //0.84 |
以上常數皆用大寫表示。
數字轉字串有兩種做法:
var a = 12
a = a.toString() //'12'
var b = 43
b = b + '' //'43',因為字串加數字等於字串
概念:以下所有的位置都是由 0 開始計算,而非 1 。
str.toUpperCase(a)
:將str的第a個字(從零開始)轉為大寫。
str.toLowerCase(a)
:將str的第a個字(從零開始)轉為小寫。
var a = 'hello'.toUpperCase(2) //'heLlo'
var b = 'HELLO'.toUpperCase(0)` //'hELLO'
ASCII 是將字串用數字存起來的形式,每個字母都可以對應到一個數字,且大小寫不同,例如 A 對應到 65 、 B 對應到 66 、 a 對應到 97 。
str.charCodeAt(a)
:得到字串第 a 位置的 ASCII。
String.fromCharCode(num)
:得到數字 num 在 ASCII 上對應到的字母。
運用 ASCII 可將數字轉大小寫,例如:
var str = 'a'
var temp = str.charCodeAt(a) //97
temp -= 32 //65
str = String.fromCharCode(temp) //'A'
若將數字比大小,則是轉換為 ASCII 再比。因為字母的 ASCII 有連貫,因此若要測試字母是否為大寫,可以用 str >= 'A' && str <= 'Z'
。
str.indexOf('key')
: key第一個字在str的位置。若有兩個以上,則回傳最前面的。若找不到key,會回傳負數。
str.replace('key', new)
:將 str 中的 key 換成 new。一樣只會換第一個,若想要全部替換可使用正規表達式。詳細可參考 MDN 。
var a = 'hello'.indexOf('el') // 1
var b = 'hello'.replace('l', 'i') // 'heilo'
str.spilt(a)
:以 a 為標準將字串拆開,並變成陣列。
str.strim()
:去掉字串前後的空格。
str[i]
:叫出字串位置 i 的字。
var a = 'hello world'
var spilt = a.spilt(' ') // ['hello', 'world']
var b = a[4] //'o'
arr.join(a)
:用 a 將陣列接起來,回傳字串。
var a = ['hello', 'i', 'am']
a = a.join('!') // 'hello!i!am'
arr.map(function)
:讓串列中每個元素都跑過函式。
var a = [1, 3, 5, 7, 9]
a = a.map(function(i){
return i+1
})
console.log(a) // [2, 4, 6, 8, 10]
arr.filter(function)
:串列通過函式回傳 true 的會留下。
var a = [1, 2, 3, 4, 5]
a = a.fliter(function(i){
return i % 2 === 0
})
console.log(a) // [2, 4]
只要還是陣列, .map 和 .fliter 可以一起使用,也可以無限往後加。
arr.slice(a, b)
: 回傳陣列的第 a 個元素到第 b-1 個數。
var a = [1, 2, 3, 4, 5]
a = a.slice(2, 4) //[3, 4]
arr.splice(start, remove, new)
:在第 start 的位置,刪除 remove 個元素,並插入 new 為新的元素。(會改變陣列本身)
var a = [1, 2, 3, 4, 5]
a.splice(1, 0, 9) //[1, 9, 2, 3, 4, 5]
a.splice(3, 1, 'hi') //[1, 9, 2, 'hi', 4, 5]
arr.sort(function)
:將陣列的元素由小排到大。
var a = ['apple', 'zipper', 'cute']
a.sort() //['apple', 'cute', 'zipper']
var b = [22, 18, 9]
b.sort() //[18, 22, 9]
上面數字的排序只是看最前面的數字,要真正幫數字排序可使用函數。概念為若回傳值 <= 0 不調換,但若 > 0 則互換。詳細可參考MDN因此若要由小排到大,可用:
b.sort(function(a, b){
return a - b
})
此時若要由小排到大,改成 b - a
即可。
arr.reduce(function)
:可用於累積加總,例如求陣列總和:
var a = [22, 18, 9]
b.reduce(function(acc, cur){
return acc + cur
})
其實就是將上次執行的成果(如 22 + 18)存到 acc ,再執行下一個( acc + 9)。這個方法可以避免外面再多存一個初始值。
詳細觀念可參考:
從博物館寄物櫃理解變數儲存模型
Immutability 為何重要?淺談 immutable.js 和 seamless-immutable
可先看看這幾題:
var a = ([] === [])
var b = ([1] === [1])
var c = ({} === {})
var d = ({a:1} === {a:1})
以上四個全部都是 false!
因為物件儲存的方式是先將物件放再記憶體位置內,再紀錄記憶體位置。左邊的 []
和右邊的 []
其實是存在不同記憶體位置,等式判斷時是看記憶體位置,因此才會得出 false。
另外也有這種狀況:
var a = [1]
var b = a
b[1] = 2
console.log(a, b) // [1,2], [1,2]
此時因為 b 的記憶體位置直接對應到 a 的,因此修改到 b 的內容就會修改到 a。但如果:
var a = [1]
var b = a
b = [1, 2]
console.log(a, b) // [1], [1,2]
上面直接將 b 指定到新的記憶體位置,因此 a 不會受到影響。
不可變是指在創建變數、賦值後便不可改變,若對其有任何變更(例如:新增、修改、刪除),就會回傳一個新值。例如:
var a = 'hello' //放在記憶體 0x01
a = 'change' //開新的記憶體 0x02
a.toUpperCase(0) // 只有 return 沒有改變 a
console.log(a) // 'change'
所有的字串都是不可變的,有些內建函式可以改變陣列,因此使用前可先看 說明書 才不會產生奇怪的 bug。
最後花了總共三個小時才完成這篇(躺)果然記筆記吃力不討好,不過相信未來的我會感謝自己的(?)
]]>雖然時間已經來到第三週了,不過這篇的內容都是第二週的進度。先求有再求好,因此目標是記錄學習過程中重要概念,以及自己覺得新或之後方便回來的資料。
更新紀錄:
2020/06/30 時間不夠,兩小時寫完筆記挑戰。
其他運算子可以參考上一篇:[Week2] JavaScript 基礎 -運算
符號 | 功能 | 例子 |
---|---|---|
= | 賦值 | var a = 2 |
== | 等於 | 2 + 3 == 5 > true |
=== | 等於(含型態相等) | 3 === '3' > false |
!== | 不等於 | 2 * 2 !== 3 > true |
小技巧:
為了避免奇怪的 Bug ,判斷等式時盡量使用 ===
。
符號 | 例子 | 實際上在做什麼 |
---|---|---|
+= | a += b | a = a + b |
-= | a -= b | a = a - b |
* = | a * = b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
++ | a++ | a = a + 1 |
-- | a-- | a = a - 1 |
另外 ++a
也代表 a += 1
, --a
也代表 a -= 1
。放前後的差別如下:
var a = 0
console.log(a++ && 30)
> false
console.log(a)
> 1
上面例子中, console.log(a++ && 30)
可以解讀成 console.log(a && 30)
然後再 a+=1
。
var a = 0
console.log(++a && 30) //先執行 a+=1 再 console.log(a && 30)
> true
console.log(a)
> 1
通常一句話中只會有一個 a++
或 a--
,才不會有判斷先後順序的問題。
JavaScripts 中,宣告變數的方法如下
var 變數名稱 = 變數內容
可以先將變數想像成一個箱子,裝著內容(的記憶體位置)。
記得培養好好命名的習慣,如果每個變數都只叫 a 或 b ,事後回來看會很難辨別,連自己都搞不清楚。
undefine
代表有宣告變數,但沒有內容; not define
代表該變數沒有被宣告過。this_is_a_box
thisIsABox
輸入 type (變數名稱)
可以得到變數的型態,型態可以分為以下幾種:
型態 | 例子 |
---|---|
boolean | true, false |
number | 1, 2, 999, -3.2 |
string | '字串', '30', 'hello' |
object | [1, 2, 'a'], {a: 1}, null |
undefine | undefine |
function | 所有 function |
陣列可適用於重複性高的事務,例如老師希望你輸入全班成績,只用變數的話會變成:
var number1 = 90
var number2 = 33
var number3 = 82
...
不如包成一個陣列:
var scores = [90, 33, 82, ...]
陣列的形式是:
陣列名稱 = [ 第 0 個內容, 第 1 個內容, 第 2 個內容, ...]
JavaScript 中,所有索引值(位置)都是 0 開始。
以下是基礎的陣列用法:
var 陣列名稱 = [ 第 0 個內容, 第 1 個內容, 第 2 個內容, ...]
陣列名稱[索引值] = 內容 //增加或改變陣列內容
陣列名稱.push(內容) //將內容增新到陣列尾端
陣列名稱.length //得到陣列長度(有幾個元素)
如果只是登記同學成績,陣列好像就夠了。但老師看你好像很熟悉 JaveScript ,進一步拜託你登記每位同學的名字、電話和成績。如果每種類別都用一個陣列好像太冗了,此時就可以使用物件:
var 物件名稱 = {
key0 : value0;
key1 : value1;
...
}
以上 value 的部分可以放值、字串、陣列、物件或甚至一個函式。當然物件也能和陣列一同使用,例如以下每位學生就是一個物件:
var students = [ peter, mary, ...]
var peter = {
name: 'Peter'
phone: '0922'
score: 100
}
叫出 value 的方法有兩種: 物件名稱['key']
和 物件名稱.key
。
var peter = {
name: 'Peter'
phone: '0922'
score: 100
}
console.log(peter[name])
> Peter
peter[score] = 88
console.log(peter)
> {
name: 'Peter'
phone: '0922'
score: 88
}
'hello' + 'world' > 'helloworld'
。10 + '20' > '1020'
。0.2 + 0.3 === 0.5 > false
判斷式的形式如下:
if (條件) { //如果條件為 true 則執行下方內容
執行內容
} else if(條件) { //可有可無,也可以有很多個
執行內容
} else { //上面都不滿足的話就執行下方內容
執行內容
}
例如判斷考試成績是否及格:
var score = 79
if (score >= 80){
console.log('Great!')
} else if (score >= 60){
console.log('pass')
} else {
console.log('fail')
}
> pass
若在條件處運算,則會先完成運算後再依照運算結果回傳真假。
如果今天太多條件要判斷,例如要是變數是 1
,就回傳 一月
; 2
就是 二月
,以此類推。這時要是使用 if 判斷式,就要寫 12 次了?這種情況可以用 switch case 解決:
var month = 6
switch(month){
case 1: // case + 條件
console.log('一月')
break //如果不加的話滿足 1 就會直接繼續執行下面指令
case 2:
console.log('二月')
break
...
default: //都不符合的話
console.log('x')
}
> 六月
當然這種情況也可以用陣列解決:
var monthToChinese = ['一月', '二月', '三月', ...]
console.log(monthToChinese[month - 1])
//因為陣列索引值從 0 開始算,但月份是從 1 開始
相反的,如果只是要判斷是否,就可以使用 ternary :
(條件)? (true回傳值): (false回傳值)
//例子:判斷是否及格
var score = 59
var isPass = (score >= 60)? 'pass': 'fail'
console.log(isPass)
> fail
本來希望能將迴圈、函式、常用內建函式、物件在電腦中的保存方式及其他觀念一起記錄下來,不過第一次寫到這裡時已經兩個小時了。因此明天會開一小時挑戰把剩下概念筆記完。
很喜歡這種截稿的緊張感,番茄醬工作法屢試不爽(等等)
雖然時間已經來到第三週了,不過這篇的內容都是第二週的進度。先求有再求好,因此目標是記錄學習過程中重要概念,以及自己覺得新或之後方便回來的資料。
更新紀錄:
2020/06/30 時間不夠,兩小時寫完筆記挑戰。
其他運算子可以參考上一篇:[Week2] JavaScript 基礎 -運算
符號 | 功能 | 例子 |
---|---|---|
= | 賦值 | var a = 2 |
== | 等於 | 2 + 3 == 5 > true |
=== | 等於(含型態相等) | 3 === '3' > false |
!== | 不等於 | 2 * 2 !== 3 > true |
小技巧:
為了避免奇怪的 Bug ,判斷等式時盡量使用 ===
。
符號 | 例子 | 實際上在做什麼 |
---|---|---|
+= | a += b | a = a + b |
-= | a -= b | a = a - b |
* = | a * = b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
++ | a++ | a = a + 1 |
-- | a-- | a = a - 1 |
另外 ++a
也代表 a += 1
, --a
也代表 a -= 1
。放前後的差別如下:
var a = 0
console.log(a++ && 30)
> false
console.log(a)
> 1
上面例子中, console.log(a++ && 30)
可以解讀成 console.log(a && 30)
然後再 a+=1
。
var a = 0
console.log(++a && 30) //先執行 a+=1 再 console.log(a && 30)
> true
console.log(a)
> 1
通常一句話中只會有一個 a++
或 a--
,才不會有判斷先後順序的問題。
JavaScripts 中,宣告變數的方法如下
var 變數名稱 = 變數內容
可以先將變數想像成一個箱子,裝著內容(的記憶體位置)。
記得培養好好命名的習慣,如果每個變數都只叫 a 或 b ,事後回來看會很難辨別,連自己都搞不清楚。
undefine
代表有宣告變數,但沒有內容; not define
代表該變數沒有被宣告過。this_is_a_box
thisIsABox
輸入 type (變數名稱)
可以得到變數的型態,型態可以分為以下幾種:
型態 | 例子 |
---|---|
boolean | true, false |
number | 1, 2, 999, -3.2 |
string | '字串', '30', 'hello' |
object | [1, 2, 'a'], {a: 1}, null |
undefine | undefine |
function | 所有 function |
陣列可適用於重複性高的事務,例如老師希望你輸入全班成績,只用變數的話會變成:
var number1 = 90
var number2 = 33
var number3 = 82
...
不如包成一個陣列:
var scores = [90, 33, 82, ...]
陣列的形式是:
陣列名稱 = [ 第 0 個內容, 第 1 個內容, 第 2 個內容, ...]
JavaScript 中,所有索引值(位置)都是 0 開始。
以下是基礎的陣列用法:
var 陣列名稱 = [ 第 0 個內容, 第 1 個內容, 第 2 個內容, ...]
陣列名稱[索引值] = 內容 //增加或改變陣列內容
陣列名稱.push(內容) //將內容增新到陣列尾端
陣列名稱.length //得到陣列長度(有幾個元素)
如果只是登記同學成績,陣列好像就夠了。但老師看你好像很熟悉 JaveScript ,進一步拜託你登記每位同學的名字、電話和成績。如果每種類別都用一個陣列好像太冗了,此時就可以使用物件:
var 物件名稱 = {
key0 : value0;
key1 : value1;
...
}
以上 value 的部分可以放值、字串、陣列、物件或甚至一個函式。當然物件也能和陣列一同使用,例如以下每位學生就是一個物件:
var students = [ peter, mary, ...]
var peter = {
name: 'Peter'
phone: '0922'
score: 100
}
叫出 value 的方法有兩種: 物件名稱['key']
和 物件名稱.key
。
var peter = {
name: 'Peter'
phone: '0922'
score: 100
}
console.log(peter[name])
> Peter
peter[score] = 88
console.log(peter)
> {
name: 'Peter'
phone: '0922'
score: 88
}
'hello' + 'world' > 'helloworld'
。10 + '20' > '1020'
。0.2 + 0.3 === 0.5 > false
判斷式的形式如下:
if (條件) { //如果條件為 true 則執行下方內容
執行內容
} else if(條件) { //可有可無,也可以有很多個
執行內容
} else { //上面都不滿足的話就執行下方內容
執行內容
}
例如判斷考試成績是否及格:
var score = 79
if (score >= 80){
console.log('Great!')
} else if (score >= 60){
console.log('pass')
} else {
console.log('fail')
}
> pass
若在條件處運算,則會先完成運算後再依照運算結果回傳真假。
如果今天太多條件要判斷,例如要是變數是 1
,就回傳 一月
; 2
就是 二月
,以此類推。這時要是使用 if 判斷式,就要寫 12 次了?這種情況可以用 switch case 解決:
var month = 6
switch(month){
case 1: // case + 條件
console.log('一月')
break //如果不加的話滿足 1 就會直接繼續執行下面指令
case 2:
console.log('二月')
break
...
default: //都不符合的話
console.log('x')
}
> 六月
當然這種情況也可以用陣列解決:
var monthToChinese = ['一月', '二月', '三月', ...]
console.log(monthToChinese[month - 1])
//因為陣列索引值從 0 開始算,但月份是從 1 開始
相反的,如果只是要判斷是否,就可以使用 ternary :
(條件)? (true回傳值): (false回傳值)
//例子:判斷是否及格
var score = 59
var isPass = (score >= 60)? 'pass': 'fail'
console.log(isPass)
> fail
本來希望能將迴圈、函式、常用內建函式、物件在電腦中的保存方式及其他觀念一起記錄下來,不過第一次寫到這裡時已經兩個小時了。因此明天會開一小時挑戰把剩下概念筆記完。
很喜歡這種截稿的緊張感,番茄醬工作法屢試不爽(等等)
是一套程式語言,因為幾乎沒有程式基礎,此時先了解 性質 也沒有太多意義。因此就直接開始吧。
下載方法: 官網
安裝完成後在 Command Line 輸入 node -v
,如果成功會出現版本號,如:
$ node -v
v14.4.0
在 Commend Line 輸入 node
,即可進入編輯模式,之後按 Ctrl + C
離開。也可以用 node + 檔案名稱
進入 vim 編輯器編輯 .js 檔案。
如果想要讓結果顯示出 Hello World
,可以使用:
console.log(欲顯示文字)
如果想顯示數字可以直接輸入,但如果想顯示字串(之後會介紹分類),要記得字串前後加單引號 '
或雙引號 "
。
通常是從右到左運算,另外像是先乘除後加減之類的,運算符號之間也有 優先順序 。但保險起見,可以用小括號 ()
標示出要先算的地方。
符號 | 功能 | 例子 |
---|---|---|
+ | 加 | 4 + 3 > 7 |
- | 減 | 5 - 2 > 3 |
* | 乘 | 3 * 7 > 21 |
/ | 除 | 10 / 2 > 5 |
% | 餘數 | 7 % 3 > 1 |
一般來說就是放可以對應到真假值的運算(如 true
或 4 - 3 == 1
)。另外若在 or 和 and 放數字或字串,會顯示 誰決定句子真假。
JavaScript 中的 or 是用 ||
表示。
以下是 a || b 的真值表:(用 0 表示 false , 1 表示 true)
a | b | a or b |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
> true || true
true
> true || false
true
> false || true
true
> false || false
false
誰決定句子真假:
通常除了 0、null 以外幾乎所有字都對應到 true。
上方真值表可看出,若前者為真,後面不管是誰都無所謂;但若前者為假,句子的真假則由後者決定。因此:
> 33 || 10
33
> 33 || 0
33
> 0 || 33
33
> 0 || 0
0
JavaScript 中的 and 是用 &&
表示。
以下是 a && b 的真值表:(用 0 表示 false , 1 表示 true)
a | b | a && b |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
> true && true
true
> true && false
false
> false && true
false
> false && false
false
誰決定句子真假:
上方真值表可看出,若前者為真,後者的真假會決定句子的真假;但若前者為假,無論後者真假,句子都是假的。因此:
> 33 && 10
10
> 33 && 0
0
> 0 && 33
0
> 0 && 0
0
JavaScript 中的 not 是用 !
表示。
以下是 !a 的真值表:(用 0 表示 false , 1 表示 true)
a | !a |
---|---|
1 | 0 |
0 | 1 |
> ! true
false
> ! false
true
後面放其他東西的話:
會將字轉換成真假值後直接顯示真假值
> ! 882
false
> ! 0
true
二進位法是電腦最原始的形式,因此直接使用二進位法溝通是最有效率的。
資料來源
二進位的 0100 是 2 的 3 次方、1000 是 2 的 4 次方。可推斷往左移就是乘以二的幾次方,右移則是除以二的幾次方。
<<
:左移。
>>
:右移。
> 10 << 1 #10 左移一次(乘以 2 一次)
20
> 8 >> 2 #8 右移兩次(除以 2 兩次,也就是除以 4)
2
概念:將兩個數字拆成位元,每個位置再各自使用邏輯規則比對出新的位元,最後轉換回數字。
位元運算的 and 用 &
表示。
例如 10 & 15
會輸出 0 。
數字 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
10 | 1 | 0 | 1 | 0 |
15 | 1 | 1 | 1 | 1 |
10 & 15 | 1 | 0 | 1 | 0 |
位元遮罩:
因為 1 只有最後一位有數字,如果某數 a & 1
等於一,表示 a 轉換位元後在 1 的位置上有數字,也就是說他是奇數。
位元運算的 and 用 |
表示。
例如 10 | 15
會輸出 15 。
數字 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
10 | 1 | 0 | 1 | 0 |
15 | 1 | 1 | 1 | 1 |
10 & 15 | 1 | 1 | 1 | 1 |
位元運算的 and 用 ^
表示。
xor的真值表如下:
a | b | a ^ b |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
因此10 ^ 15
會輸出 5 。
數字 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
10 | 1 | 0 | 1 | 0 |
15 | 1 | 1 | 1 | 1 |
10 & 15 | 0 | 1 | 0 | 1 |
位元運算的 not 用 ~
表示。
不過因為 10 的位元 並不只有 1010
,而是 00000(略)1010
,因此 ~10
等於 -16 。
是一套程式語言,因為幾乎沒有程式基礎,此時先了解 性質 也沒有太多意義。因此就直接開始吧。
下載方法: 官網
安裝完成後在 Command Line 輸入 node -v
,如果成功會出現版本號,如:
$ node -v
v14.4.0
在 Commend Line 輸入 node
,即可進入編輯模式,之後按 Ctrl + C
離開。也可以用 node + 檔案名稱
進入 vim 編輯器編輯 .js 檔案。
如果想要讓結果顯示出 Hello World
,可以使用:
console.log(欲顯示文字)
如果想顯示數字可以直接輸入,但如果想顯示字串(之後會介紹分類),要記得字串前後加單引號 '
或雙引號 "
。
通常是從右到左運算,另外像是先乘除後加減之類的,運算符號之間也有 優先順序 。但保險起見,可以用小括號 ()
標示出要先算的地方。
符號 | 功能 | 例子 |
---|---|---|
+ | 加 | 4 + 3 > 7 |
- | 減 | 5 - 2 > 3 |
* | 乘 | 3 * 7 > 21 |
/ | 除 | 10 / 2 > 5 |
% | 餘數 | 7 % 3 > 1 |
一般來說就是放可以對應到真假值的運算(如 true
或 4 - 3 == 1
)。另外若在 or 和 and 放數字或字串,會顯示 誰決定句子真假。
JavaScript 中的 or 是用 ||
表示。
以下是 a || b 的真值表:(用 0 表示 false , 1 表示 true)
a | b | a or b |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
> true || true
true
> true || false
true
> false || true
true
> false || false
false
誰決定句子真假:
通常除了 0、null 以外幾乎所有字都對應到 true。
上方真值表可看出,若前者為真,後面不管是誰都無所謂;但若前者為假,句子的真假則由後者決定。因此:
> 33 || 10
33
> 33 || 0
33
> 0 || 33
33
> 0 || 0
0
JavaScript 中的 and 是用 &&
表示。
以下是 a && b 的真值表:(用 0 表示 false , 1 表示 true)
a | b | a && b |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
> true && true
true
> true && false
false
> false && true
false
> false && false
false
誰決定句子真假:
上方真值表可看出,若前者為真,後者的真假會決定句子的真假;但若前者為假,無論後者真假,句子都是假的。因此:
> 33 && 10
10
> 33 && 0
0
> 0 && 33
0
> 0 && 0
0
JavaScript 中的 not 是用 !
表示。
以下是 !a 的真值表:(用 0 表示 false , 1 表示 true)
a | !a |
---|---|
1 | 0 |
0 | 1 |
> ! true
false
> ! false
true
後面放其他東西的話:
會將字轉換成真假值後直接顯示真假值
> ! 882
false
> ! 0
true
二進位法是電腦最原始的形式,因此直接使用二進位法溝通是最有效率的。
資料來源
二進位的 0100 是 2 的 3 次方、1000 是 2 的 4 次方。可推斷往左移就是乘以二的幾次方,右移則是除以二的幾次方。
<<
:左移。
>>
:右移。
> 10 << 1 #10 左移一次(乘以 2 一次)
20
> 8 >> 2 #8 右移兩次(除以 2 兩次,也就是除以 4)
2
概念:將兩個數字拆成位元,每個位置再各自使用邏輯規則比對出新的位元,最後轉換回數字。
位元運算的 and 用 &
表示。
例如 10 & 15
會輸出 0 。
數字 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
10 | 1 | 0 | 1 | 0 |
15 | 1 | 1 | 1 | 1 |
10 & 15 | 1 | 0 | 1 | 0 |
位元遮罩:
因為 1 只有最後一位有數字,如果某數 a & 1
等於一,表示 a 轉換位元後在 1 的位置上有數字,也就是說他是奇數。
位元運算的 and 用 |
表示。
例如 10 | 15
會輸出 15 。
數字 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
10 | 1 | 0 | 1 | 0 |
15 | 1 | 1 | 1 | 1 |
10 & 15 | 1 | 1 | 1 | 1 |
位元運算的 and 用 ^
表示。
xor的真值表如下:
a | b | a ^ b |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
因此10 ^ 15
會輸出 5 。
數字 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
10 | 1 | 0 | 1 | 0 |
15 | 1 | 1 | 1 | 1 |
10 & 15 | 0 | 1 | 0 | 1 |
位元運算的 not 用 ~
表示。
不過因為 10 的位元 並不只有 1010
,而是 00000(略)1010
,因此 ~10
等於 -16 。
某天你正在寫新功能,此時有一個 bug 需要緊急修復,現在你面對到一個問題:當你回傳 bug fix 時,不可能連正在開發的新功能一起完成——這樣新功能的檔案該放哪?
branch 可以面對這個狀況。
可以把 branch 想成創造分身/另一個世界線的概念,先把開發中的新功能放到那個分身/平行世界中,等 bug fix 結束,再合併回來,這樣如果過程中發生了無法回復的傷害,也可以乾脆不要這個分身/平行世界。因此 branch 也廣泛用於多人共同編輯的專案上。
這個網站可以讓大家模擬 branch 的運作方法: Learn Git Branching
git branch (新 branch 名稱)
git branch -v
:查看現在有哪些 branch 。
git branch -d (欲刪除 branch 名稱)
相對於切換 commit 的 git checkout
, branch 上的切換是:
git checkhout (欲切換 branch 名稱)
git merge (欲合併 branch 名稱)
merge
是將後方 branch 合併進現在的 branch 。因此若要將 branch1 合併進 master ,就要先切換到 master , 再輸入git merge branch1
。
不過誰合併誰實際上似乎不會有太多差異,只會在系統紀錄上有細微差別(新的 commit 誰的名字在前面)。
若 master 和欲合併的 branch 中都有更改過某個檔案,合併時可能會出現:
Auto-merging 456.txt
CONFLICT (content): Merge conflict in 456.txt
Automatic merge failed; fix conflicts and then commit the result.
這就表示兩個版本在 456.txt
做了不同的更改, Git 不知道要以哪個為優先。此時打開 456.txt
會看到有衝突的地方會顯示:
<<<<<<< HEAD
123123
=======
hihihi
>>>>>>> new
手動修改留下最後想要的版本,再 commit 就可以了。
當多人協作版本控制時,就需要一個地方 .git 的資料夾。 Git 是版本控制的程式,而 GitHub 就可以是放 Git respository 的地方。
https://gitbook.tw/chapters/github/push-to-github.html
首先要把本地(你的電腦)內的 Git 連接上 GitHub :
git remote add origin (repository位址)
這裡的 remote
是遠端的意思, origin
則只是代號。
本地(你的電腦)和雲端 (GitHub)並不是連動的,可想像成 Google 雲端硬碟和電腦裡的檔案,因此時常需要手動同步。
這裡指令的邏輯是從本地出發,因此上傳要用推(push),下載要用拉(pull)。
git push orgin master
你可能會想「啊我就不想打那麼多字啊」,這樣可以一開始就用 git push -u orgin master
設定好 upstream
,之後只打 git push
, Git 就會預設你要上傳到 upstream ,這裡 GitHub 會建議你一開始設定 master 為upstream。
如果已經有權限,就用:
git pull orgin master
可能會產生衝突,此時就用上面 merge 遇到衝突的方法,一樣手動解決即可。
但如果想下載別人在 GitHub 上的檔案,則要用:
git clone (網址)
如果沒有權限是不能再把東西 push 上去的,此時就可以使用 fork
的功能。
Git flow 是一套使用 Git 工作的流程,現在已經有許多不同的 Git flow 出現,以下介紹 GitHub 官網提供 的版本。
最後可以 pull 回來,並 git branch -d
將剛剛開的 branch 刪掉。
想要改 commit 的說明文字,可以輸入:
git reset --amend
之後進入 vim 編輯器修改就可以了。
如果已經 commit 但後悔了,可以使用:
git reset HEAD^
HEAD
是用來存放「最新檔案資訊的」檔案, ^
代表前一個的意思,也就是將最新檔案資訊 reset 到前一個。
如果之前修改的檔案都不要了,可在後面加一個 --hard
。否則會自動預設 --soft
,也就是雖然 commit 消失,但檔案還是被改動過的樣子。
還沒 commit 前,若想回復檔案到上一個 commit 的狀態,可輸入:
git check --(欲回復檔案名)
git branch -m (新的名稱)
想將遠端的 branch 抓到本地,可輸入:
git pull origin (欲抓 branch 名稱)
Hook 是鉤子的意思,在程式中看到 Hook 就是勾住某樣東西,發生變化時通知我。例如 Git 的 pre-commit Hook 就是在 commit 前檢查是否符合規範,若違反就無法 commit 。
git pull origin master
,確保和最新狀況一樣等等才能順利上傳。git branch (branch name)
並切換過去 git checkout (branch name)
。也可用組合技 git branch -b (branch name)
。 git commit -am "說明"
,若有新增的檔案則用 git add .
和 git commit -m "說明"
。git push origin (branch name)
。git checkout master
。git pull origin master
git branch -d (branch name)
某天你正在寫新功能,此時有一個 bug 需要緊急修復,現在你面對到一個問題:當你回傳 bug fix 時,不可能連正在開發的新功能一起完成——這樣新功能的檔案該放哪?
branch 可以面對這個狀況。
可以把 branch 想成創造分身/另一個世界線的概念,先把開發中的新功能放到那個分身/平行世界中,等 bug fix 結束,再合併回來,這樣如果過程中發生了無法回復的傷害,也可以乾脆不要這個分身/平行世界。因此 branch 也廣泛用於多人共同編輯的專案上。
這個網站可以讓大家模擬 branch 的運作方法: Learn Git Branching
git branch (新 branch 名稱)
git branch -v
:查看現在有哪些 branch 。
git branch -d (欲刪除 branch 名稱)
相對於切換 commit 的 git checkout
, branch 上的切換是:
git checkhout (欲切換 branch 名稱)
git merge (欲合併 branch 名稱)
merge
是將後方 branch 合併進現在的 branch 。因此若要將 branch1 合併進 master ,就要先切換到 master , 再輸入git merge branch1
。
不過誰合併誰實際上似乎不會有太多差異,只會在系統紀錄上有細微差別(新的 commit 誰的名字在前面)。
若 master 和欲合併的 branch 中都有更改過某個檔案,合併時可能會出現:
Auto-merging 456.txt
CONFLICT (content): Merge conflict in 456.txt
Automatic merge failed; fix conflicts and then commit the result.
這就表示兩個版本在 456.txt
做了不同的更改, Git 不知道要以哪個為優先。此時打開 456.txt
會看到有衝突的地方會顯示:
<<<<<<< HEAD
123123
=======
hihihi
>>>>>>> new
手動修改留下最後想要的版本,再 commit 就可以了。
當多人協作版本控制時,就需要一個地方 .git 的資料夾。 Git 是版本控制的程式,而 GitHub 就可以是放 Git respository 的地方。
https://gitbook.tw/chapters/github/push-to-github.html
首先要把本地(你的電腦)內的 Git 連接上 GitHub :
git remote add origin (repository位址)
這裡的 remote
是遠端的意思, origin
則只是代號。
本地(你的電腦)和雲端 (GitHub)並不是連動的,可想像成 Google 雲端硬碟和電腦裡的檔案,因此時常需要手動同步。
這裡指令的邏輯是從本地出發,因此上傳要用推(push),下載要用拉(pull)。
git push orgin master
你可能會想「啊我就不想打那麼多字啊」,這樣可以一開始就用 git push -u orgin master
設定好 upstream
,之後只打 git push
, Git 就會預設你要上傳到 upstream ,這裡 GitHub 會建議你一開始設定 master 為upstream。
如果已經有權限,就用:
git pull orgin master
可能會產生衝突,此時就用上面 merge 遇到衝突的方法,一樣手動解決即可。
但如果想下載別人在 GitHub 上的檔案,則要用:
git clone (網址)
如果沒有權限是不能再把東西 push 上去的,此時就可以使用 fork
的功能。
Git flow 是一套使用 Git 工作的流程,現在已經有許多不同的 Git flow 出現,以下介紹 GitHub 官網提供 的版本。
最後可以 pull 回來,並 git branch -d
將剛剛開的 branch 刪掉。
想要改 commit 的說明文字,可以輸入:
git reset --amend
之後進入 vim 編輯器修改就可以了。
如果已經 commit 但後悔了,可以使用:
git reset HEAD^
HEAD
是用來存放「最新檔案資訊的」檔案, ^
代表前一個的意思,也就是將最新檔案資訊 reset 到前一個。
如果之前修改的檔案都不要了,可在後面加一個 --hard
。否則會自動預設 --soft
,也就是雖然 commit 消失,但檔案還是被改動過的樣子。
還沒 commit 前,若想回復檔案到上一個 commit 的狀態,可輸入:
git check --(欲回復檔案名)
git branch -m (新的名稱)
想將遠端的 branch 抓到本地,可輸入:
git pull origin (欲抓 branch 名稱)
Hook 是鉤子的意思,在程式中看到 Hook 就是勾住某樣東西,發生變化時通知我。例如 Git 的 pre-commit Hook 就是在 commit 前檢查是否符合規範,若違反就無法 commit 。
git pull origin master
,確保和最新狀況一樣等等才能順利上傳。git branch (branch name)
並切換過去 git checkout (branch name)
。也可用組合技 git branch -b (branch name)
。 git commit -am "說明"
,若有新增的檔案則用 git add .
和 git commit -m "說明"
。git push origin (branch name)
。git checkout master
。git pull origin master
git branch -d (branch name)
Git 是一種版本控制的系統。結束。
——等等,那什麼又是版本控制?
玩遊戲時可以將進度存檔,要是之後被打死就可以回到這個紀錄時間點重來。另外像是寫報告時會出現報告_final.docx
、報告_final_final.docx
、報告_final_final_final.docx
,其實就是版本控制的概念。
工作時很多人負責同一個專案出問題,想知道誰在哪個時間點寫錯程式碼嗎?或是正在開發新功能同時需要修 bug ,兩者必須分開進行又不想刪掉手邊正在開發的新功能嗎?版本控制都能輕鬆讓你回到世界還很單純的時刻 (?)
無論是自己還是多人版本控制,東西太多時就會混亂,就需要有個系統幫忙進行版本控制,而 Git 就是這樣的一個系統。
在了解 Git 之前,先試著想想如果要設計一個管理版本控制的系統,會需要甚麼樣的架構?
同時管理兩個以上有關聯的檔案,如果每個檔案的版本分開紀錄,資料夾很有可能會變成這樣:
這個檔案.txt
這個檔案_版本二.txt
那個檔案.txt
那個檔案_版本二.txt
那個檔案_版本三.txt
然後你同事轉過來問你:「ㄟ當初那個檔案的版本二,是對應到這個檔案的哪個版本啊?」結果你也忘記,尷尬。
解決方法:
大家捆起來一起做版本紀錄,就像是把全部的東西放進資料夾裡面,最後主目錄會變成這樣:
專案資料夾(內含這個檔案.txt、那個檔案.txt)
專案_版本二資料夾(內含這個檔案.txt、那個檔案_版本二.txt)
專案_版本三資料夾(內含這個檔案_版本二.txt、那個檔案_版本三.txt)
五個人同時負責同個專案,版本名稱好像不能單純用數字命名了。
解決方法:
不要糾結命名方法了, Git 直接亂數命名,然後開一個檔案紀錄版本們的時間和修改者等資料,還有一個檔案紀錄哪個是最新版本。
推薦使用 Git Bash ,下載方式可參考我之前寫的: [DAY4] 初學Command Line
指令 | 功能 | 其他 |
---|---|---|
git init | 初始化 | 成功後會建立 .git 的隱藏資料夾 |
git status | 看狀態 | |
git add | 將檔案加入版本控制 | |
git rm --cached | 將檔案移除版本控制 | 若沒加 --cached 會直接刪掉檔案,要小心 |
git commit | 建立新的版本 | 最後可加 -m "敘述" 可對 commit 做說明 |
git log | 查看歷史紀錄 | |
git checkout | 回到某個版本 | |
git branch | 和 branch 有關 | 可直接加名稱新增branch |
-v 查看現在有哪些 branch |
||
-d 刪除 branch |
||
git branchout | 切換 branch | |
git merge | 合併 branch | 把 branch 合併進來 |
首先要讓 Git 連接資料夾,確認位置在目標資料夾後輸入:
git init
之後輸入 ls -a
查看所有檔案,會發現多了一個 .git
的隱藏資料夾,就表示初始化成功了。
git status
可查看現在 Git 內的狀況。
Git可以的工作區塊可以分成三個,分別是工作目錄 (Worling Diretory)
、暫存區 (Staging Area)
和 儲存區 (Respository)
。
可以把儲存區想成一個倉庫,每次有或從外面(工作目錄)送進來前,都會先放在倉庫前面的廣場(暫存區),都好了才開門一次放進倉庫,這樣除了倉庫門不用開開關關外,裡面的人也比較好清點貨物。
以下圖為例
On branch master
:目前沒有分支。
Changes to be committed
:將要提交的檔案。也就是在廣場捆好等倉庫門打開就可以直接運送進去的檔案。
Changes not staged for commit
:被更動尚未要提交的檔案。其實也在廣場,只是門打開時無法被送進去。
Untracked files
:未被追蹤的檔案。還沒進入廣場。
區域之間的關係和移動檔案的方法,可以用下圖解釋:
git add "欲加入檔案名稱"
git add
可將檔案從 untrscked 的狀態加入版本控制。
與之相反的是:
git rm "欲移出檔案名稱" --cached
記得一定要加 --cached
,不然檔案會被刪掉。
小技巧:
對象是所有檔案時,可使用.
或-all
,不用一個一個輸入。
兩者差異可參考:把檔案交給 Git 控管
細節:暫存區的檔案修改完後,要再 add
一次,不然不會 commit。
在廣場準備得差不多了,可以打開倉庫門將檔案存入做為新的版本:
git commit (欲放入 respository 的檔案)
enter 後會進入 vim ,這時可以輸入對這個新版本的敘述,完成後就算是建立一個新的版本了。
git commit -a
:將暫存區所有檔案一起存入新 commit 。
git commit (欲放入 respository 的檔案) -m "敘述"
:不用進入 vim ,直接寫好 commit 的附註。
git commit -am "敘述"
:上面兩者的合體技,一起存入後寫好附註。
git log
會出現各個版本的歷史紀錄,比較新的 commit 會在上方,其中顯示的格式如下:
commit 457d6edf9bd8355014956d497f8617980b76cf9f #幾乎沒有意義的名稱
Author: 編輯者名字 <編輯者信箱>
Date: Fri Jun 19 17:38:55 2020 +0800 #最後修改時間
123 #說明
如果想看比較簡潔的,可以用 git log --oneline
,歷史紀錄會以 名稱前五碼 說明
的形式條列。
想切到某個版本,可以用:
git chechout (欲切回版本名稱)
如果要切回最新版本,可用 git checkout master
。
有些檔案(例如說明文件)雖然在資料夾內,但不太常修改,因此我們不想將它加入 Git 中,此時可以將它加入 .gitignore
中。
首先用 vim .gitignore
打開文字編輯器,進入 vim 後輸入欲加入的檔名後離開就算完成了。之後就算使用 git add .
也不會將這些檔案加入 Git 了。
git init
初始化 Git 。.gitignore
。git add .
將所有檔案放入暫存區(倉庫前廣場),然後再 git commit -m
建立新的版本(塞進倉庫)。commit -am
,但要注意若有新增的檔案,還請乖乖使用 git add
。git giff
可以看不同版本之間的差異。branch的概念和常用指令、github是什麼、以及其他 Git 狀況模擬。
Git 是一種版本控制的系統。結束。
——等等,那什麼又是版本控制?
玩遊戲時可以將進度存檔,要是之後被打死就可以回到這個紀錄時間點重來。另外像是寫報告時會出現報告_final.docx
、報告_final_final.docx
、報告_final_final_final.docx
,其實就是版本控制的概念。
工作時很多人負責同一個專案出問題,想知道誰在哪個時間點寫錯程式碼嗎?或是正在開發新功能同時需要修 bug ,兩者必須分開進行又不想刪掉手邊正在開發的新功能嗎?版本控制都能輕鬆讓你回到世界還很單純的時刻 (?)
無論是自己還是多人版本控制,東西太多時就會混亂,就需要有個系統幫忙進行版本控制,而 Git 就是這樣的一個系統。
在了解 Git 之前,先試著想想如果要設計一個管理版本控制的系統,會需要甚麼樣的架構?
同時管理兩個以上有關聯的檔案,如果每個檔案的版本分開紀錄,資料夾很有可能會變成這樣:
這個檔案.txt
這個檔案_版本二.txt
那個檔案.txt
那個檔案_版本二.txt
那個檔案_版本三.txt
然後你同事轉過來問你:「ㄟ當初那個檔案的版本二,是對應到這個檔案的哪個版本啊?」結果你也忘記,尷尬。
解決方法:
大家捆起來一起做版本紀錄,就像是把全部的東西放進資料夾裡面,最後主目錄會變成這樣:
專案資料夾(內含這個檔案.txt、那個檔案.txt)
專案_版本二資料夾(內含這個檔案.txt、那個檔案_版本二.txt)
專案_版本三資料夾(內含這個檔案_版本二.txt、那個檔案_版本三.txt)
五個人同時負責同個專案,版本名稱好像不能單純用數字命名了。
解決方法:
不要糾結命名方法了, Git 直接亂數命名,然後開一個檔案紀錄版本們的時間和修改者等資料,還有一個檔案紀錄哪個是最新版本。
推薦使用 Git Bash ,下載方式可參考我之前寫的: [DAY4] 初學Command Line
指令 | 功能 | 其他 |
---|---|---|
git init | 初始化 | 成功後會建立 .git 的隱藏資料夾 |
git status | 看狀態 | |
git add | 將檔案加入版本控制 | |
git rm --cached | 將檔案移除版本控制 | 若沒加 --cached 會直接刪掉檔案,要小心 |
git commit | 建立新的版本 | 最後可加 -m "敘述" 可對 commit 做說明 |
git log | 查看歷史紀錄 | |
git checkout | 回到某個版本 | |
git branch | 和 branch 有關 | 可直接加名稱新增branch |
-v 查看現在有哪些 branch |
||
-d 刪除 branch |
||
git branchout | 切換 branch | |
git merge | 合併 branch | 把 branch 合併進來 |
首先要讓 Git 連接資料夾,確認位置在目標資料夾後輸入:
git init
之後輸入 ls -a
查看所有檔案,會發現多了一個 .git
的隱藏資料夾,就表示初始化成功了。
git status
可查看現在 Git 內的狀況。
Git可以的工作區塊可以分成三個,分別是工作目錄 (Worling Diretory)
、暫存區 (Staging Area)
和 儲存區 (Respository)
。
可以把儲存區想成一個倉庫,每次有或從外面(工作目錄)送進來前,都會先放在倉庫前面的廣場(暫存區),都好了才開門一次放進倉庫,這樣除了倉庫門不用開開關關外,裡面的人也比較好清點貨物。
以下圖為例
On branch master
:目前沒有分支。
Changes to be committed
:將要提交的檔案。也就是在廣場捆好等倉庫門打開就可以直接運送進去的檔案。
Changes not staged for commit
:被更動尚未要提交的檔案。其實也在廣場,只是門打開時無法被送進去。
Untracked files
:未被追蹤的檔案。還沒進入廣場。
區域之間的關係和移動檔案的方法,可以用下圖解釋:
git add "欲加入檔案名稱"
git add
可將檔案從 untrscked 的狀態加入版本控制。
與之相反的是:
git rm "欲移出檔案名稱" --cached
記得一定要加 --cached
,不然檔案會被刪掉。
小技巧:
對象是所有檔案時,可使用.
或-all
,不用一個一個輸入。
兩者差異可參考:把檔案交給 Git 控管
細節:暫存區的檔案修改完後,要再 add
一次,不然不會 commit。
在廣場準備得差不多了,可以打開倉庫門將檔案存入做為新的版本:
git commit (欲放入 respository 的檔案)
enter 後會進入 vim ,這時可以輸入對這個新版本的敘述,完成後就算是建立一個新的版本了。
git commit -a
:將暫存區所有檔案一起存入新 commit 。
git commit (欲放入 respository 的檔案) -m "敘述"
:不用進入 vim ,直接寫好 commit 的附註。
git commit -am "敘述"
:上面兩者的合體技,一起存入後寫好附註。
git log
會出現各個版本的歷史紀錄,比較新的 commit 會在上方,其中顯示的格式如下:
commit 457d6edf9bd8355014956d497f8617980b76cf9f #幾乎沒有意義的名稱
Author: 編輯者名字 <編輯者信箱>
Date: Fri Jun 19 17:38:55 2020 +0800 #最後修改時間
123 #說明
如果想看比較簡潔的,可以用 git log --oneline
,歷史紀錄會以 名稱前五碼 說明
的形式條列。
想切到某個版本,可以用:
git chechout (欲切回版本名稱)
如果要切回最新版本,可用 git checkout master
。
有些檔案(例如說明文件)雖然在資料夾內,但不太常修改,因此我們不想將它加入 Git 中,此時可以將它加入 .gitignore
中。
首先用 vim .gitignore
打開文字編輯器,進入 vim 後輸入欲加入的檔名後離開就算完成了。之後就算使用 git add .
也不會將這些檔案加入 Git 了。
git init
初始化 Git 。.gitignore
。git add .
將所有檔案放入暫存區(倉庫前廣場),然後再 git commit -m
建立新的版本(塞進倉庫)。commit -am
,但要注意若有新增的檔案,還請乖乖使用 git add
。git giff
可以看不同版本之間的差異。branch的概念和常用指令、github是什麼、以及其他 Git 狀況模擬。
我們一般看到的視窗叫「圖形化介面」(Graphical User Interface,簡稱 GUI)。但早期還沒有圖形化界面之前,是透過一個文字視窗和電腦溝通,就是 Commend Line Interface ,簡稱 CLI ,其實就是用另一種方式對電腦下指令而已。
因為有些功能只能用 CLI ,而且有時用用文字比較快。
不用把 CLI 想得太難,我們其實很習慣用文字溝通了。例如在臉書還沒有「哈」的表情符號可以按之前,我們就會使用 XD
來表示笑臉了。
推薦使用 Git 附加的 Git Bash,可在官網下載:https://git-scm.com/ (點進 Download 後就會自動下載,之後一直按 Next 即可安裝好)
第二選擇是 Cmder。
另外也可以用 這個網站 模擬 Commit Line 結果。
以下所有指令其實都是程式,可以在電腦內找到對應的檔案。
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
pwd | 印出所在位置 | ls | 印出現在位置下檔案 |
cd | 切換資料夾 | clear | 清除頁面上所有資料 |
man | 使用說明書 |
Print Working Directory,印出所在位置。
如上圖,我現在所下的指令是在「/c/Users/asus zenbook」這個路徑底下。
LiSt,印出現在資料夾底下檔案。
ls 後方可加參數,例如ls -al
, a 就是「列出所有檔案,包含隱藏檔」, l 是用列表形式呈現檔案細部資料。
Change Directory,切換資料夾。
cd Videos
表示移動到現在位置下的資料夾 Videos, cd ..
是指回到上一層(就像在解壓縮檔裡面看到的點點)。另外資料夾前面的 ~
是 /c/Users/asus zenbook/
的簡寫。
把頁面上所有紀錄清空。
MANual,使用說明書。
後面加上指令名稱就會顯示詳細用法。
但若用 windows 系統(如圖),因為沒有這個指令,就會顯示不存在。
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
touch | 建立檔案或更改時間 | rm | 移除檔案 |
mkdir | 建立資料夾 | rmdir | 移除資料夾 |
mv | 移動檔案或改名 | cp | 複製檔案 |
功能:建立檔案或是更改時間
如上圖,對已經使用的檔案使用 touch
,會更改檔案最後更改時間。
如上圖,若本來沒有這個檔案,touch
會建立一個。
ReMove,移除檔案。
如圖,刪除 abc.txt 。
其他相關的指令:
rmdir
:刪除資料夾。
rm -r
:刪除資料夾及其底下的東西。
rm -f
:強制刪除(忽略有些檔案的保護),要小心使用。
MaKeDIRrctory,建立資料夾。
小訣竅:只打檔案前幾個字再使用 tab
,可以不用打完全名。
MoVe,移動檔案或改名。
mv 欲移動檔案 移動目的地
絕對路徑:從 root 開始存取的路徑,也就是對檔案內容中的「位置」欄。
相對路徑:從現在所在的位置開始存取的路徑。
當 mv 的目的地不存在時,就會創造出新的檔案並將欲移動檔案移過去,也就是將檔案重新命名,可以理解為:
mv 欲改名檔案 新名稱
CoPy,複製檔案。
cp 欲複製檔案 複製後名稱
cp -r
:複製的對象是資料夾時。
小訣竅:當操作對象是資料夾時,在指令後方直接加 dir
或 -r
。
vim 是大部分系統都會內建的文件編輯器,進入編輯器的語法如下:
vim 欲開啟檔案
一般模式:進到檔案後預設的模式。
編輯模式:在一般模式按 i
進入,之後按 esc
離開。這個模式可以自由編輯文件中的文字。
指令模式:在一般模式輸入 :
進入,之後按 esc
離開。可以對文件下指令,例如 w
為寫入 q
為離開 vim。
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
cat | 連接文件 | less | 分頁印出檔案 |
grep | 搜尋關鍵字 | wget | 下載檔案 |
curl | 顯示 response | top | 顯示 process |
date | 顯示現在時間 | echo | 在檔案上印出內容 |
cat 欲連接檔案1 欲連接檔案2
cat 欲印出檔案
cat
功能為連接兩個文件,若後方只有一個檔案,則可用來檢視文件內容。
若檔案內容太多,也可使用 less
分頁印出檔案。
less 欲分頁印出檔案
grep 關鍵字 目標文件
如上圖,會顯示目標文件 abc
中有關鍵字 w
的那行。
(有些系統似乎會將關鍵字標成不同顏色)
wget 欲下載的檔案網址
curl API網址
會顯示response。
Table Of Process,印出所有 process ,可用於看電腦狀況。
顯示今天日期和時間
印出內容的指令,常見的搭配是:
echo "欲輸入內容" > 目標檔案
如此一來可以不用進入 vim 就改動檔案內容。
作為重新導向 input 和 output 之用,例如:
ls -al > list_result
是將 ls -al
的結果輸出成一個叫 list_result
的檔案。值得注意的是,>
後方檔案若原本有內容,會被全部覆蓋掉。若不想被覆蓋,可使用 >>
,新的內容會出現在原本內容後。
pipe 其實就是|
這個符號。其功能是將左邊的輸入作為右邊輸入,例如:
其中 cat abc | grep w
的意思是將前面 cat abc
的結果輸入 grep w
,因此會顯示出和 grep w abc
一樣的結果。
其實都是很基本的東西,就記錄著作為以後忘記回頭查的參考吧。
]]>我們一般看到的視窗叫「圖形化介面」(Graphical User Interface,簡稱 GUI)。但早期還沒有圖形化界面之前,是透過一個文字視窗和電腦溝通,就是 Commend Line Interface ,簡稱 CLI ,其實就是用另一種方式對電腦下指令而已。
因為有些功能只能用 CLI ,而且有時用用文字比較快。
不用把 CLI 想得太難,我們其實很習慣用文字溝通了。例如在臉書還沒有「哈」的表情符號可以按之前,我們就會使用 XD
來表示笑臉了。
推薦使用 Git 附加的 Git Bash,可在官網下載:https://git-scm.com/ (點進 Download 後就會自動下載,之後一直按 Next 即可安裝好)
第二選擇是 Cmder。
另外也可以用 這個網站 模擬 Commit Line 結果。
以下所有指令其實都是程式,可以在電腦內找到對應的檔案。
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
pwd | 印出所在位置 | ls | 印出現在位置下檔案 |
cd | 切換資料夾 | clear | 清除頁面上所有資料 |
man | 使用說明書 |
Print Working Directory,印出所在位置。
如上圖,我現在所下的指令是在「/c/Users/asus zenbook」這個路徑底下。
LiSt,印出現在資料夾底下檔案。
ls 後方可加參數,例如ls -al
, a 就是「列出所有檔案,包含隱藏檔」, l 是用列表形式呈現檔案細部資料。
Change Directory,切換資料夾。
cd Videos
表示移動到現在位置下的資料夾 Videos, cd ..
是指回到上一層(就像在解壓縮檔裡面看到的點點)。另外資料夾前面的 ~
是 /c/Users/asus zenbook/
的簡寫。
把頁面上所有紀錄清空。
MANual,使用說明書。
後面加上指令名稱就會顯示詳細用法。
但若用 windows 系統(如圖),因為沒有這個指令,就會顯示不存在。
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
touch | 建立檔案或更改時間 | rm | 移除檔案 |
mkdir | 建立資料夾 | rmdir | 移除資料夾 |
mv | 移動檔案或改名 | cp | 複製檔案 |
功能:建立檔案或是更改時間
如上圖,對已經使用的檔案使用 touch
,會更改檔案最後更改時間。
如上圖,若本來沒有這個檔案,touch
會建立一個。
ReMove,移除檔案。
如圖,刪除 abc.txt 。
其他相關的指令:
rmdir
:刪除資料夾。
rm -r
:刪除資料夾及其底下的東西。
rm -f
:強制刪除(忽略有些檔案的保護),要小心使用。
MaKeDIRrctory,建立資料夾。
小訣竅:只打檔案前幾個字再使用 tab
,可以不用打完全名。
MoVe,移動檔案或改名。
mv 欲移動檔案 移動目的地
絕對路徑:從 root 開始存取的路徑,也就是對檔案內容中的「位置」欄。
相對路徑:從現在所在的位置開始存取的路徑。
當 mv 的目的地不存在時,就會創造出新的檔案並將欲移動檔案移過去,也就是將檔案重新命名,可以理解為:
mv 欲改名檔案 新名稱
CoPy,複製檔案。
cp 欲複製檔案 複製後名稱
cp -r
:複製的對象是資料夾時。
小訣竅:當操作對象是資料夾時,在指令後方直接加 dir
或 -r
。
vim 是大部分系統都會內建的文件編輯器,進入編輯器的語法如下:
vim 欲開啟檔案
一般模式:進到檔案後預設的模式。
編輯模式:在一般模式按 i
進入,之後按 esc
離開。這個模式可以自由編輯文件中的文字。
指令模式:在一般模式輸入 :
進入,之後按 esc
離開。可以對文件下指令,例如 w
為寫入 q
為離開 vim。
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
cat | 連接文件 | less | 分頁印出檔案 |
grep | 搜尋關鍵字 | wget | 下載檔案 |
curl | 顯示 response | top | 顯示 process |
date | 顯示現在時間 | echo | 在檔案上印出內容 |
cat 欲連接檔案1 欲連接檔案2
cat 欲印出檔案
cat
功能為連接兩個文件,若後方只有一個檔案,則可用來檢視文件內容。
若檔案內容太多,也可使用 less
分頁印出檔案。
less 欲分頁印出檔案
grep 關鍵字 目標文件
如上圖,會顯示目標文件 abc
中有關鍵字 w
的那行。
(有些系統似乎會將關鍵字標成不同顏色)
wget 欲下載的檔案網址
curl API網址
會顯示response。
Table Of Process,印出所有 process ,可用於看電腦狀況。
顯示今天日期和時間
印出內容的指令,常見的搭配是:
echo "欲輸入內容" > 目標檔案
如此一來可以不用進入 vim 就改動檔案內容。
作為重新導向 input 和 output 之用,例如:
ls -al > list_result
是將 ls -al
的結果輸出成一個叫 list_result
的檔案。值得注意的是,>
後方檔案若原本有內容,會被全部覆蓋掉。若不想被覆蓋,可使用 >>
,新的內容會出現在原本內容後。
pipe 其實就是|
這個符號。其功能是將左邊的輸入作為右邊輸入,例如:
其中 cat abc | grep w
的意思是將前面 cat abc
的結果輸入 grep w
,因此會顯示出和 grep w abc
一樣的結果。
其實都是很基本的東西,就記錄著作為以後忘記回頭查的參考吧。
]]>