├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── codewar.md ├── examples ├── README.md ├── common.md ├── week1 │ ├── README.md │ ├── github.sh │ ├── hw5.md │ └── num.sh ├── week10 │ ├── README.md │ └── restaurant-demo │ │ ├── faq.html │ │ ├── images │ │ ├── avatar.png │ │ ├── bg.jpg │ │ ├── f-001.png │ │ ├── f-002.png │ │ ├── first.jpg │ │ ├── tv.jpg │ │ └── yt.jpg │ │ ├── index.html │ │ ├── lottery.html │ │ └── style.css ├── week11 │ ├── README.md │ ├── challenge.md │ └── hw2 │ │ ├── php │ │ ├── admin.php │ │ ├── check_permission.php │ │ ├── create_post.php │ │ ├── handle_create_post.php │ │ ├── handle_delete.php │ │ ├── handle_login.php │ │ ├── handle_update_post.php │ │ ├── header.php │ │ ├── index.php │ │ ├── login.php │ │ ├── logout.php │ │ ├── normalize.css │ │ ├── post.php │ │ ├── style.css │ │ ├── update_post.php │ │ └── utils.php │ │ └── static │ │ ├── admin.html │ │ ├── blog.html │ │ ├── edit.html │ │ ├── index.html │ │ ├── login.html │ │ ├── normalize.css │ │ └── style.css ├── week12 │ ├── README.md │ ├── hw1 │ │ ├── api_add_comments.php │ │ ├── api_comments.php │ │ └── index.html │ └── hw2 │ │ ├── README.md │ │ ├── add_todo.php │ │ ├── get_todo.php │ │ ├── index.html │ │ ├── todo-done.html │ │ ├── todo-extract-data.html │ │ ├── todo-ui-only.html │ │ └── todo-v1.html ├── week13 │ └── README.md ├── week14 │ └── README.md ├── week16 │ └── README.md ├── week17 │ └── README.md ├── week18 │ └── README.md ├── week19 │ └── README.md ├── week2 │ ├── README.md │ ├── challenges.md │ └── examples.md ├── week21 │ └── README.md ├── week22 │ └── README.md ├── week23 │ └── README.md ├── week24 │ └── README.md ├── week3 │ ├── README.md │ └── challenges.md ├── week4 │ ├── README.md │ ├── challenge.js │ ├── hw1.js │ ├── hw2.js │ ├── hw3.js │ ├── hw4.js │ ├── package-lock.json │ ├── package.json │ ├── race-condition.js │ ├── race.png │ ├── twitch.js │ └── twitch2.js ├── week5 │ ├── README.md │ ├── bus.js │ └── send.js ├── week6 │ ├── README.md │ ├── examples.md │ ├── hw1 │ │ ├── avatar.png │ │ ├── bg.jpg │ │ ├── f-001.png │ │ ├── f-002.png │ │ └── index.html │ └── hw2 │ │ └── index.html ├── week7 │ ├── README.md │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ └── hw3 │ │ └── index.html ├── week8 │ ├── README.md │ ├── backup.md │ ├── challenge.md │ ├── examples.md │ ├── hw1 │ │ ├── bg.jpg │ │ ├── first.jpg │ │ ├── index.html │ │ ├── tv.jpg │ │ └── yt.jpg │ ├── hw2 │ │ ├── bg.jpg │ │ ├── bg1.jpg │ │ ├── demo_after.html │ │ ├── demo_before.html │ │ ├── index.html │ │ ├── logo.png │ │ ├── normal.html │ │ └── preview.jpg │ └── week8-challenge.html └── week9 │ └── README.md ├── homeworks ├── week1 │ ├── README.md │ ├── hw1.md │ ├── hw3.md │ ├── hw4.md │ └── hw5.md ├── week10 │ ├── README.md │ └── hw1.md ├── week11 │ ├── README.md │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ └── hw3.md ├── week12 │ ├── README.md │ ├── comments.png │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ ├── hw3.md │ └── todo.png ├── week13 │ ├── README.md │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ ├── hw3 │ │ └── index.html │ └── hw4.md ├── week14 │ ├── README.md │ ├── hw2.md │ └── hw3.md ├── week15 │ ├── README.md │ └── hw1.md ├── week16 │ ├── README.md │ ├── hw1.md │ ├── hw2.md │ ├── hw3.md │ ├── hw4.md │ └── hw5.md ├── week17 │ ├── README.md │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ └── hw3.md ├── week18 │ ├── README.md │ ├── hw1 │ │ └── index.html │ └── hw3.md ├── week19 │ ├── README.md │ └── hw1.md ├── week2 │ ├── README.md │ ├── hw1.js │ ├── hw2.js │ ├── hw3.js │ ├── hw4.js │ ├── hw5.js │ └── hw6.md ├── week20 │ ├── README.md │ └── hw1.md ├── week21 │ ├── README.md │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ ├── hw3 │ │ └── index.html │ └── hw4.md ├── week22 │ ├── README.md │ ├── hw1 │ │ └── index.html │ └── hw2.md ├── week23 │ ├── README.md │ ├── hw1 │ │ └── index.html │ └── hw2.md ├── week24 │ ├── README.md │ ├── hw1 │ │ └── index.html │ └── hw2.md ├── week3 │ ├── README.md │ ├── hw1.js │ ├── hw2.js │ ├── hw3.js │ ├── hw4.js │ ├── hw5.js │ └── hw6.md ├── week4 │ ├── README.md │ ├── hw1.js │ ├── hw2.js │ ├── hw3.js │ ├── hw4.js │ └── hw5.md ├── week5 │ ├── README.md │ └── hw1.md ├── week6 │ ├── README.md │ ├── form.png │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ ├── hw3.md │ ├── res1.png │ ├── res2.png │ └── res3.png ├── week7 │ ├── README.md │ ├── carousel.gif │ ├── faq.png │ ├── form.png │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ ├── hw3 │ │ └── index.html │ ├── hw4.md │ └── todo.gif ├── week8 │ ├── README.md │ ├── card.gif │ ├── hw1 │ │ └── index.html │ ├── hw2 │ │ └── index.html │ ├── hw3.md │ ├── lol.png │ ├── menu.gif │ ├── p1.png │ └── p2.png └── week9 │ ├── README.md │ ├── board.png │ ├── hw1 │ └── index.html │ └── hw2.md ├── package-lock.json └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | jest: true 7 | }, 8 | extends: 'airbnb', 9 | globals: { 10 | Atomics: 'readonly', 11 | SharedArrayBuffer: 'readonly', 12 | }, 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | ecmaVersion: 2018, 18 | }, 19 | plugins: [ 20 | 'react', 21 | ], 22 | rules: { 23 | "no-console": "off" 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | conn.php 3 | .DS_STORE -------------------------------------------------------------------------------- /codewar.md: -------------------------------------------------------------------------------- 1 | # Codewar 練習題 2 | 3 | Codewar 是一個程式解題平台,上面充滿著各種開發者出的題目,會使用這個平台的理由為: 4 | 5 | 1. 可以自己寫測試驗證基本測資 6 | 2. 過關後可以看到其他人的解答 7 | 3. 方便導師追蹤解題成效 8 | 9 | ## 注意事項 10 | 11 | 解題最重要的一點就是:絕對不要輕易看答案。為什麼解題能夠成長?是因為你有思考,思考過後想出來的答案才是你的,如果你放棄了然後去看別人的答案,那就始終是別人的,不會是你自己的。 12 | 13 | 想了一陣子還是想不出來的話,不用急,去洗個澡或是散個步搞不好就想通了(真心不騙) 14 | 15 | 若是認真想過還是想不出來,這時候可以先尋求一些提示,如果還是沒頭緒,這時候才能去查解答。但看完解答之後務必理解,並且重新再測驗一遍。 16 | 17 | 做完之後請自行把標題的 ❌ 換成 ✅。 18 | 19 | ## 題目列表 20 | 21 | 以下按照題目難度分類 22 | 23 | ## 零顆星(超簡單) 24 | 25 | ### ❌ Opposite number 26 | 題目連結:https://www.codewars.com/kata/opposite-number/javascript 27 | 題目說明:正數變負數,反之亦然 28 | 29 | ### ❌ Even or Odd 30 | 題目連結:https://www.codewars.com/kata/even-or-odd/javascript 31 | 題目說明:判斷是奇數或是偶數 32 | 33 | ## 一顆星(熟悉語法) 34 | 35 | ### ❌ Number-Star ladder 36 | 題目連結:https://www.codewars.com/kata/number-star-ladder/javascript 37 | 題目說明: 38 | 這題就是依照規律輸出文字,沒什麼好講的 39 | 40 | ### ❌ Who likes it 41 | 題目連結:https://www.codewars.com/kata/who-likes-it 42 | 題目說明:模擬 Facebook 按讚時或出現的文字 43 | 44 | ### ❌ String repeat 45 | 題目連結:https://www.codewars.com/kata/string-repeat/javascript 46 | 題目說明:回傳重複 n 遍的字串 47 | 48 | ### ❌ Build Tower 49 | 題目連結:https://www.codewars.com/kata/build-tower 50 | 題目說明: 51 | 也是依照規律輸出文字即可 52 | 53 | ### ❌ Reversed Strings 54 | 題目連結:https://www.codewars.com/kata/reversed-strings/javascript 55 | 題目說明: 56 | 把輸入的文字反轉過後回傳,如果想挑戰自己的話,可以試試看用陣列的各種內建函式組合完成 57 | 58 | ### ❌ Reversed Words 59 | 題目連結:https://www.codewars.com/kata/reversed-words 60 | 題目說明: 61 | 這一題是進階版的字串反轉,原本的只要把每個「字元」反轉,這個則是要把每個「單字」反轉。 62 | 63 | ### ❌ Alternate case 64 | 題目連結:https://www.codewars.com/kata/alternate-case 65 | 題目說明:把大寫字母轉成小寫,小寫字母轉成大寫 66 | 67 | ### ❌ You only need one - Beginner 68 | 題目連結:https://www.codewars.com/kata/you-only-need-one-beginner/javascript 69 | 題目說明:回傳要找的元素是否在陣列裡面 70 | 71 | ### ❌ Find the capitals 72 | 題目連結:https://www.codewars.com/kata/find-the-capitals-1/javascript 73 | 題目說明:回傳大寫字母所在的 index 74 | 75 | ### ❌ Sum arrays 76 | 題目連結:https://www.codewars.com/kata/sum-arrays/javascript 77 | 題目說明:把陣列加總回傳結果 78 | 79 | ### ❌ Find the smallest integer in the array 80 | 題目連結:https://www.codewars.com/kata/find-the-smallest-integer-in-the-array 81 | 題目說明:找出陣列中最小的數字 82 | 83 | ## 兩顆星(需要花點時間思考) 84 | 85 | ### ❌ Shortest Word 86 | 題目連結:https://www.codewars.com/kata/shortest-word/javascript 87 | 題目說明:回傳最短的單字的長度 88 | 89 | ### ❌ Bit Counting 90 | 題目連結:https://www.codewars.com/kata/bit-counting/javascript 91 | 題目說明:計算 bit 的總數 92 | 93 | ### ❌ Find The Parity Outlier 94 | 題目連結:https://www.codewars.com/kata/find-the-parity-outlier/javascript 95 | 題目說明:全部的數字裡,只有一個的奇偶跟其他的不一樣,你要找出這個數字 96 | 97 | ### ❌ Take a Ten Minute Walk 98 | 題目連結:https://www.codewars.com/kata/take-a-ten-minute-walk/javascript 99 | 題目說明:有一個人他可以往東南西北這四個方向走,請幫他計算它能否剛好在十步的時候回到原點 100 | 101 | ### ❌ Tribonacci Sequence 102 | 題目連結:https://www.codewars.com/kata/tribonacci-sequence/javascript 103 | 題目說明:費式數列的進階版 104 | 105 | ### ❌ A Man and his Umbrellas 106 | 題目連結:https://www.codewars.com/kata/a-man-and-his-umbrellas/javascript 107 | 題目說明: 108 | 這題需要花多一點時間去思考。 109 | 110 | input 會給你每天的氣象預報,基本上就是下雨跟沒下雨。如果早上下雨,那就會從家裡帶一把傘去公司,如果家裡沒傘的話需要買一把。如果晚上下雨,必須要從公司帶一把傘回家。如果公司沒傘,必須去買一支傘。 111 | 112 | 你要輸出的結果就是:總共需要買幾支傘才行。 113 | 114 | 舉例來說:`["rainy", "clear", "rainy", "cloudy"]`,就是第一天早上下雨,所以要買第一把傘到公司,回家的時候沒下雨,所以把傘放在公司。而第二天早上又下雨,家裡沒傘,需要買第二把傘,因此答案是 2。 115 | 116 | `["rainy", "rainy", "rainy", "rainy", "thunderstorms", "rainy"]`的話,每一天的早上跟晚上都在下雨,所以只要買一把傘就可以從家裡到公司,再從公司帶回家裡。 117 | 118 | ### ❌ Check if two words are isomorphic to each other 119 | 題目連結:https://www.codewars.com/kata/check-if-two-words-are-isomorphic-to-each-other 120 | 題目說明: 121 | 這題比較複雜一點,如果兩個字串 A 跟 B 存在「一對一關係」,那我們就可以說這兩個字串是同構(isomorphic)的。 122 | 123 | 舉例來說,ABB 跟 CDD,A 對應到 C,B 對應到 D,存在一對一的關係,所以是同構的。 124 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 這個資料夾底下是每一週的「自我檢討」以及「參考解答」,或比起參考解答,更精確地說法是:「我的解答」。這邊底下每個資料夾都代表某一週,點進去之後會直接看到 `README.md`,就是那一週的「自我檢討」,其他內容則會是「參考解答」,基本上會用跟作業相同的檔名。 4 | 5 | 有關於寫作業以及交作業,順序為: 6 | 7 | 1. 寫作業 8 | 2. 寫完要交作業之前,觀看當週的「自我檢討」 9 | 3. 根據自我檢討的內容修改作業 10 | 4. 繳交作業 11 | 5. 交完作業以後觀看「參考解答」來學習我的解法 12 | 13 | 底下有針對自我檢討以及參考解答的詳細說明。 14 | 15 | ## 自我檢討 16 | 17 | 在繳交每週作業「之前」,請務必觀看那週的「自我檢討」,因為在自我檢討裡面會有一些常見的錯誤以及注意事項。 18 | 19 | 自我檢討就是讓你在「寫完作業以後」但是「交作業前」看的,讓你能夠先訂正一些常見錯誤。之所以會有自我檢討,一方面是為了省下助教以及老師改作業的時間,貫徹工程師的「懶人」精神 —— DRY,Don't Repeat Yourself。 20 | 21 | 如果一個錯誤每一個學生幾乎都會犯,那就沒有必要每個學生都再改一次,而是統一提供一個「常見錯誤」的說明,讓大家自己看著修正即可。 22 | 23 | 因此,請大家在寫完作業以後先來看自我檢討,並且「修正完錯誤」之後再交作業。我們改作業的時候如果發現這些常見錯誤沒有修掉,很有可能不會特別提醒,因為我們會預設大家都有先自我檢討過才交作業。 24 | 25 | 自我檢討的檔案會放在 exmaples/ 的資料夾裡面,通常都是 README.md。 26 | 27 | ## 參考解答 28 | 29 | 參考解答觀看的時機點是「交完作業之後」,所以在交作業前請勿觀看,以免破壞學習樂趣。 30 | 31 | 參考解答主要是給想要更精進的同學看的,基本上就會是我個人對當週作業的程式碼以及一些說明。若你只是題目解不出來,建議先參考同學的程式碼,他的程度會跟你的比較相近。 32 | 33 | 最後再強調一次,有關於寫作業以及交作業,順序為: 34 | 35 | 1. 寫作業 36 | 2. 寫完要交作業之前,觀看當週的「自我檢討」 37 | 3. 根據自我檢討的內容修改作業 38 | 4. 繳交作業(確認自我檢討完以後有修正常見錯誤才交作業) 39 | 5. 交完作業以後觀看「參考解答」來學習我的解法 40 | 41 | 另外,並不是每一週都會有自我檢討或是參考解答,會視當週的狀況來決定。 42 | 43 | 參考解答也會放在 examples 資料夾裡面,你要自己點開各個檔案才會知道內容是什麼,通常可以從檔案名稱或是資料夾名稱猜測是哪個作業。 44 | 45 | ## 常見重點整理 46 | 47 | 請參考[常見重點整理](common.md) -------------------------------------------------------------------------------- /examples/week1/README.md: -------------------------------------------------------------------------------- 1 | # Week1 作業自我檢討 2 | 3 | 請注意,當週自我檢討只有 hw1,參考解答只有 hw5 以及挑戰題。 4 | 5 | ### hw1:交作業流程 6 | 7 | 1. 新開一個 branch:`git branch hw1` 8 | 2. 切換到 branch: `git checkout hw1` 9 | 3. 寫作業 10 | 4. 如果有新增的檔案,記得加進去 git: `git add .` 11 | 5. 提交改動:`git commit -am "hw1"` 12 | 6. 推到 GitHub:`git push origin hw1` 13 | 7. 到自己的 repo 去,並且發起 PR(Pull Request) 14 | 8. 把 PR 的連結複製起來,並且在學習系統上繳交作業 15 | 16 | 等作業改完並且 merge 以後: 17 | 18 | 1. 切換到 master:`git checkout master` 19 | 2. 把最新的改動拉下來:`git pull origin master` 20 | 3. 刪除已經 merge 的 branch:`git branch -d hw1` 21 | 22 | 如果交作業步驟有錯的麻煩自己再修正一下,感謝~ 23 | 24 | 然後要特別提醒幾件事情,第一件是課程中把 Git 的不同 branch 比喻為「不同的資料夾」,只是為了讓初學者比較方便去理解版本控制的概念。 25 | 26 | 實際上 Git 在運行時,不會真的每開一個 branch 就開一個資料夾,也不會每一個 commit 就複製一次檔案。如果真的是這樣,那假設你有一個 10MB 的檔案,做完 100 個 commit 複製完 100 次之後不就超級大了嗎? 27 | 28 | 因此,聰明的 Git 真正儲存的是「檔案的差異」,例如說你在檔案第二行插入了一行文字:「hello」,Git 就只會記住「檔案第二行插入 Hello」這件事情,並不會複製整個檔案。那 Git 要怎麼知道某個時間點的檔案長什麼樣子?就把原始檔案再重新套用一次每個 commit 的差異,不就是那個時間點的樣子嗎?(不過 Git 在這件事情上應該會做一些優化就是了) 29 | 30 | 所以大家一定要清楚地知道,Git 真正儲存的是檔案的差異。然後第一次 `git init` 的時候的確是會把整份檔案都記起來,才知道最一開始長什麼樣子。對於每一個檔案,Git 也都有自己的壓縮演算法去壓縮,所以比你的檔案還小是正常的。 31 | 32 | 再來還有一件事情很重要,那就是 master 就只是「預設的 branch」,或者通常是最主要的 branch,但這並不代表它是「最新的 branch」。舉例來說,你也可以切出一條 branch 叫做「test」,然後一直往 test 加東西,但是從來都不合併回 master,那 test 就是在你專案裡的最新的 branch。所以 master 就只是一條預設的 branch 而已,最新的東西不一定在上面。 33 | 34 | 另外,發 PR 的時候如果有東西你想改,其實你可以自己在電腦上改完以後 commit 再 push,GitHub 上的 PR 就會自動更新了,你什麼都不用做,更不用把 PR 關掉再開起來。因為 PR 的主體是「branch」,所以 branch 更新了,PR 的內容自然也會一起更新。 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/week1/github.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 附註: 4 | # 底下寫法其實滿差的,因為一旦原始的資料變了或是欄位的順序錯了就會掛掉 5 | # 但一時找不太到支援度高的寫法怎麼寫,所以只能先求有再來求好QQ 6 | 7 | username=$1; 8 | 9 | data=$(curl --silent https://api.github.com/users/$username); 10 | echo $data | grep -o '"name": ".*", "company' | sed 's/"name": "//g' | sed 's/", "company//g'; 11 | echo $data | grep -o '"bio": ".*", "twitter_username' | sed 's/"bio": "//g' | sed 's/", "twitter_username//g'; 12 | echo $data | grep -o '"location": ".*", "email' | sed 's/"location": "//g' | sed 's/", "email//g'; 13 | echo $data | grep -o '"blog": ".*", "location' | sed 's/"blog": "//g' | sed 's/", "location//g'; 14 | -------------------------------------------------------------------------------- /examples/week1/hw5.md: -------------------------------------------------------------------------------- 1 | # hw5:簡答題 2 | 3 | ## 請解釋後端與前端的差異。 4 | 5 | 前端負責顯示畫面,只要是你看得到的東西基本上都是前端,後端則是負責處理那些與資料庫的溝通等等。 6 | 7 | 所以假設今天官網要新增一個頁面或者是修改背景顏色,這都是看得到的,就會交給前端來處理。但如果是要新增一個會員,那就會需要後端在資料庫裡面幫忙新增。 8 | 9 | 不過有關新增會員這個動作,其實也可以是前後端交互的結果。 10 | 11 | 1. 打開註冊頁面(前端) 12 | 2. 填寫資料(前端) 13 | 3. 送出表單,這個資料就會從前端傳到後端伺服器 14 | 4. 後端伺服器把資料存進資料庫,並且回傳 response 15 | 5. 前端顯示 response 16 | 17 | ## 假設我今天去 Google 首頁搜尋框打上:JavaScript 並且按下 Enter,請說出從這一刻開始到我看到搜尋結果為止發生在背後的事情。 18 | 19 | 1. 瀏覽器送出關鍵字「JavaScript」到 Google 的 Server 20 | 2. Google server 去資料庫查詢關鍵字,並且取得搜尋結果 21 | 3. Google server 把搜尋結果回傳 22 | 4. 瀏覽器顯示搜尋結果 23 | 24 | 基本上是這樣子,但要複雜可以更複雜,我覺得以第一週來說,只要能簡單區分前後端即可。 25 | 26 | 不過我發現有滿多同學有其中一段有點搞混,因此我這邊統一再解釋一遍。 27 | 28 | 有關於「把網域(domain)轉換成 IP 位置」這件事,我們把它稱之為 DNS 解析(resolve DNS),之前在影片中有提過會去 DNS Server 問,例如說: 29 | 30 | 1. 我想知道 google.com 的 IP 位置 31 | 2. 因此我發一個 request 給 DNS Server 32 | 3. DNS Server 回覆:172.217.160.78 33 | 4. 我現在知道它在哪裡了 34 | 35 | 而我上面提到的這個「DNS Server」,它也是一台 Server 嘛,所以它也是有位置的!但是因為他要負責解析 DNS,如果這個主機也是用一個 domain 來表示,那不就變成雞生蛋,蛋生雞的問題嗎?假設這個 DNS server 的位置是:dns.a.com 好了,那我要發 request 去 google.com,就要先去問 dns.a.com,可是我要知道怎麼去 dns.a.com,又要問 dns.a.com,如此就沒完沒了,根本沒辦法去問。 36 | 37 | 因此,DNS server 都是直接用 IP 位置來表示。 38 | 39 | 中華電信的 DNS Server IP 位置通常是 `168.95.1.1` 與 `168.95.192.1`,而 Google 也有提供免費的 DNS Server,IP 位置是 `8.8.8.8`,另一個網站 Cloudflare 也有一個,位置是 `1.1.1.1`,都非常好記。 40 | 41 | 所以之前在課程上的舉例不太對,因為我偷懶在舉例時直接把 Google 的 IP 位置說是 8.8.8.8,這樣容易讓人把「Google 的後端 Server」與「Google 的 DNS Server」搞混。 42 | 43 | 所以有了這個概念以後,假設我們電腦使用的是 Google 的 DNS,那我們這一題的流程可以修正為: 44 | 45 | 1. 瀏覽器送出關鍵字「JavaScript」到 Google 的 Server 46 | 2. 去 DNS Server(8.8.8.8)問說 google.com 在哪裡 47 | 3. DNS Server(8.8.8.8) 回傳 172.217.160.78 48 | 4. 瀏覽器發送 request 給 172.217.160.78 49 | 5. Google server 收到資料,去資料庫查詢關鍵字,並且取得搜尋結果 50 | 6. Google server 把搜尋結果回傳 51 | 7. 瀏覽器顯示搜尋結果 52 | 53 | 關於第五步:「Google server 收到資料,去資料庫查詢關鍵字,並且取得搜尋結果」我也要特別聲明一下,這邊跟「爬蟲」沒什麼關係。爬蟲不是在你搜尋的時候才跑的,而是無時無刻不在跑,每分每秒都有機器人在爬網站,然後把網站的內容分析過後存到資料庫裡面去。而你搜尋的時候只是從這個資料庫去搜尋而已,跟爬蟲是沒什麼關係的。 54 | 55 | 接著,我上面其實沒有把「主體是誰」講得很清楚,DNS 解析這一段到底是誰在做?是瀏覽器會做嗎?還是瀏覽器不管這個? 56 | 57 | 首先呢,以 Chrome 來說,基本上很多程式碼都是用 C 語言寫成的,而 C 語言裡面有提供一些函式,例如說:`gethostbyname`、`getaddrinfo`或是`getnameinfo`,就是讓你來拿到 domain 相關的資訊。 58 | 59 | 所以當我們在瀏覽器輸入 google.com 按下 enter 的時候,瀏覽器會呼叫這些程式碼並且傳入 google.com,然後 C 語言會去呼叫作業系統的東西,送出 request 然後去 DNS Server 查詢資料,再把結果回傳給瀏覽器。 60 | 61 | 然後有一個同學問了一個很好的問題: 62 | 63 | > 可是當我們在 google 打關鍵字搜尋時,我們已經在 google.com 啦,那還需要問 server 在哪裡嗎? 64 | 65 | 答案是或許需要,也或許不需要。在電腦科學的領域中有一個詞你會很常聽到,英文叫做 Cache(發音像是 Cash 不是 Catch),台灣的翻譯是「快取」,中國那邊的翻譯則是「緩存」,簡單來說就是一個暫存資訊的地方。對於有些可能沒那麼常變動的東西,就會把結果放在這邊,加快存取的速度。 66 | 67 | 而這個快取在很多地方都會有,不只一個地方。 68 | 69 | 先以瀏覽器為例,瀏覽器有自己的 DNS Cache。所以假設你要問的網址已經在瀏覽器的 DNS Cache 裡了,那就不會再問一次,瀏覽器就會直接知道說 Request 要發到哪個 IP 位置。Cache 如果有資料我們叫做 Hit,沒有的話叫做 Miss。 70 | 71 | 而作業系統也有自己的 DNS Cache,如果瀏覽器的快取 miss 了,就會去看作業系統的 Cache,hit 的話就把結果傳回去,一樣不會去問 DNS Server。但如果作業系統的也 miss 了,就會真的發 request 去 DNS Server,問說到底這個網域是對應到哪個 IP 位置。 72 | 73 | 在我們又理解的更深以後,就可以把流程改成這樣: 74 | 75 | 1. 瀏覽器送出關鍵字「JavaScript」到 google.com 76 | 2. 瀏覽器檢查 dns cache 有沒有 google.com 77 | 3. 有的話直接發送 request 給那個位置 78 | 4. 沒有的話呼叫 C 語言提供的 function(例如說 gethostbyname) 79 | 5. C 語言呼叫作業系統 80 | 6. 作業系統檢查 dns cache 有沒有 google.com 81 | 7. 有的話直接回傳位置 82 | 8. 沒有的話去 DNS Server(8.8.8.8)問說 google.com 在哪裡 83 | 9. DNS Server(8.8.8.8)回傳 172.217.160.78 84 | 10. 瀏覽器發送 request 給 172.217.160.78 85 | 11. Google server 收到資料,去資料庫查詢關鍵字,並且取得搜尋結果 86 | 12. Google server 把搜尋結果回傳 87 | 13. 瀏覽器顯示搜尋結果 88 | 89 | 差不多就是這樣,我覺得這題理解到這個程度就行了。 90 | 91 | 如果想看複雜的版本,可以參考: 92 | 93 | 1. [从输入 URL 到页面加载完成的过程中都发生了什么事情?](http://fex.baidu.com/blog/2014/05/what-happen/) 94 | 2. [从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!](https://segmentfault.com/a/1190000013662126) 95 | 3. [An attempt to answer the age old interview question "What happens when you type google.com into your browser and press enter?"](https://github.com/alex/what-happens-when) 96 | 4. [上面這個的簡體中文版](https://github.com/skyline75489/what-happens-when-zh_CN) 97 | 98 | ## 請列舉出 3 個「課程沒有提到」的 command line 指令並且說明功用。 99 | 100 | 沒辦法,我覺得好用的我在課程裡面好像都講到了QQ 101 | -------------------------------------------------------------------------------- /examples/week1/num.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for (( i=1; i<=$1; i=i+1 )) 4 | do 5 | touch "${i}.js"; 6 | done 7 | echo "檔案建立完成"; -------------------------------------------------------------------------------- /examples/week10/README.md: -------------------------------------------------------------------------------- 1 | # Week10 show time! 2 | 3 | ## 餐廳官網 4 | 5 | 程式碼放在檔案 `restaurant-demo` 資料夾裡面。 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/faq.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 餐廳官網 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
29 |
30 |

31 | FAQ - 常見問題 32 |

33 | 34 |
35 |
36 |
37 | Q0: 38 |
39 |
40 |

如何辦理退貨

41 |

線上刷卡的使用方式也超簡單,只要您確認您當次的購物清單與金額都無誤,就可立即到匯款上傳系統,登入個人帳號密碼後,點選"線上刷卡方式",確認收件住址後,再進行刷卡確認程序即可~

42 |
43 |
44 |
45 |
46 | Q1: 47 |
48 |
49 |

如何辦理退貨

50 |

線上刷卡的使用方式也超簡單,只要您確認您當次的購物清單與金額都無誤,就可立即到匯款上傳系統,登入個人帳號密碼後,點選"線上刷卡方式",確認收件住址後,再進行刷卡確認程序即可~

51 |
52 |
53 |
54 |
55 | Q2: 56 |
57 |
58 |

如何辦理退貨

59 |

線上刷卡的使用方式也超簡單,只要您確認您當次的購物清單與金額都無誤,就可立即到匯款上傳系統,登入個人帳號密碼後,點選"線上刷卡方式",確認收件住址後,再進行刷卡確認程序即可~

60 |
61 |
62 |
63 |
64 | Q3: 65 |
66 |
67 |

如何辦理退貨

68 |

線上刷卡的使用方式也超簡單,只要您確認您當次的購物清單與金額都無誤,就可立即到匯款上傳系統,登入個人帳號密碼後,點選"線上刷卡方式",確認收件住址後,再進行刷卡確認程序即可~

69 |
70 |
71 |
72 |
73 | Q4: 74 |
75 |
76 |

如何辦理退貨

77 |

線上刷卡的使用方式也超簡單,只要您確認您當次的購物清單與金額都無誤,就可立即到匯款上傳系統,登入個人帳號密碼後,點選"線上刷卡方式",確認收件住址後,再進行刷卡確認程序即可~

78 |
79 |
80 |
81 | 82 | 83 |
84 |
85 | 86 | 105 | 132 | 133 | -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/avatar.png -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/bg.jpg -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/f-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/f-001.png -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/f-002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/f-002.png -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/first.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/first.jpg -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/tv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/tv.jpg -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/images/yt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week10/restaurant-demo/images/yt.jpg -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 餐廳官網 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 32 |
33 |
34 |

35 | 當輕食之風盛起 36 |

37 |

38 | 由於現代人生活忙碌,常以外食為主,當高糖、高鹽、高油成為人們的日常,會導致慢性疾病與肥胖的機率大增。 39 |
40 | 我們定期走訪農田,選用最天然、營養的食材,以簡單的烹調方式處理,最大化忠實呈現食材原本的鮮味。 41 |
42 | 咬一口不只賣餐點,我們希望能夠讓大家愛上料理的魅力,餐館特意打造全開放式廚房,烹調透明化,也不定時舉辦美食廚房DIY,讓大家體驗不需要過多調味,就能擁有千變萬化的食物口感。 43 |

44 |
45 |
46 | 我要點餐 47 |
48 |
49 | 查詢訂單 50 |
51 |
52 |
53 |
54 | 55 |
56 |

57 | Menu 搶先看 58 |

59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |

69 | 吃過都說好 70 |

71 |
72 |
73 |
74 |
75 | 76 |
77 |

國父桑

78 |
79 |
80 | 我一生疲於建國,去過日本,去過歐洲,卻從未到過台北101,也從來沒有吃過這麼好吃的東西...今天在時空旅行者的幫助下我終於吃到了...此生再無遺憾 QAQ!! 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |

國父桑

89 |
90 |
91 | 我一生疲於建國,去過日本,去過歐洲,卻從未到過台北101,也從來沒有吃過這麼好吃的東西...今天在時空旅行者的幫助下我終於吃到了...此生再無遺憾 QAQ!! 92 |
93 |
94 |
95 |
96 |
97 | 98 |
99 |

國父桑

100 |
101 |
102 | 我一生疲於建國,去過日本,去過歐洲,卻從未到過台北101,也從來沒有吃過這麼好吃的東西...今天在時空旅行者的幫助下我終於吃到了...此生再無遺憾 QAQ!! 103 |
104 |
105 |
106 |
107 |
108 |

109 | 我們在哪裡 110 |

111 | 112 |
113 |
114 | 133 | 134 | -------------------------------------------------------------------------------- /examples/week10/restaurant-demo/lottery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 餐廳官網 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
29 |
30 |
31 | 2020 夏日輕盈特賞! 抽獎活動辦法 32 |
33 |
34 |
35 | 活動期間: 36 |
37 |
38 | 2020/06/01~2020/07/01 39 |
40 |
41 |
42 |
43 | 活動說明: 44 |
45 |
46 | 今天老闆佛心來著決定給大家發獎勵,有看有機會,沒看只能幫QQ!只要在店內消費滿1000000元即有機會獲得 - 頭獎日本東京來回雙人遊! 47 |
48 |
49 |
50 |
51 | 獎  品: 52 |
53 |
54 | ❤ 頭獎一名:日本東京來回雙人遊(市價14990元)
55 | ❤ 貳獎三名:90 吋電視一台(市價5990元)
56 | ❤ 參獎十名:知名 YouTuber 簽名握手會入場券一張(市價1500元)
57 |
58 |
59 |
60 | 我要抽獎 61 |
62 |
63 |
64 |

恭喜你中頭獎了!日本東京來回雙人遊!

65 |
66 | 我要抽獎 67 |
68 |
69 |
70 | 89 | 160 | 161 | -------------------------------------------------------------------------------- /examples/week11/challenge.md: -------------------------------------------------------------------------------- 1 | # Week11 作業挑戰題 2 | 3 | 在 hw1 的延伸挑戰題中我們需要更強的身份系統,這就是考驗你 table design 功力的時刻了。 4 | 5 | 在 users 上面一樣會放一個 roleId,但重點是你必須引進一個新的概念叫做 permissions: 6 | 7 | | id | entity | action | 8 | |----|---------|------------| 9 | | 1 | comment | view | 10 | | 2 | comment | delete | 11 | | 3 | comment | delete_all | 12 | | 4 | comment | update | 13 | | 5 | comment | update_all | 14 | | 6 | comment | create | 15 | 16 | 裡面每一個 item 都表示著一個 entity 的行為。然後還要另外開一個 table 叫做 roles,儲存每一個 role: 17 | 18 | | id | name | 19 | |----|---------| 20 | | 1 | admin | 21 | | 2 | editor | 22 | | 3 | viewer | 23 | 24 | 接著你要有另一個 table 叫做 roles_permissions: 25 | 26 | | id | role_id | permission_id | 27 | |----|---------|------------| 28 | | 1 | 1 | 1 | 29 | | 2 | 1 | 2 | 30 | | 3 | 1 | 3 | 31 | | 4 | 2 | 1 | 32 | | 5 | 2 | 6 | 33 | | 6 | 4 | 1 | 34 | 35 | 所以要看一個 user 的權限,你就可以從他的 role id 去撈這個 role 擁有的 permission ids,例如說 role_id 1 有的權限就是 1, 2, 3,代表著瀏覽評論、刪除評論跟刪除所有評論。 36 | 37 | 而 role_id 2 有的權限是 1, 6,代表觀看文章以及新增文章。 38 | 39 | 現在已經可以知道這些 role 有哪些權限了,接著後端在做的時候,可以先把 permission table 整個撈出來,然後設定成一個變數,目標是讓 `$permission['comment']['delete']` 的值是 2,這樣就可以在相關的檔案裡面去做權限判斷了。 40 | 41 | 新增 role 的時候就是有一個地方可以輸入 role 的名稱,然後可以勾選需要哪些權限,再把這些東西寫進 roles 跟 roles_permissions 就好。 42 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/admin.php: -------------------------------------------------------------------------------- 1 | prepare( 8 | 'select '. 9 | 'P.id as id, P.content as content, P.title as title, '. 10 | 'P.created_at as created_at, U.nickname as nickname, U.username as username '. 11 | 'from posts as P ' . 12 | 'left join users as U on P.username = U.username '. 13 | 'where P.is_deleted = 0 order by id desc' 14 | ); 15 | $result = $stmt->execute(); 16 | if (!$result) { 17 | die('Error:' . $conn->error); 18 | } 19 | $result = $stmt->get_result(); 20 | ?> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 部落格 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 |
42 |
43 |
44 | fetch_assoc()) { 46 | ?> 47 |
48 |
49 | 50 |
51 | 62 |
63 | 64 |
65 |
66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/check_permission.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/create_post.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 部落格 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 |
29 |
30 |
31 |
32 |
33 | 發表文章: 34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/handle_create_post.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 21 | $stmt->bind_param('sss', $username, $content, $title); 22 | $result = $stmt->execute(); 23 | if (!$result) { 24 | die($conn->error); 25 | } 26 | 27 | header("Location: admin.php"); 28 | ?> 29 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/handle_delete.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 18 | $stmt->bind_param('i', $id); 19 | 20 | $result = $stmt->execute(); 21 | if (!$result) { 22 | die($conn->error); 23 | } 24 | 25 | header("Location: admin.php"); 26 | ?> 27 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/handle_login.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 19 | $stmt->bind_param("s", $username); 20 | $result = $stmt->execute(); 21 | if (!$result) { 22 | die($conn->error); 23 | } 24 | 25 | $result = $stmt->get_result(); 26 | if ($result->num_rows === 0) { 27 | header("Location: login.php?errCode=2"); 28 | exit(); 29 | } 30 | 31 | // 有查到使用者 32 | $row = $result->fetch_assoc(); 33 | if (password_verify($password, $row['password'])) { 34 | $_SESSION['username'] = $username; 35 | header("Location: index.php"); 36 | } else { 37 | header("Location: login.php?errCode=2"); 38 | } 39 | 40 | ?> 41 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/handle_update_post.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 25 | $stmt->bind_param('ssi', $title, $content, $id); 26 | 27 | $result = $stmt->execute(); 28 | if (!$result) { 29 | die($conn->error); 30 | } 31 | 32 | header("Location: " . $page); 33 | ?> 34 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/header.php: -------------------------------------------------------------------------------- 1 | 5 | 31 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/index.php: -------------------------------------------------------------------------------- 1 | prepare( 7 | 'select '. 8 | 'P.id as id, P.content as content, P.title as title, '. 9 | 'P.created_at as created_at, U.nickname as nickname, U.username as username '. 10 | 'from posts as P ' . 11 | 'left join users as U on P.username = U.username '. 12 | 'where P.is_deleted = 0 order by id desc' 13 | ); 14 | $result = $stmt->execute(); 15 | if (!$result) { 16 | die('Error:' . $conn->error); 17 | } 18 | $result = $stmt->get_result(); 19 | ?> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 部落格 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 40 |
41 |
42 | fetch_assoc()) { 44 | ?> 45 |
46 |
47 |
48 |
49 | 50 | 編輯 51 | 52 |
53 |
54 | 57 |
58 |
59 | READ MORE 60 |
61 | 62 |
63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/login.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 部落格 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 |
27 |

Login

28 |
29 |
30 |
USERNAME
31 | 32 |
33 | 34 |
35 |
PASSWORD
36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/logout.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/post.php: -------------------------------------------------------------------------------- 1 | prepare( 9 | 'select '. 10 | 'P.id as id, P.content as content, P.title as title, '. 11 | 'P.created_at as created_at, U.nickname as nickname, U.username as username '. 12 | 'from posts as P ' . 13 | 'left join users as U on P.username = U.username '. 14 | 'where P.id = ?' 15 | ); 16 | $stmt->bind_param('i', $id); 17 | $result = $stmt->execute(); 18 | if (!$result) { 19 | die('Error:' . $conn->error); 20 | } 21 | $result = $stmt->get_result(); 22 | $row = $result->fetch_assoc(); 23 | ?> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 部落格 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 | 編輯 52 | 53 |
54 |
55 | 58 |
59 |
60 |
61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .navbar { 6 | height: 80px; 7 | } 8 | 9 | .navbar__site-name { 10 | font-size: 28px; 11 | font-weight: bold; 12 | } 13 | 14 | .navbar__site-name a { 15 | text-decoration: none; 16 | color: #333333; 17 | } 18 | 19 | .navbar__list { 20 | display: flex; 21 | flex: 1; 22 | margin: 0; 23 | padding: 0; 24 | list-style: none; 25 | justify-content: space-between; 26 | } 27 | 28 | .navbar__list > div { 29 | display: flex; 30 | } 31 | 32 | .navbar__list li { 33 | margin-left: 21px; 34 | } 35 | 36 | .navbar__list a { 37 | text-decoration: none; 38 | color: #787878; 39 | font-size: 16px; 40 | } 41 | 42 | .navbar__wrapper { 43 | display: flex; 44 | align-items: center; 45 | height: 100%; 46 | } 47 | 48 | .wrapper { 49 | max-width: 1280px; 50 | margin: 0 auto; 51 | padding: 0px 12px; 52 | } 53 | 54 | .banner { 55 | background-image: linear-gradient(to top, #000000, #434343); 56 | height: 240px; 57 | color: #eeeeee; 58 | display: flex; 59 | align-items: center; 60 | justify-content: center; 61 | flex-direction: column; 62 | } 63 | 64 | .banner__wrapper > div { 65 | margin-top: 26px; 66 | } 67 | 68 | 69 | .banner h1 { 70 | font-size: 28px; 71 | margin: 0; 72 | } 73 | 74 | .container-wrapper { 75 | display: flex; 76 | justify-content: center; 77 | padding-bottom: 138px; 78 | min-height: calc(100vh - 350px); 79 | } 80 | 81 | .posts, .container { 82 | background: white; 83 | padding: 6px; 84 | display: inline-flex; 85 | margin: 0 auto; 86 | margin-top: -30px; 87 | flex-direction: column; 88 | } 89 | 90 | .post { 91 | width: 900px; 92 | padding: 30px; 93 | border: solid 1px #9a9a9a; 94 | } 95 | 96 | .post + .post { 97 | margin-top: 24px; 98 | } 99 | 100 | .post__header { 101 | font-size: 16px; 102 | display: flex; 103 | align-items: center; 104 | justify-content: space-between; 105 | } 106 | 107 | .post__action { 108 | padding: 3px 12px; 109 | border: solid 1px #a8a8a8; 110 | color: #a8a8a8; 111 | text-decoration: none; 112 | font-size: 14px; 113 | } 114 | 115 | .post__info { 116 | margin-top: 19px; 117 | padding: 12px 22px; 118 | background: #eee; 119 | } 120 | 121 | .post__content { 122 | margin-top: 17px; 123 | white-space: pre-wrap; 124 | line-height: 1.5em; 125 | } 126 | 127 | .btn-read-more { 128 | display: inline-block; 129 | padding: 12px 16px; 130 | border: solid 1px #737373; 131 | color: #737373; 132 | margin-top: 36px; 133 | text-decoration: none; 134 | } 135 | 136 | .login-wrapper { 137 | margin: 0 auto; 138 | border: solid 1px #000000; 139 | width: 527px; 140 | display: flex; 141 | flex-direction: column; 142 | align-items: center; 143 | padding: 62px 72px; 144 | margin-top: -30px; 145 | background: white; 146 | } 147 | 148 | .login-wrapper form { 149 | width: 100%; 150 | } 151 | 152 | .login-wrapper input { 153 | width: 100%; 154 | } 155 | 156 | .input__field { 157 | margin-top: 16px; 158 | height: 40px; 159 | } 160 | 161 | .login-wrapper input[type=submit] { 162 | height: 56px; 163 | background-image: linear-gradient(to top, #000000, #434343); 164 | color: white; 165 | margin-top: 73px; 166 | } 167 | 168 | .login-wrapper h2 { 169 | font-size: 32px; 170 | font-weight: normal; 171 | } 172 | 173 | .input__wrapper + .input__wrapper { 174 | margin-top: 23px; 175 | } 176 | 177 | .input__label { 178 | font-size: 14px; 179 | margin-left: 21px; 180 | } 181 | 182 | .edit-post { 183 | width: 900px; 184 | padding: 30px; 185 | border: solid 1px #9a9a9a; 186 | } 187 | 188 | .edit-post__title { 189 | font-size: 20px; 190 | } 191 | 192 | .edit-post__input-wrapper { 193 | width: 100%; 194 | margin-top: 28px; 195 | } 196 | 197 | .edit-post__input-wrapper input, .edit-post__input-wrapper textarea { 198 | width: 100%; 199 | padding: 6px; 200 | } 201 | 202 | .edit-post__btn-wrapper { 203 | text-align: right; 204 | } 205 | 206 | .edit-post__btn { 207 | display: inline-block; 208 | padding: 10px 39px; 209 | border: solid 1px #737373; 210 | color: #737373; 211 | cursor: pointer; 212 | margin-top: 25px; 213 | } 214 | 215 | .admin-post { 216 | padding: 24px 0px; 217 | display: flex; 218 | justify-content: space-between; 219 | align-items: center; 220 | border-bottom: 1px solid #9a9a9a; 221 | font-size: 14px; 222 | width: 920px; 223 | } 224 | 225 | .admin-post__title { 226 | font-size: 16px; 227 | margin-left: 16px; 228 | } 229 | 230 | .admin-post__info { 231 | display: flex; 232 | align-items: center; 233 | margin-left: 12px; 234 | } 235 | 236 | .admin-post__created-at { 237 | color: #8f8f8f; 238 | } 239 | 240 | .admin-post__btn { 241 | padding: 4px 8px; 242 | border: 1px solid #a8a8a8; 243 | color: #a8a8a8; 244 | cursor: pointer; 245 | margin-left: 8px; 246 | text-decoration: none; 247 | } 248 | 249 | .admin-post__btn:last-child { 250 | margin-right: 16px; 251 | } 252 | 253 | footer { 254 | padding: 10px 0px; 255 | background-color: #323232; 256 | font-size: 14px; 257 | color: white; 258 | text-align: center; 259 | } 260 | 261 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/update_post.php: -------------------------------------------------------------------------------- 1 | prepare( 10 | 'select '. 11 | 'P.id as id, P.content as content, P.title as title, '. 12 | 'P.created_at as created_at, U.nickname as nickname, U.username as username '. 13 | 'from posts as P ' . 14 | 'left join users as U on P.username = U.username '. 15 | 'where P.id = ?' 16 | ); 17 | $stmt->bind_param('i', $id); 18 | $result = $stmt->execute(); 19 | if (!$result) { 20 | die('Error:' . $conn->error); 21 | } 22 | $result = $stmt->get_result(); 23 | $row = $result->fetch_assoc(); 24 | ?> 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 部落格 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 |
47 |
48 |
49 |
50 |
51 | 編輯文章: 52 |
53 |
54 | 55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /examples/week11/hw2/php/utils.php: -------------------------------------------------------------------------------- 1 | query($sql); 11 | $row = $result->fetch_assoc(); 12 | return $row; // username, id, nickname, role 13 | } 14 | 15 | function escape($str) { 16 | return htmlspecialchars($str, ENT_QUOTES); 17 | } 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /examples/week11/hw2/static/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 部落格 8 | 9 | 10 | 11 | 12 | 13 | 14 | 32 | 38 |
39 |
40 |
41 |
42 |
43 | 嗨~歡迎來到程式新手村 feat. 胡斯的異想世界 44 |
45 | 56 |
57 | 58 |
59 |
60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/week11/hw2/static/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 部落格 8 | 9 | 10 | 11 | 12 | 13 | 14 | 32 | 38 |
39 |
40 |
41 |
42 |
嗨~歡迎來到程式新手村 feat. 胡斯的異想世界
43 |
44 | 編輯 45 |
46 |
47 | 50 |
郭沫若說過一句富有哲理的話,形成天才的決定因素應該是勤奮。這句話語雖然很短,但令我浮想聯翩。每個人都不得不面對這些問題。在面對這種問題時, 我認為, 生活中,若中午吃什麼出現了,我們就不得不考慮它出現了的事實。中午吃什麼,發生了會如何,不發生又會如何。我們一般認為,抓住了問題的關鍵,其他一切則會迎刃而解。我們都知道,只要有意義,那麼就必須慎重考慮。現在,解決中午吃什麼的問題,是非常非常重要的。所以, 那麼, 一般來講,我們都必須務必慎重的考慮考慮 51 | 52 | 郭沫若說過一句富有哲理的話,形成天才的決定因素應該是勤奮。這句話語雖然很短,但令我浮想聯翩。每個人都不得不面對這些問題。在面對這種問題時, 我認為, 生活中,若中午吃什麼出現了,我們就不得不考慮它出現了的事實。中午吃什麼,發生了會如何,不發生又會如何。我們一般認為,抓住了問題的關鍵,其他一切則會迎刃而解。我們都知道,只要有意義,那麼就必須慎重考慮。現在,解決中午吃什麼的問題,是非常非常重要的。所以, 那麼, 一般來講,我們都必須務必慎重的考慮考慮 53 | 54 | 郭沫若說過一句富有哲理的話,形成天才的決定因素應該是勤奮。這句話語雖然很短,但令我浮想聯翩。每個人都不得不面對這些問題。在面對這種問題時, 我認為, 生活中,若中午吃什麼出現了,我們就不得不考慮它出現了的事實。中午吃什麼,發生了會如何,不發生又會如何。我們一般認為,抓住了問題的關鍵,其他一切則會迎刃而解。我們都知道,只要有意義,那麼就必須慎重考慮。現在,解決中午吃什麼的問題,是非常非常重要的。所以, 那麼, 一般來講,我們都必須務必慎重的考慮考慮 55 |
56 |
57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/week11/hw2/static/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 部落格 8 | 9 | 10 | 11 | 12 | 13 | 14 | 32 | 38 |
39 |
40 |
41 |
42 |
43 | 發表文章: 44 |
45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 |
送出
53 |
54 |
55 |
56 |
57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/week11/hw2/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 部落格 8 | 9 | 10 | 11 | 12 | 13 | 14 | 32 | 38 |
39 |
40 |
41 |
42 |
嗨~歡迎來到程式新手村 feat. 胡斯的異想世界
43 |
44 | 編輯 45 |
46 |
47 | 50 |
郭沫若說過一句富有哲理的話,形成天才的決定因素應該是勤奮。這句話語雖然很短,但令我浮想聯翩。每個人都不得不面對這些問題。在面對這種問題時, 我認為, 生活中,若中午吃什麼出現了,我們就不得不考慮它出現了的事實。中午吃什麼,發生了會如何,不發生又會如何。我們一般認為,抓住了問題的關鍵,其他一切則會迎刃而解。我們都知道,只要有意義,那麼就必須慎重考慮。現在,解決中午吃什麼的問題,是非常非常重要的。所以, 那麼, 一般來講,我們都必須務必慎重的考慮考慮 51 |
52 | READ MORE 53 |
54 |
55 |
56 |
嗨~歡迎來到程式新手村 feat. 胡斯的異想世界
57 |
58 | 編輯 59 |
60 |
61 | 64 |
郭沫若說過一句富有哲理的話,形成天才的決定因素應該是勤奮。這句話語雖然很短,但令我浮想聯翩。每個人都不得不面對這些問題。在面對這種問題時, 我認為, 生活中,若中午吃什麼出現了,我們就不得不考慮它出現了的事實。中午吃什麼,發生了會如何,不發生又會如何。我們一般認為,抓住了問題的關鍵,其他一切則會迎刃而解。我們都知道,只要有意義,那麼就必須慎重考慮。現在,解決中午吃什麼的問題,是非常非常重要的。所以, 那麼, 一般來講,我們都必須務必慎重的考慮考慮 65 |
66 | READ MORE 67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/week11/hw2/static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 部落格 8 | 9 | 10 | 11 | 12 | 13 | 14 | 32 | 38 |
39 |

Login

40 |
41 |
42 |
USERNAME
43 | 44 |
45 | 46 |
47 |
PASSWORD
48 | 49 |
50 | 51 |
52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /examples/week11/hw2/static/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .navbar { 6 | height: 80px; 7 | } 8 | 9 | .navbar__site-name { 10 | font-size: 28px; 11 | font-weight: bold; 12 | } 13 | 14 | .navbar__site-name a { 15 | text-decoration: none; 16 | color: #333333; 17 | } 18 | 19 | .navbar__list { 20 | display: flex; 21 | flex: 1; 22 | margin: 0; 23 | padding: 0; 24 | list-style: none; 25 | justify-content: space-between; 26 | } 27 | 28 | .navbar__list > div { 29 | display: flex; 30 | } 31 | 32 | .navbar__list li { 33 | margin-left: 21px; 34 | } 35 | 36 | .navbar__list a { 37 | text-decoration: none; 38 | color: #787878; 39 | font-size: 16px; 40 | } 41 | 42 | .navbar__wrapper { 43 | display: flex; 44 | align-items: center; 45 | height: 100%; 46 | } 47 | 48 | .wrapper { 49 | max-width: 1280px; 50 | margin: 0 auto; 51 | padding: 0px 12px; 52 | } 53 | 54 | .banner { 55 | background-image: linear-gradient(to top, #000000, #434343); 56 | height: 240px; 57 | color: #eeeeee; 58 | display: flex; 59 | align-items: center; 60 | justify-content: center; 61 | flex-direction: column; 62 | } 63 | 64 | .banner__wrapper > div { 65 | margin-top: 26px; 66 | } 67 | 68 | 69 | .banner h1 { 70 | font-size: 28px; 71 | margin: 0; 72 | } 73 | 74 | .container-wrapper { 75 | display: flex; 76 | justify-content: center; 77 | padding-bottom: 138px; 78 | min-height: calc(100vh - 350px); 79 | } 80 | 81 | .posts, .container { 82 | background: white; 83 | padding: 6px; 84 | display: inline-flex; 85 | margin: 0 auto; 86 | margin-top: -30px; 87 | flex-direction: column; 88 | } 89 | 90 | .post { 91 | width: 900px; 92 | padding: 30px; 93 | border: solid 1px #9a9a9a; 94 | } 95 | 96 | .post + .post { 97 | margin-top: 24px; 98 | } 99 | 100 | .post__header { 101 | font-size: 16px; 102 | display: flex; 103 | align-items: center; 104 | justify-content: space-between; 105 | } 106 | 107 | .post__action { 108 | padding: 3px 12px; 109 | border: solid 1px #a8a8a8; 110 | color: #a8a8a8; 111 | text-decoration: none; 112 | font-size: 14px; 113 | } 114 | 115 | .post__info { 116 | margin-top: 19px; 117 | padding: 12px 22px; 118 | background: #eee; 119 | } 120 | 121 | .post__content { 122 | margin-top: 17px; 123 | white-space: pre-line; 124 | line-height: 1.5em; 125 | } 126 | 127 | .btn-read-more { 128 | display: inline-block; 129 | padding: 12px 16px; 130 | border: solid 1px #737373; 131 | color: #737373; 132 | margin-top: 36px; 133 | text-decoration: none; 134 | } 135 | 136 | .login-wrapper { 137 | margin: 0 auto; 138 | border: solid 1px #000000; 139 | width: 527px; 140 | display: flex; 141 | flex-direction: column; 142 | align-items: center; 143 | padding: 62px 72px; 144 | margin-top: -30px; 145 | background: white; 146 | } 147 | 148 | .login-wrapper form { 149 | width: 100%; 150 | } 151 | 152 | .login-wrapper input { 153 | width: 100%; 154 | } 155 | 156 | .input__field { 157 | margin-top: 16px; 158 | height: 40px; 159 | } 160 | 161 | .login-wrapper input[type=submit] { 162 | height: 56px; 163 | background-image: linear-gradient(to top, #000000, #434343); 164 | color: white; 165 | margin-top: 73px; 166 | } 167 | 168 | .login-wrapper h2 { 169 | font-size: 32px; 170 | font-weight: normal; 171 | } 172 | 173 | .input__wrapper + .input__wrapper { 174 | margin-top: 23px; 175 | } 176 | 177 | .input__label { 178 | font-size: 14px; 179 | margin-left: 21px; 180 | } 181 | 182 | .edit-post { 183 | width: 900px; 184 | padding: 30px; 185 | border: solid 1px #9a9a9a; 186 | } 187 | 188 | .edit-post__title { 189 | font-size: 20px; 190 | } 191 | 192 | .edit-post__input-wrapper { 193 | width: 100%; 194 | margin-top: 28px; 195 | } 196 | 197 | .edit-post__input-wrapper input, .edit-post__input-wrapper textarea { 198 | width: 100%; 199 | padding: 6px; 200 | } 201 | 202 | .edit-post__btn-wrapper { 203 | text-align: right; 204 | } 205 | 206 | .edit-post__btn { 207 | display: inline-block; 208 | padding: 10px 39px; 209 | border: solid 1px #737373; 210 | color: #737373; 211 | cursor: pointer; 212 | margin-top: 25px; 213 | } 214 | 215 | .admin-post { 216 | padding: 24px 0px; 217 | display: flex; 218 | justify-content: space-between; 219 | align-items: center; 220 | border-bottom: 1px solid #9a9a9a; 221 | font-size: 14px; 222 | width: 920px; 223 | } 224 | 225 | .admin-post__title { 226 | font-size: 16px; 227 | margin-left: 16px; 228 | } 229 | 230 | .admin-post__info { 231 | display: flex; 232 | align-items: center; 233 | margin-left: 12px; 234 | } 235 | 236 | .admin-post__created-at { 237 | color: #8f8f8f; 238 | } 239 | 240 | .admin-post__btn { 241 | padding: 4px 8px; 242 | border: 1px solid #a8a8a8; 243 | color: #a8a8a8; 244 | cursor: pointer; 245 | margin-left: 8px; 246 | text-decoration: none; 247 | } 248 | 249 | .admin-post__btn:last-child { 250 | margin-right: 16px; 251 | } 252 | 253 | footer { 254 | padding: 10px 0px; 255 | background-color: #323232; 256 | font-size: 14px; 257 | color: white; 258 | text-align: center; 259 | } 260 | 261 | -------------------------------------------------------------------------------- /examples/week12/README.md: -------------------------------------------------------------------------------- 1 | # Week12 作業自我檢討 2 | 3 | SPA 的部分請參考: 4 | 5 | 1. [前後端分離與 SPA](https://blog.techbridge.cc/2017/09/16/frontend-backend-mvc/) 6 | 2. [跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR](https://medium.com/@hulitw/introduction-mvc-spa-and-ssr-545c941669e9) 7 | 8 | ## SPA 補充 9 | 10 | 這邊稍微講一下,有些人會以為 SPA 的網址沒辦法變,這是錯誤的。SPA 的網址還是可以變,但是跟之前後端為主的方式是不同的。 11 | 12 | 後端是這樣的: 13 | 14 | 1. 你現在在網址 A 15 | 2. 點了某個按鈕,跳到網址 B,瀏覽器發一個 request 到 server 16 | 3. 瀏覽器接收到 response,render B 頁面資料 17 | 18 | 而 SPA 是這樣的: 19 | 20 | 1. 你現在在網址 A 21 | 2. 點了某個按鈕,跳到網址 B,這時候我們其實只是用 JS 來改變網址列,所以並不會發一個 request 到 server 22 | 3. SPA 自己處理網址變動,render 頁面 B 的資料 23 | 24 | JS 會利用一個叫做 history API 的東西來做,詳情大家可以自己去尋找相關資料,想快速試試看的可以隨便開一個頁面,然後打開 console 輸入 `history.pushState(null, null, '/hello')`,就能夠看到什麼叫做用 JS 來改變網址了。 25 | 26 | ## Todos 範例說明 27 | 28 | 1. [只有 UI 切好的版本](hw2/todo-ui-only.html) 29 | 2. [有完整功能的第一版](hw2/todo-v1.html) 30 | 3. [可以從 UI 擷取出資料的第二版](hw2/todo-extract-data.html) 31 | 4. [串接完 API 的完整版](hw2/todo-done.html) 32 | 5. [不一樣的想法的版本](hw2/index.html) 33 | -------------------------------------------------------------------------------- /examples/week12/hw1/api_add_comments.php: -------------------------------------------------------------------------------- 1 | false, 12 | "message" => "Please input missing fields" 13 | ); 14 | 15 | $response = json_encode($json); 16 | echo $response; 17 | die(); 18 | } 19 | 20 | $nickname = $_POST['nickname']; 21 | $site_key = $_POST['site_key']; 22 | $content = $_POST['content']; 23 | 24 | $sql = "insert into discussions(site_key, nickname, content) values (?, ?, ?)"; 25 | $stmt = $conn->prepare($sql); 26 | $stmt->bind_param('sss', $site_key, $nickname, $content); 27 | $result = $stmt->execute(); 28 | 29 | if (!$result) { 30 | $json = array( 31 | "ok" => false, 32 | "message" => $conn->error 33 | ); 34 | $response = json_encode($json); 35 | echo $response; 36 | die(); 37 | } 38 | 39 | $json = array( 40 | "ok" => true, 41 | "message" => "success" 42 | ); 43 | 44 | $response = json_encode($json); 45 | echo $response; 46 | ?> 47 | 48 | -------------------------------------------------------------------------------- /examples/week12/hw1/api_comments.php: -------------------------------------------------------------------------------- 1 | false, 10 | "message" => "Please add site_key in url" 11 | ); 12 | 13 | $response = json_encode($json); 14 | echo $response; 15 | die(); 16 | } 17 | 18 | $site_key = $_GET['site_key']; 19 | 20 | $sql = 21 | "select id, nickname, content, created_at from discussions where site_key = ? " . 22 | (empty($_GET['before']) ? "" : "and id < ?") . 23 | " order by id desc limit 5 "; 24 | $stmt = $conn->prepare($sql); 25 | if (empty($_GET['before'])) { 26 | $stmt->bind_param('s', $site_key); 27 | } else { 28 | $stmt->bind_param('si', $site_key, $_GET['before']); 29 | } 30 | 31 | $result = $stmt->execute(); 32 | 33 | if (!$result) { 34 | $json = array( 35 | "ok" => false, 36 | "message" => $conn->error 37 | ); 38 | $response = json_encode($json); 39 | echo $response; 40 | die(); 41 | } 42 | 43 | $result = $stmt->get_result(); 44 | $discussions = array(); 45 | while($row = $result->fetch_assoc()) { 46 | array_push($discussions, array( 47 | "id" => $row["id"], 48 | "nickname" => $row["nickname"], 49 | "content" => $row["content"], 50 | "created_at" => $row["created_at"] 51 | )); 52 | } 53 | 54 | $json = array( 55 | "ok" => true, 56 | "discussions" => $discussions 57 | ); 58 | 59 | $response = json_encode($json); 60 | echo $response; 61 | ?> 62 | 63 | -------------------------------------------------------------------------------- /examples/week12/hw1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Week12 留言板 8 | 9 | 10 | 11 | 16 | 118 | 119 | 120 | 121 |
122 |
123 |
124 | 125 | 126 |
127 |
128 | 129 | 130 |
131 | 132 |
133 |
134 |
135 |
136 | 137 | 138 | -------------------------------------------------------------------------------- /examples/week12/hw2/README.md: -------------------------------------------------------------------------------- 1 | ## Todos 範例說明 2 | 3 | 1. [只有 UI 切好的版本](todo-ui-only.html) 4 | 2. [有完整功能的第一版](todo-v1.html) 5 | 3. [可以從 UI 擷取出資料的第二版](todo-extract-data.html) 6 | 4. [串接完 API 的完整版](todo-done.html) 7 | 5. [不一樣的想法的版本](index.html) 8 | -------------------------------------------------------------------------------- /examples/week12/hw2/add_todo.php: -------------------------------------------------------------------------------- 1 | false, 10 | "message" => "Please input missing fields" 11 | ); 12 | 13 | $response = json_encode($json); 14 | echo $response; 15 | die(); 16 | } 17 | 18 | $todo = $_POST['todo']; 19 | 20 | $sql = "insert into todos(todo) values(?)"; 21 | $stmt = $conn->prepare($sql); 22 | $stmt->bind_param('s', $todo); 23 | $result = $stmt->execute(); 24 | 25 | if (!$result) { 26 | $json = array( 27 | "ok" => false, 28 | "message" => $conn->error 29 | ); 30 | $response = json_encode($json); 31 | echo $response; 32 | die(); 33 | } 34 | 35 | $json = array( 36 | "ok" => true, 37 | "message" => "success", 38 | "id" => $conn->insert_id 39 | ); 40 | 41 | $response = json_encode($json); 42 | echo $response; 43 | ?> 44 | 45 | -------------------------------------------------------------------------------- /examples/week12/hw2/get_todo.php: -------------------------------------------------------------------------------- 1 | false, 10 | "message" => "Please add id in url" 11 | ); 12 | 13 | $response = json_encode($json); 14 | echo $response; 15 | die(); 16 | } 17 | 18 | $id = intval($_GET['id']); 19 | 20 | $sql = 21 | "select id, todo from todos where id = ?"; 22 | $stmt = $conn->prepare($sql); 23 | $stmt->bind_param('i', $id); 24 | 25 | 26 | $result = $stmt->execute(); 27 | 28 | if (!$result) { 29 | $json = array( 30 | "ok" => false, 31 | "message" => $conn->error 32 | ); 33 | $response = json_encode($json); 34 | echo $response; 35 | die(); 36 | } 37 | 38 | $result = $stmt->get_result(); 39 | $row = $result->fetch_assoc(); 40 | $json = array( 41 | "ok" => true, 42 | "data" => array( 43 | "id" => $row["id"], 44 | "todo" => $row["todo"] 45 | ) 46 | ); 47 | 48 | $response = json_encode($json); 49 | echo $response; 50 | ?> 51 | 52 | -------------------------------------------------------------------------------- /examples/week12/hw2/todo-ui-only.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Week12 Todo List 8 | 9 | 10 | 11 | 47 | 48 | 49 | 50 |
51 |
52 |
53 |

Todo List

54 |
55 | 56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 | 67 |
68 | 69 |
70 |
71 |
2 個未完成
72 |
73 |
全部
74 |
未完成
75 |
已完成
76 |
77 |
78 | 移除已完成待辦事項 79 |
80 |
81 | 82 | 83 |
84 |
85 |
86 | 98 | 99 | -------------------------------------------------------------------------------- /examples/week13/README.md: -------------------------------------------------------------------------------- 1 | # Week13 自我檢討 2 | 3 | ## hw3 改成 fetch 4 | 5 | 先來看一下 week8 原本使用 xhr 的版本,這邊我只在乎兩個點,那就是我在呼叫拿資料的 function 時怎麼呼叫,然後資料怎麼回傳,只有這兩個部分需要改動,其他地方都不用動: 6 | 7 | ``` js 8 | function getStreams(name, cb) { 9 | var request = new XMLHttpRequest(); 10 | request.open('GET', url + '/streams?game=' + encodeURIComponent(name), true); 11 | request.setRequestHeader('Accept', 'application/vnd.twitchtv.v5+json') 12 | request.setRequestHeader('Client-ID', 'xxxxxx') 13 | 14 | request.onload = function() { 15 | if (this.status >= 200 && this.status < 400) { 16 | cb(JSON.parse(this.response)); 17 | } 18 | }; 19 | request.send(); 20 | } 21 | 22 | getStreams(topGames[0], (data) => { 23 | appendStreams(data.streams) 24 | addPlaceholder() 25 | addPlaceholder() 26 | addPlaceholder() 27 | }) 28 | ``` 29 | 30 | ### 第一次嘗試:改成 fetch 31 | 32 | 第一次嘗試,我們先保留原本 callback 的設計,把 xhr 換成 fetch 看看,會變成這樣: 33 | 34 | ``` js 35 | function getStreams(name, cb) { 36 | fetch(url + '/streams?game=' + encodeURIComponent(name), { 37 | headers: { 38 | Accept: 'application/vnd.twitchtv.v5+json', 39 | 'Client-ID': 'xxxxxx' 40 | } 41 | }) 42 | .then(res => res.json()) 43 | .then(data => { 44 | cb(data) 45 | }) 46 | } 47 | 48 | getStreams(topGames[0], (data) => { 49 | appendStreams(data.streams) 50 | addPlaceholder() 51 | addPlaceholder() 52 | addPlaceholder() 53 | }) 54 | ``` 55 | 56 | 只有 getStreams 這個 function 有變而已,底下用的地方完全沒變,都還是跟原本一樣使用 callback function 去拿資料。 57 | 58 | 可是這樣其實沒那麼好,會被看出來你其實不會用 promise。因為如果會用的話,其實 callback 就不會出現了。 59 | 60 | ### 第二次嘗試:都改成用 Promise 61 | 62 | Promise 基本上可以取代 callback,所以其實我們可以從 callback 的機制換成 Promise,會變這樣: 63 | 64 | ``` js 65 | function getStreams(name) { 66 | // 這個 return 是重點,還記得 Promise.then 以後還是一個 promise 嗎? 67 | // 所以我們最後回傳的是一個 promise 68 | return fetch(url + '/streams?game=' + encodeURIComponent(name), { 69 | headers: { 70 | Accept: 'application/vnd.twitchtv.v5+json', 71 | 'Client-ID': 'xxxxxx' 72 | } 73 | }) 74 | .then(res => res.json()) 75 | } 76 | 77 | // 這邊改用 .then 拿資料 78 | getStreams(topGames[0]).then(data => { 79 | appendStreams(data.streams) 80 | addPlaceholder() 81 | addPlaceholder() 82 | addPlaceholder() 83 | }) // 順便加上錯誤處理好了 84 | .catch(err => console.log(err)) 85 | ``` 86 | 87 | 這邊我們把原本的 callback 改成 promise,然後順便加上 .catch 來處理錯誤,就完全不需要再用到 callback 了,程式碼看起來也乾淨一些。 88 | 89 | 這邊提一個之前看到的錯誤,有人會這樣寫: 90 | 91 | ``` js 92 | fetch(url + '/streams?game=' + encodeURIComponent(name), { 93 | headers: { 94 | Accept: 'application/vnd.twitchtv.v5+json', 95 | 'Client-ID': 'xxxxxx' 96 | } 97 | }) 98 | .then(res => res.json()) 99 | .then(data => data) 100 | ``` 101 | 102 | 最後一個 .then 完全是不必要的,因為他只會回傳自己而已。有那行跟沒那行一模一樣,只是多此一舉。 103 | 104 | ### 第三次嘗試:async 105 | 106 | 最後我們試著來改成 async 的寫法,還記得我說過的訣竅嗎?就把東西先改成很像同步的樣子,最後再加上關鍵字就好。先來看 getStreams 怎麼改: 107 | 108 | ``` js 109 | async function getStreams(name) { 110 | const response = await fetch(url + '/streams?game=' + encodeURIComponent(name), { 111 | headers: { 112 | Accept: 'application/vnd.twitchtv.v5+json', 113 | 'Client-ID': 'xxxxxx' 114 | } 115 | }) 116 | const data = await response.json() 117 | return data 118 | } 119 | ``` 120 | 121 | 大家可以自己對照之前 promise 的版本,其實可以看得出來就只是在 promise 前面加 await,然後前面就可以像同步那樣直接接收回傳值了。 122 | 123 | 再來是用的部分,會這樣用: 124 | 125 | ``` js 126 | const data = await getStreams(topGames[0]).catch(err => console.log(err)) 127 | appendStreams(data.streams) 128 | addPlaceholder() 129 | addPlaceholder() 130 | addPlaceholder() 131 | ``` 132 | 133 | 這邊其實是錯的用法,因為變成 async 之後,錯誤就可以改用一般的 try catch 而不是 Promise 的 catch 了,會變這樣: 134 | 135 | ``` js 136 | try { 137 | const data = await getStreams(topGames[0]) 138 | appendStreams(data.streams) 139 | addPlaceholder() 140 | addPlaceholder() 141 | addPlaceholder() 142 | } catch(err) { 143 | console.log(err) 144 | } 145 | ``` 146 | 147 | 這就是改成 async 的重點,那就是「用看起來很像同步的寫法,前面再加上 await 就好」 148 | 149 | 最後再講一個東西,那就是只有在 async 的 function 裡面才能使用 await 這個關鍵字,因此上面這一段必須被包在 async 的 function 裡面才行,像是這樣: 150 | 151 | ``` js 152 | async function run() { 153 | try { 154 | const data = await getStreams(topGames[0]) 155 | appendStreams(data.streams) 156 | addPlaceholder() 157 | addPlaceholder() 158 | addPlaceholder() 159 | } catch(err) { 160 | console.log(err) 161 | } 162 | } 163 | 164 | run() 165 | ```` 166 | 167 | 總結幾個重點: 168 | 169 | 1. 有了 Promise 就不會有 callback,除非用的 library 不支援 170 | 2. 用了 async/await,通常就不會有 .then 跟 .catch 了 171 | -------------------------------------------------------------------------------- /examples/week14/README.md: -------------------------------------------------------------------------------- 1 | # Week14 自我檢討 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## 部署 6 | 7 | 本週重點就只有這一個而已,但這當然也不是件容易的事。 8 | 9 | 這一期幸運的地方在前一期有學長姐留下的筆記,所以很多時候都跟著做就好。但你要知道那是前人血淚的結晶(?),他們也是部署過很多次,崩潰很多次之後才記起來的。 10 | 11 | 如果你這週是順利的,未來某一天你一樣會經歷這個崩潰的過程。如果你這週本來就很崩潰,放心,以後還有更多讓你崩潰的事。 12 | 13 | 另外,這週最常見的問題就是防火牆的設定,你東西連不進去基本上都是防火牆的問題。防火牆有兩個,一個是你主機上的,一個是你用的雲服務上面的,請確保兩個防火牆都有設定好,應該就沒問題了。 14 | 15 | 附上第四期同學做得很完整的筆記:[在 AWS 上面部署 LEMP server](https://github.com/Lidemy/mentor-program-4th-Lauviah0622/blob/master/homeworks/week14/hw2.md) 16 | 17 | ## DNS 18 | 19 | 之前在第四週有提過 DNS 但其實講的滿淺的,事實上 DNS server 其實是有分層級的,大家可以參考底下幾篇文章: 20 | 21 | 1. [DNS原理总结及其解析过程详解](https://blog.csdn.net/qq_32642107/article/details/102665148) 22 | 2. [金三銀四網絡面經之 DNS 詳解!](https://www.chainnews.com/zh-hant/articles/998094400229.htm) 23 | 3. [How DNS Works – the Domain Name System (Part One)](https://cloudacademy.com/blog/how-dns-works/) 24 | 4. [How DNS works(可愛的漫畫)](https://howdns.works/ep5/) 25 | 5. [What happens when you update your DNS?(這篇比較偏技術)](https://jvns.ca/blog/how-updating-dns-works/) 26 | 27 | ## 安全性 28 | 29 | 提醒一下各位同學,week14 架的主機要注意一下密碼的安全性 30 | 31 | 有同學用一個滿好猜的密碼,然後發現被駭客入侵了!你可能會想說:你一台這麼小的主機,怎麼會被入侵? 32 | 33 | 但其實駭客根本也不知道你是誰,應該是他們有做機器人去掃某些 ip 段,然後去試一些知名的服務(例如說 mysql),發現有開就再測測看常見密碼,試出來連得進去以後就自動把你資料庫備份,然後把原本的刪掉,並且留下勒索訊息 34 | 35 | 這一切都是自動化的,所以他們連你是誰都不知道XD 36 | 37 | 防範方法有幾個,第一個就是資料庫帳號密碼不要設太弱 38 | 第二個就是防火牆開好開滿,你可以設定只有 localhost 可以連到資料庫,這樣外面就都進不去,你甚至連 3306 port 都可以不打開 39 | 40 | 那這樣我們自己要怎麼看資料庫的內容呢? 41 | 42 | 有一種技術叫做 SSH Tunnel,就跟之前我在 week16 提過的 port forwarding 很像,你可以把遠端主機的 3306 透過 ssh tunnel 對應到自己電腦的 3306,所以你連 localhost:3306 就等於是連你遠端主機 43 | 44 | 然後駭客沒有你連 ssh 的那個 key,所以也連不進去,因此就會是安全的 45 | 46 | ssh tunnel 參考資料:[[教學] 透過 SSH Tunnel 將伺服器內部服務綁定到本機電腦上 | 辛比誌](https://xenby.com/b/269-%E6%95%99%E5%AD%B8-%E9%80%8F%E9%81%8E-ssh-tunnel-%E5%B0%87%E4%BC%BA%E6%9C%8D%E5%99%A8%E5%85%A7%E9%83%A8%E6%9C%8D%E5%8B%99%E7%B6%81%E5%AE%9A%E5%88%B0%E6%9C%AC%E6%A9%9F%E9%9B%BB%E8%85%A6%E4%B8%8A) 47 | 48 | 然後如果你想反其道而行,試試看被駭客入侵的感覺,可以把資料庫帳號密碼設成什麼 test 或是 root, admin, guest 這種常見的,然後等個一兩週看看,就可以體會到這是什麼感覺 49 | 50 | ## 主機連不上 51 | 52 | 先確認幾件事情: 53 | 54 | 1. AWS 上的 security group 22 port 有開 55 | 2. AWS 主機預設 IP 是會換的,有設定可以調整,讓 IP 不會換 56 | 3. 在 AWS 後台有其他方式可以連進去,可參考:[Open Terminal Sessions on EC2 Instances in your Web Browser](https://trevorsullivan.net/2018/10/16/open-terminal-sessions-on-ec2-instances-in-your-web-browser/) 跟 [SSH to AWS EC2 instance using the web browser](https://medium.com/@michael.niedermayr/ssh-to-aws-ec2-instance-using-the-web-browser-786bd4d2663b) 57 | 4. 連進去主機後確認主機上的防火牆 22 port 有開:`sudo ufw status` 58 | 5. 確認 ssh 服務有開:`sudo netstat -tulpn | grep :22` 59 | 60 | -------------------------------------------------------------------------------- /examples/week16/README.md: -------------------------------------------------------------------------------- 1 | # Week16 自我檢討 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## Callback queues 6 | 7 | 這一週附上的 event loop 影片應該就講的滿清楚的了,但大部分同學都會忽略掉一個小細節。 8 | 9 | 以下面的程式碼為例: 10 | 11 | ``` js 12 | setTimeout(() => { 13 | console.log('hello') 14 | }, 0) 15 | ``` 16 | 17 | 執行流程是什麼? 18 | 19 | 是先把這整段放到 call stack 裡面去執行,所以才會執行 setTimeout 這個 function。然後 setTimeout 會呼叫瀏覽器幫忙設定一個 0 ms 後到期的定時器,到期之後就會把第一個參數:`() => {console.log('hello')}` 放進去 callback queue。 20 | 21 | 這邊最多人誤解的點就是會把 `setTimeout(...)` 整段丟進去 callback queue,不是這樣的,只會把第一個參數丟進去而已。你必須先執行 setTimeout 才能設定計時器,才能把第一個參數丟進去 callback queue。 22 | 23 | 然後還有另一個會搞錯的地方,那就是很多人以為是把 `console.log('hello')` 丟進去 callback queue,不是,這是一個 function call,不是一個 function。丟進去 callback queue 的是 `() => {console.log('hello')}` 這個 function。 24 | 25 | 設定完成以後從 call stack pop 出來,main 也 pop,stack 清空,把 `() => {console.log('hello')}` 丟進去 call stack,執行這個 function,執行之後發現這個 function 裡面還要呼叫 `console.log('hello')`,所以把 `console.log` 丟進去 call stack,印出 hello,pop,然後原本的 function 也沒東西要執行了所以也 pop,stack 清空,結束。 26 | 27 | ## 錯誤範例 28 | 29 | 底下找幾個現成的錯誤範例來解釋錯在哪裡: 30 | 31 | ### 範例一 32 | 33 | ``` js 34 | console.log(1) // 放入 Call Stack 並直接執行,印出 1,執行完後移除 35 | setTimeout(() => { // setTimeout() 放到 Webapis 執行,直到倒數完畢, 36 | console.log(2) // () => { console.log(2) } 被放到 Callback Queue 待命 37 | }, 0) 38 | ``` 39 | 40 | 錯誤的點在:「setTimeout() 放到 Webapis 執行」,web api 不是一個地方,是一個種類,setTimeout 是屬於 web api 的其中一個,但是不是 web api 跟非同步無關。 41 | 42 | ### 範例二 43 | 44 | 1. 將`console.log(1)`放入 call stack 執行,輸出 1 45 | 2. 將`setTimeout(() => { console.log(2) }, 0)`放入 call stack 執行,在經過 0 秒後呼叫`() => { console.log(2) }`,由於 setTimeout 屬於 WebAPI, 46 | 所以將 `() => { console.log(2) }` 排進 callback queue,執行結束後,setTimeout 就會從 call stack 中 pop 掉 47 | 48 | 錯誤的點在於第二步,「由於 setTimeout 屬於 WebAPI,所以...」,不是,這跟是不是 WebAPI 無關,而是跟 setTimeout 本身要做的事有關。 49 | 50 | 換句話說,有同步的 WebAPI,也有非同步的 API,有同步的不是 WebAPI 的東西,也有非同步的不是 WebAPI 的東西。 51 | 52 | 這邊我看了一下我之前寫的文章,發現是我讓大家誤解了,描述得不夠好,我文章中是這樣寫的: 53 | 54 | > 然後 setTimeout 屬於 Web API,所以會跟瀏覽器說:「欸欸,幫我設定一個計時器,2000 毫秒以後呼叫 fn」,然後就執行結束,從 call stack 裡面 pop 掉。 55 | 56 | 我這邊要強調的是:「因為 setTimeout 是 WebAPI,所以會跟瀏覽器溝通,要瀏覽器去執行某些事情」,而不是「因為是 WebAPI 所以會把 callback 丟進 callback queue」。 57 | 58 | ### 範例三 59 | 60 | 把 `setTimeout` 放進 call stack 61 | 因為 `setTimeout` 是非同步函式,所以會移進 Web API 等待時間到 62 | 經過 0 ms 之後,將 `() => { console.log(2)}` 放進 Queue 63 | 此時因為 call stack 裡也有任務正在執行,所以先在 Queue 裡面等待 64 | 65 | 錯誤的點在於 Web API 不是一個地方。這邊可以直接講瀏覽器就好,呼叫 setTimeout 之後叫瀏覽器設定一個計時器,0ms 之後會觸發,那這個計時器設定在哪邊?不重要,這是瀏覽器會去處理的事。 66 | 67 | ### 範例四 68 | 69 | 將 `setTimeout(() => { 70 | console.log(2) 71 | }, 0)` 放進 Call Stack 執行,呼叫 setTimeout 這個 Web API,本行執行完畢,setTimeout 倒數時間設定為 0,倒數完畢後,將 `console.log(2)` 放入 Callback Queue 待命。 72 | 73 | 錯誤的點在於:將 `console.log(2)` 放入 Callback Queue 待命,是將 `() => { console.log(2) }` 放入 callback queue。 74 | 75 | 有關 setTimeout 的延伸閱讀: 76 | 77 | 1. [为什么 setTimeout 有最小时延 4ms ?](https://juejin.im/post/6846687590616137742) 78 | 2. [HTML spec: 8.6 Timers](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) 79 | 80 | ## Function expression 的初始化 81 | 82 | 這邊可以參考我跟同學們的討論:https://github.com/Lidemy/mentor-program-3rd-ClayGao/pull/24 83 | -------------------------------------------------------------------------------- /examples/week17/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## 你的離開不是你以為的離開 6 | 7 | 跟之前 PHP 一樣,很多人以為 `header("Location: index.php")`之後程式就結束了,不,沒有,這只是個 function call 而已。 8 | 9 | Express 同理,無論你是呼叫 `next` 還是 `res.redirect`,程式都沒有停止執行,所以像這樣一段程式碼: 10 | 11 | ``` js 12 | function handle(req, res) { 13 | if (!req.body.username) { 14 | res.redirect('/') 15 | } 16 | 17 | User.create() 18 | } 19 | ``` 20 | 21 | 程式還是有執行到 `User.create()` 那一行,那要怎麼結束程式執行?因為我們在 function 裡面,所以 return 就好: 22 | 23 | ``` js 24 | function handle(req, res) { 25 | if (!req.body.username) { 26 | res.redirect('/') 27 | return 28 | } 29 | 30 | User.create() 31 | } 32 | ``` 33 | 34 | 這樣才是符合我們預期的行為。 35 | 36 | 但這邊還要注意一件事情,那就是在用 Promise 的時候你通常都是在一個 function 裡面,所以這樣寫是沒有用的: 37 | 38 | ``` js 39 | function handle(req, res) { 40 | User.findBy({ 41 | id: 1 42 | }).then(user => { 43 | if(!user) { 44 | res.redirect('/') 45 | return 46 | } 47 | }).then(user => { 48 | // 這邊還是會執行到 49 | User.create() 50 | }) 51 | } 52 | ``` 53 | 54 | 因為 promise chaining 的特性,所以最後的那個 `.then` 還是會執行到,你寫的 return 是你傳進去 then 的那個 function 的 return,不是 `handle` 的 return。 55 | 56 | 這邊建議大家改用 async await,程式碼簡單好懂: 57 | 58 | ``` js 59 | async function handle(req, res) { 60 | const user = await User.findBy({ 61 | id: 1 62 | }) 63 | 64 | if(!user) { 65 | res.redirect('/') 66 | return 67 | } 68 | 69 | User.create() 70 | } 71 | ``` 72 | 73 | ## Promise chaining 74 | 75 | 我看到這週有很多人善用了 Promise chaining 的特性,寫出像是這種程式碼: 76 | 77 | ``` js 78 | User.findBy({ 79 | id: 1 80 | }).then(user => { 81 | return user.destory() 82 | }).then(() => { 83 | res.redirect('/') 84 | }) 85 | ``` 86 | 87 | 那你能看出上面的範例跟下面的差在哪裡嗎? 88 | 89 | ``` js 90 | User.findBy({ 91 | id: 1 92 | }).then(user => { 93 | user.destory() 94 | }).then(() => { 95 | res.redirect('/') 96 | }) 97 | ``` 98 | 99 | 沒錯,就差在 `user.destory()` 有沒有 return,可是這有什麼差呢?有,差很大。 100 | 101 | Promise chaining 是什麼意思?之前有講過,你要在 .then 裡面「回傳一個 Promise」才會成立,而上面那種錯誤寫法,.then 裡面沒有 return,所以預設就是回傳 undefined,因此 Promise chaining 不會成立。 102 | 103 | 但你不會發現,因為正確的寫法是「user.destory 執行完以後,才會執行 res.redirect」,但錯誤的寫法也只是「user.destory 還沒執行完,就會執行 res.redirect」 104 | 105 | 因為 redirect 也需要時間,所以實際上重新導向時,刪除 user 的動作還是很有可能已經完成了,因此在結果上類似,但後者還是錯誤的寫法,因為這並不是 promise chaining。 106 | 107 | ## 權限檢查 108 | 109 | Week20 的 show time 有 lidemy 學習系統後端的程式碼導讀,裡面有權限檢查的部分,其實可以善用 middleware 來做,會方便很多,像是這樣: 110 | 111 | ``` js 112 | function checkIsLogin(req, res, next) { 113 | // 沒有登入,導回首頁 114 | if (!req.session.username) { 115 | res.redirect('/') 116 | return 117 | } 118 | 119 | // 有登入,放行 120 | next() 121 | } 122 | 123 | app.get('/admin', checkIsLogin, adminController.index) 124 | ``` 125 | 126 | ## 環境變數 127 | 128 | 敏感資料要放環境變數,例如說 session 的 secret 雖然課程沒特別提,但其實就算是,可以參考:https://github.com/Lidemy/mentor-program-4th-awuuu0716/pull/20#discussion_r504042290 129 | 130 | ## 命名規則 131 | 132 | JS 命名規則提很多次了,通常是 camelCase,然後 url 會是 kebab case,用 `-` 來分開。 133 | 134 | 例如說網址會是 `add-post` 而不是 `addPost` 也不是 `add_post`,用來處理的 controller function 會叫 `handleAddPost`,不會是 `handle_add_post` 也不會是 `HandleAddPost`。 135 | 136 | 137 | -------------------------------------------------------------------------------- /examples/week18/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | 暫無 -------------------------------------------------------------------------------- /examples/week19/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | 暫無 -------------------------------------------------------------------------------- /examples/week2/README.md: -------------------------------------------------------------------------------- 1 | # Week2 作業自我檢討 2 | 3 | 這週的參考解答放在另外一個檔案裡面,然後自我檢討有幾件事情要請大家檢查。 4 | 5 | 第一,請看清楚題目的要求,「印出」代表在 function 裡面會出現 `console.log`,把東西印出來;「回傳」代表要利用 `return` 把答案回傳,所以在呼叫 function 時不會有任何輸出,一定要把這兩點分得很清楚。 6 | 7 | 第二,hw5 的 join 那題,請試試看以下幾個 case 是否有回傳正確答案: 8 | 9 | ``` js 10 | join([1, 2, 3], ''),正確回傳值:123 11 | join(["a", "b", "c"], "!"),正確回傳值:a!b!c 12 | join(["aaa", "bb", "c", "dddd"], ',,'),正確回傳值:aaa,,bb,,c,,dddd 13 | ``` 14 | 15 | 要特別注意的是分隔符號只會在每個元素中間出現,所以如果你寫成: 16 | 17 | ``` js 18 | function join(arr, concatStr) { 19 | let result = ''; 20 | for (let i = 0; i < arr.length; i += 1) { 21 | result += arr[i] + concatStr; 22 | } 23 | return result; 24 | } 25 | ``` 26 | 27 | 是標準錯誤寫法,因為最後面會多了一個 concatStr。 28 | 29 | 第三,有關於 hw6 的簡答題 30 | 31 | 在跑這個迴圈:`if (arr[i] !== arr[i-1] + arr[i-2]) return 'invalid'` 的時候,執行到 i=4 時 `if (arr[4] !== arr[3] + arr[2])` 答案會是 true,因為 22 !== 13 + 8,所以會回傳 invalid,而且回傳之後剩下的程式碼都不會再執行了 32 | 33 | 如果你執行流程寫一寫最後結果跟這個不一樣,那你一定是漏掉了什麼,麻煩再修正一下。 34 | 35 | 第四,hw1 給範圍的目的是:「讓你知道題目範圍是多少」 36 | 37 | 那超出範圍怎麼辦?不會,保證不會測試到超出範圍的數字,所以你可以完全不去考慮那些情形。雖然說在實際工作上可能不會有那麼明確的範圍規定,但是在程式解題的領域裡面基本上都會,因為有了這些限制,你才有辦法去決定要用什麼方式去解它。 38 | 39 | 我看很多人程式碼裡面有特別寫:`if (n>=1 && n<=30)`,這是沒有必要的,因為就算超出範圍,題目也沒有跟你說要怎麼辦,所以也沒有一個正確的處理方法。 40 | 41 | ## 本週重點與非重點 42 | 43 | 跟大家補充一些這週學習的重點,以及補充一些課程中沒講到但卻很重要的細節 44 | 45 | ## 重點(請優先學習) 46 | 47 | 1. 基本語法(變數、判斷式、迴圈、函式) 48 | 2. 變數儲存的模型(我之前 po 的那個博物館文章) 49 | 3. function 的運用 50 | 4. 命名「慣例」 51 | 5. 型態 52 | 6. return 的作用 53 | 54 | ### 變數命名 55 | 56 | 不同的程式語言會有不同的命名「慣例」,會說是慣例是因為就算不照這個命名也不會出錯。 57 | 58 | 例如說你可以有一個變數是 `Str = 3`,或是 `maxNumber = 'cool'`,明明變數名稱就不是這個意思,卻放了完全不同的資料。但相信大家都看得出來,這樣做的話是非常不好的方法,因為在誤導人。 59 | 60 | 在談變數與函式命名的時候,我覺得可以簡單分成兩種: 61 | 62 | 1. 語法上的慣例 63 | 2. 語意上的慣例 64 | 65 | 上面講的是語意上的慣例,你把一個字串叫做 `str` 會比較做 `num` 好,因為 str 是 string 的縮寫,所以光是從命名就可以看出是一個字串。 66 | 67 | 所以在取變數名稱的時候請盡量取的貼切一點,要找最大值就直接把它取叫 `max` 或是 `maxNumber`、`maxValue` 之類的都可以,會比 `num` 或是 `value` 好,因為更明確一點。 68 | 69 | 有關於語意,在第三週的自我檢討會再談這件事。 70 | 71 | 接著來談一下「語法上的慣例」,這邊以 maxNumber 為例: 72 | 73 | 1. maxNumber (camel case),小寫開頭,然後單字的連接變成大寫,像駝峰一樣 74 | 2. MaxNumber (pascal case),大寫開頭的駝峰式 75 | 3. max_value (snake case),用底線隔開單字,底線像是蛇一樣 76 | 4. max-value (kebab case),用 - 隔開像是烤肉串,Kebab 就是烤肉串的意思 77 | 78 | 所以這四種其實都代表相同的意思,只是語法不一樣,最後長出來的變數名稱也會不一樣。先強調一點: 79 | 80 | > 無論你選哪一種,請記得貫徹始終 81 | 82 | 不要一個變數叫做 `Abc`(大寫開頭),另一個叫做 `maxNum`(小寫駝峰),另一個又叫做 `to_upper_case`(snake case),這樣是最差勁的命名。 83 | 84 | 然後在 JS 裡面,無論是函式或是變數,習慣都是採用第一種:「駝峰式命名」,例如說我們有用過的:`toUpperCase` 就是一個很好的範例。 85 | 86 | 所以請大家在命名函式以及變數的時候,一律使用第一種命名方式。然後常用的命名規則還有第二種,大寫開頭的駝峰式,但那個是碰到 class 才會用到,這個還沒教到,之後教的時候會再提醒一次。 87 | 88 | ### 型態 89 | 90 | 課程裡面講到的那幾種型態其實都滿常用到的,尤其需要特別注意的是字串跟數字。字串跟數字相加會是字串,這個一定要記得,因為你 90% 會出錯在這種東西。 91 | 92 | 例如說: 93 | 94 | ``` js 95 | var arr = ['12', '34'] 96 | console.log(1 + arr[0]) 97 | ``` 98 | 99 | 答案是多少? 100 | 101 | 答案是 112 不是 13,因為數字 + 字串會變成字串。所以你要使用 `Number()` 或是 `parseInt()` 把你的字串轉成數字,才能順利把數字相加。 102 | 103 | 型態這問題會在第三週再提到一次。 104 | 105 | ### return 的作用 106 | 107 | 兩個重點: 108 | 109 | 1. return 的意義:把結果傳回去 110 | 2. 函式一執行到 return,就把結果傳回去然後結束了,其他在 function 裡的程式碼「不會執行到」 111 | 112 | 大家一定要很清楚地知道 `console.log` 就只是「印出」,把任何東西印在你的執行環境上面。你在 Node.js 下 console.log,就會在你的 terminal 上面印出東西。你在瀏覽器下 console.log,就會在瀏覽器的 console 印出結果。 113 | 114 | 但你要知道的是,印出的結果是給誰看的?給人看的。所以對電腦來說,console.log 是沒有什麼意義的。 115 | 116 | 再來談談 return,大家有用過 Excel 嗎?裡面不是會有一些內建函式嗎,例如說 `SUM`,如果我用了 `SUM(A1:A10)`,就可以得到這幾個格子相加後的結果。SUM 就是一個函式的經典範例。 117 | 118 | 那除了這些內建的,你也可以自己幫 Excel 新增函式,你可以把一連串的操作合併起來,之後要重複運用就會容易很多。每一個程式都會有下面三個要素: 119 | 120 | 1. 輸入 121 | 2. 運算 122 | 3. 輸出 123 | 124 | 以這週的作業來講,capitalize 那一題就是: 125 | 126 | 1. 輸入(一個字串) 127 | 2. 運算 128 | 3. 輸出(一個字串) 129 | 130 | 假設我們今天要寫一個程式,可以讓人輸入一個陣列代表成績,然後輸出成績的平均數以及有多少人及格,那我們可能會這樣寫: 131 | 132 | ``` js 133 | var scores = [20, 40, 60, 40, 100, 90, 80]; 134 | var sum = 0; 135 | 136 | for (var i = 0; i < scores.length; i++) { 137 | sum += scores; 138 | } 139 | 140 | console.log('平均:', sum / scores.length); 141 | 142 | var failed = 0 143 | for( var i = 0; i < scores.length; i++) { 144 | if (scores[i] < 60) { 145 | failed++; 146 | } 147 | } 148 | 149 | console.log('不及格的人有:', failed); 150 | ``` 151 | 152 | 所以這個程式就是: 153 | 154 | 1. 輸入(陣列) 155 | 2. 運算 156 | 3. 輸出(平均分數跟不及格的人) 157 | 158 | 問題來了,如果你同事跟你說他也想算這東西,只是它不需要平均分數,只需要算不及格的人有多少,那你該怎麼把程式碼給他? 159 | 160 | 你當然可以只把這一段給他: 161 | 162 | ``` js 163 | var failed = 0 164 | for( var i = 0; i < scores.length; i++) { 165 | if (scores[i] < 60) { 166 | failed++; 167 | } 168 | } 169 | ``` 170 | 171 | 可是如果這樣,那他的變數名稱也要叫做 scores,不然就會出錯,他就要改名,或是去改你的程式碼。現在的程式碼只有 6 行,看起來還好,但如果有 60 行呢?那問題就大了。 172 | 173 | 所以我們可以把這一段獨立出來,變成另外一個:副程式,也就是函式: 174 | 175 | ``` js 176 | function failedCount(scores) { 177 | var failed = 0 178 | for( var i = 0; i < scores.length; i++) { 179 | if (scores[i] < 60) { 180 | failed++; 181 | } 182 | } 183 | return failed; 184 | } 185 | ``` 186 | 187 | 如此一來,只需要把這個 function 給同事,跟他說:「你把陣列傳進去,就會得到結果了」。之所以會說這個是副程式,就是因為他一樣有以下三個元素: 188 | 189 | 1. 輸入(陣列) 190 | 2. 運算 191 | 3. 輸出(不及格的人數) 192 | 193 | return 的意義就是要產生第三步驟的那個輸出。 194 | 195 | ### 其他 196 | 197 | 有些人好像對浮點數誤差有一點疑惑,這邊簡單解釋一下。 198 | 199 | 首先,數字是不是無限多?是。 200 | 201 | 那在電腦裡面,數字儲存的空間是不是無限大?不是,通常是 32 bits 或是 64 bits。 202 | 203 | 但用有限的東西來存無限的東西會發生什麼事? 204 | 205 | 答案就是誤差,就是不精準。因為輸入是無限的,可是你的儲存空間是有限的,代表你絕對不可能有辦法儲存所有種組合,所以就一定會有誤差,會有範圍限制。 206 | 207 | ## 非重點(有時間再學習) 208 | 209 | 1. sort 的 compare function 210 | 2. 把函式當作參數 211 | 3. 位元運算 212 | 4. 挑戰題與超級挑戰題 213 | 5. 內建函式 214 | 215 | 這些東西都可以等之後再回來補,而且有些根本不用補,你多看幾次就會熟了。例如說內建函式的部分,以後用到的機會很多,之後一直用一直用,你不想熟也得熟。 216 | 217 | -------------------------------------------------------------------------------- /examples/week2/challenges.md: -------------------------------------------------------------------------------- 1 | ### 挑戰題 2 | 3 | 這題請參考:https://blog.huli.tw/2016/09/23/binary-search-introduction/ 4 | 5 | ``` js 6 | function search(array, target){ 7 | var L = 0, R = array.length - 1; 8 | while(L<=R) { 9 | var M = Math.floor((L+R)/2); 10 | if(array[M] 0 || b > 0 || carry > 0) { 72 | const [sum, c] = fullAdder(a & 1, b & 1, carry); 73 | carry = c 74 | result = (sum ? positionMask : 0) | result 75 | a >>= 1 76 | b >>= 1 77 | positionMask <<= 1 78 | } 79 | return result 80 | } 81 | ``` 82 | 83 | ### 超級超級挑戰題 84 | 85 | 請參考這兩位同學的作業: 86 | 87 | 1. https://github.com/Lidemy/mentor-program-4th-WooooHuan/pull/2 88 | 2. https://github.com/Lidemy/mentor-program-4th-cwc329/pull/4 89 | 90 | -------------------------------------------------------------------------------- /examples/week2/examples.md: -------------------------------------------------------------------------------- 1 | ### 第一題:printStars 2 | 3 | 這題比較沒難度就不解釋了,照著做就行了。有很多人會把字串拼好再印出來,也是 ok 4 | 5 | ``` js 6 | function printStars(n) { 7 | for (let i = 0; i < n; i += 1) { 8 | console.log('*'); 9 | } 10 | } 11 | ``` 12 | 13 | ### 第二題:capitalize 14 | 15 | 有很多人都會用 charAt 或是先把字串用 split 變成陣列再來做這題,但其實在 JS 裡面你本來就可以用 str[0] 取到第一個字,不需要用 chatAt。 16 | 17 | 另一個常見問題是會檢查第一個字是否是小寫再轉,但內建的 toUpperCase 如果本來就是大寫,轉完也還是大寫,想一下之後會發現根本不需要檢查。 18 | 19 | ``` js 20 | function capitalize(str) { 21 | return str[0].toUpperCase() + str.slice(1); 22 | } 23 | ``` 24 | 25 | ### 第三題:reverse 26 | 27 | 迴圈倒著做就好 28 | 29 | ``` js 30 | function reverse(str) { 31 | let result = ''; 32 | for (let i = str.length - 1; i >= 0; i -= 1) { 33 | result += str[i]; 34 | } 35 | console.log(result); 36 | } 37 | ``` 38 | 39 | ### 第四題:printFactor 40 | 41 | 這題的迴圈不要從 0 開始,因為語意上是不通的,你不會想拿數字去除以 0 42 | 43 | ``` js 44 | function printFactor(n) { 45 | for (let i = 1; i <= n; i += 1) { 46 | if (n % i === 0) { 47 | console.log(i); 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | ### 第五題:內建函式自己做 54 | 55 | 這一題最常見的錯誤是最後面多了一個連接的字串。 56 | 57 | 另外,當陣列是空的的時候可以特別處理,在這情形下應該要回傳空字串 58 | 59 | ``` js 60 | function join(arr, concatStr) { 61 | if (arr.length === 0) { // special case 62 | return ''; 63 | } 64 | 65 | let result = arr[0]; 66 | for (let i = 1; i < arr.length; i += 1) { 67 | result += concatStr + arr[i]; 68 | } 69 | return result; 70 | } 71 | ``` 72 | 73 | repeat 比較容易,就迴圈一下 74 | 75 | ``` js 76 | function repeat(str, times) { 77 | let result = ''; 78 | for (let i = 0; i < times; i += 1) { 79 | result += str; 80 | } 81 | return result; 82 | } 83 | ``` 84 | 85 | ## hw6:簡答題 86 | 87 | 執行流程就不多講了,這個當作人體 debugger 一行一行寫出來就好,而這個函式做的檢查是:確定陣列前兩項的和是不是等於下一項,是的話就回傳 valid,如果不是或者陣列裡面有元素 <=0 的話,也回傳 invalid。 88 | 89 | 對於這種「前兩項的和要等於下一項」的數列,有個專有名詞叫做費氏數列,有興趣的朋友們可以去查查看,這個在電腦科學界裡面非常有名。 -------------------------------------------------------------------------------- /examples/week21/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## 本週參考 6 | 7 | 1. [HW1 todo list(無編輯功能)](https://codesandbox.io/s/week21-todo-list-example-ot7fg?file=/src/App.js) 8 | 2. [HW1 todo list(有編輯功能)](https://codesandbox.io/s/di-21-zhou-todo-list-kebianjiban-mzo11 9 | ) 10 | 3. [HW2 五子棋](https://codesandbox.io/s/di-21-zhouwuziqi-h7j1t?file=/src/App.js) 11 | 12 | ## 關於 state 的改變 13 | 14 | 這邊建議大家去找更多關於 React 裡面 useState 相關的文章,因為有些部分課程沒有仔細提到。 15 | 16 | 可以參考: 17 | 18 | 1. [關於 useState,你需要知道的事](https://medium.com/@xyz030206/%E9%97%9C%E6%96%BC-usestate-%E4%BD%A0%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E4%BA%8B-5c8c4cdda82c) 19 | 2. [Functional updates](https://reactjs.org/docs/hooks-reference.html#functional-updates) 20 | 21 | React 的 useState 中 updateState 的部分基本上是非同步的,所以如果你有一個 object 的 state: 22 | 23 | ``` js 24 | const [data, setData] = useState({ a: 1, b: 1}) 25 | ``` 26 | 27 | 然後你在某個 function 裡面連續做兩次 update: 28 | 29 | ``` js 30 | setData({ 31 | ...data, 32 | a: 2, 33 | }) 34 | setData({ 35 | ...data, 36 | b: 2 37 | }) 38 | ``` 39 | 40 | 呼叫完第一次的 setData 以後,data 不會變,依舊是 `{a: 1, b: 1}`,所以你這邊執行完,新的 data 會是 `{a: 1, b: 2}` 而不是預期中的 `{a: 2, b: 2}` 41 | 42 | 如果你要連續改變同一個 state,請用 functional update 的方式,就可以變成你想做的樣子: 43 | 44 | ``` js 45 | setData(data => ({ 46 | ...data, 47 | a: 2, 48 | })) 49 | setData(data => ({ 50 | ...data, 51 | b: 2 52 | })) 53 | ``` 54 | 55 | ## controlled component 與 uncontrolled component 56 | 57 | 有點忘記這邊課程有沒有提到,但這很重要: 58 | 59 | 1. https://reactjs.org/docs/glossary.html#controlled-vs-uncontrolled-components 60 | 61 | ## 關於五子棋判斷邏輯 62 | 63 | 如果某方下了一顆棋子然後產生勝負的話,就代表最後一顆落子一定在連成一線當中,才會產生勝負,因此只要朝那顆棋子的周邊去搜就可以了。 64 | 65 | 可以寫一個很 general 的 function,能夠傳入最後一個的 x, y 以及要判斷的方向: 66 | 67 | ``` js 68 | function countTotal(currentX, currentY, directionX, directionY) { 69 | 70 | // 現在要檢查的棋子是什麼顏色 71 | const now = board[currentX][currentY] 72 | 73 | let tempX = currentX 74 | let tempY = currentY 75 | let total = 0 76 | do { 77 | tempX += directionX // 檢查下一顆棋子 78 | tempY += directionY 79 | 80 | // 如果新的棋子等於我現在要檢查的(意思就是連續啦) 81 | if (board[tempX][tempY] === now) { 82 | 83 | // 連續的棋子數 + 1 84 | total++ 85 | } else { 86 | break 87 | } 88 | } 89 | return total 90 | } 91 | ``` 92 | 93 | 這樣就可以得到某個方向的總數,於是判斷勝負的程式碼就可以這樣寫: 94 | 95 | ``` js 96 | if ( 97 | countTotal(x, y, 1, 0) + countTotal(x, y, -1, 0) >= 4 || 98 | countTotal(x, y, 0, 1) + countTotal(x, y, 0, -1) >= 4 || 99 | countTotal(x, y, 1, 1) + countTotal(x, y, -1, -1) >= 4 || 100 | countTotal(x, y, 1, -1) + countTotal(x, y, -1, 1) >= 4 101 | ) { 102 | // do something 103 | } 104 | ``` 105 | 106 | 優雅地解決了這一題,而且程式碼可能還比窮舉法少XD 107 | 108 | 然後如果你是用一維陣列去存棋盤的話,建議不要,因為會變得很麻煩,用二維方便很多。 -------------------------------------------------------------------------------- /examples/week22/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## 有了 react-router 之後的 deploy 6 | 7 | 有些問題需要注意,第一個問題可參考:[淺談新手在學習 SPA 時的常見問題:以 Router 為例](https://blog.huli.tw/2019/09/18/spa-common-problem-about-router/) 8 | 9 | 第二個問題是你在 deploy 的時候有可能需要調整一些參數,例如說 create-react-app 的 homepage 以及 react-router 的 basename,詳情可參考:[Building for Relative Paths](https://create-react-app.dev/docs/deployment#building-for-relative-paths) 10 | 11 | ## 本週作業範例 12 | 13 | 請參考:https://github.com/aszx87410/react-board-test/tree/blog-finished 14 | 15 | (在 blog-finished 這個 branch 裡面) 16 | 17 | ## 有關分頁 18 | 19 | 太多人分頁都在前端做了,這是不好的做法,想一個問題就行了: 20 | 21 | > 如果有一千篇文章怎麼辦? 22 | 23 | 你不可能只要看第一頁,結果抓 1000 篇文章下來吧? 24 | 25 | 正確做法是每換一次頁面就打 api 去抓相對應的文章,要看幾篇就抓幾篇,api 有支援 `_limit` 跟 `_page` 之類的,然後 response header 有總共的文章數量,不要再把分頁做在前端了。 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/week23/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## 有關 Redux 6 | 7 | 簡單再來講一下 Redux 是什麼,它其實就是另外一個存放 state 的地方。 8 | 9 | 為什麼 React component 已經可以存了,卻還要另一個地方呢?因為有些 state 你不知道要放在哪個 component 底下,好像放哪裡都很奇怪,例如說一些 global 的 state。 10 | 11 | 這時候 Redux 就是一個可以讓你放這些 state 的地方。在 Redux 的運作模式裡面,你要改變 state 就要 dispatch 一個 action,action 到 reducer 之後會產生新的 state,就把 state 改變了。 12 | 13 | 然後 react-redux 就是把 Redux 的 state 給自動綁定到 component 的 props,也順便把 dispatch 一起傳進去,把 React 跟 Redux 給綁在一起,這樣才能使用。 14 | 15 | ## 好文推薦 16 | 17 | 1. [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) 18 | 19 | 另外 Redux 官方文件真的很讚,把它認真看完你對 Redux 的理解會超越我(或是差我一點點啦) 20 | 21 | 再來推薦一個必看的,叫做 [Redux Style Guide](https://redux.js.org/style-guide/style-guide),會釐清很多你對 redux 的疑惑,如果想看中文版的話這邊有: 22 | 23 | 1. [Redux Style Guide (中文翻譯) — Part 1](https://medium.com/@a401120174/tr-85e00315cd73) 24 | 2. [Redux Style Guide (中文翻譯) — Part 2](https://medium.com/@a401120174/redux-%E5%AE%98%E6%96%B9%E9%A2%A8%E6%A0%BC%E6%8C%87%E5%8D%97-%E8%AD%AF-part-2-a438fb544b61) 25 | 26 | 再推薦一篇:[Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior](https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/) 27 | -------------------------------------------------------------------------------- /examples/week24/README.md: -------------------------------------------------------------------------------- 1 | # 注意事項 2 | 3 | 提示:在寫完作業之後看效果最佳,沒寫作業前請不要看 4 | 5 | ## 有關 Redux middleware 6 | 7 | 先來說說 Redux 為什麼要有 middleware,以及 middleware 到底是什麼。 8 | 9 | 簡單來說呢,你的 action 在 dispatch 以後,在抵達 reducer 之前,就會經過 middleware,因此流程是這樣的: 10 | 11 | action -> middleware1 -> middleware2 -> reducer 12 | 13 | 所以你可以透過 middleware 對某些 action 做一些事情。 14 | 15 | 那為什麼需要 middleware?因為有些東西不適合放在 component 裡面去做,例如說直接去 call API。為什麼呢?因為這樣子 component 的邏輯就跟 api 的邏輯綁在一起了,很難拆分開來。或是從另一個角度去講,你很難測試。 16 | 17 | 考慮底下這個簡單的 component: 18 | 19 | ``` js 20 | class Posts extends React.Component { 21 | componentDidMount() { 22 | WebAPI.getPosts().then((posts) => { 23 | this.setState({ 24 | posts 25 | }) 26 | }) 27 | } 28 | 29 | render() { 30 | return ( 31 | this.props.posts.map(post => ) 32 | ) 33 | } 34 | } 35 | ``` 36 | 37 | 只要這個 component mount,就會去呼叫 API。 38 | 39 | 我剛剛提到的測試是什麼意思? 40 | 41 | 我們可能會想測試說:「這個 component render 以後是不是真的會去呼叫 API」。 42 | 43 | 你可能會想說:「那現在這樣不是很好嗎?」 44 | 45 | No no no,我們其實不用真的去測試「送出 request」這一塊,只要能確定「WebAPI.getPosts」這個 function 有被呼叫就行了。為什麼呢?因為我們還會對「WebAPI.getPosts」這個 function 寫另外的測試,確保它有送出 request。 46 | 47 | 簡單來說,測試是會分層的,你不應該把所有層次都混在一起。 48 | 49 | 以上面的例子來說,混在一起就是代表說當我測試 Posts 會不會呼叫 API 的時候,我測了兩個東西: 50 | 51 | 1. Posts 會不會呼叫 WebAPI.getPosts 52 | 2. WebAPI.getPosts 是否真的會送出 request 53 | 54 | 但其實我們只要測試第一個就好了,因為第二個是 WebAPI 這個 module 的事情,不關我們的事。 55 | 56 | 那怎樣比較好?我們來看個範例: 57 | 58 | ``` js 59 | class Posts extends React.Component { 60 | componentDidMount() { 61 | this.props.getPosts() 62 | } 63 | 64 | render() { 65 | return ( 66 | this.props.posts.map(post => ) 67 | ) 68 | } 69 | } 70 | ``` 71 | 72 | 這邊不是直接去呼叫 API,而是利用 props 提供的 function 去做呼叫。 73 | 74 | 這樣的好處是什麼? 75 | 76 | 好處是我只要檢查 component 是不是有呼叫 `getPosts` 就好了,一切就結束了。 77 | 78 | 那 getPosts 又要怎麼寫呢?最簡單的寫法是這樣: 79 | 80 | ``` js 81 | const mapDispatchToProps = dispatch => { 82 | return { 83 | getPosts: function() { 84 | WebAPI.getPosts().then((posts) => { 85 | dispatch(actions.setPosts(posts)) 86 | }) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | 把邏輯寫在這個地方,這樣就可以在呼叫 action 的時候去 call API 了。 93 | 94 | 可是這樣也很奇怪啊,會造成呼叫 API 的邏輯跟 mapDispatchToProps 綁在一起了,而且四散各地,假設你有 10 個不同的 component 都要呼叫各自的 API,你的呼叫 API 的邏輯就散佈在十個檔案之中。 95 | 96 | 那要怎樣才能夠拆開呢? 97 | 98 | 這就要靠 redux 的 middleware 啦,先來介紹耳熟能詳的 [redux-thunk](https://github.com/reduxjs/redux-thunk),概念很簡單,就是把 action 變成 function,然後 thunk 會幫你執行這個 function。 99 | 100 | 以上面的例子來改寫,會變成這樣: 101 | 102 | ``` js 103 | const mapDispatchToProps = dispatch => { 104 | return { 105 | getPosts: function() { 106 | dispatch(actions.getPosts()) 107 | } 108 | } 109 | } 110 | 111 | // actions.js 112 | function getPosts() { 113 | // redux-thunk 會幫你把 dispatch 傳進來 114 | return function(dispatch) { 115 | WebAPI.getPosts.then(posts => { 116 | dispatch(actions.setPosts(posts)) 117 | }) 118 | } 119 | } 120 | ``` 121 | 122 | 如此一來,你就把 call API 的邏輯移到了 action 裡面,就不會四散於各地,不會存在於 component 當中。 123 | 124 | 其實很推薦大家可以看看 redux-thunk 的[原始碼](https://github.com/reduxjs/redux-thunk/blob/master/src/index.js),只有 14 行而已: 125 | 126 | ``` js 127 | function createThunkMiddleware(extraArgument) { 128 | return ({ dispatch, getState }) => (next) => (action) => { 129 | if (typeof action === 'function') { 130 | return action(dispatch, getState, extraArgument); 131 | } 132 | 133 | return next(action); 134 | }; 135 | } 136 | 137 | const thunk = createThunkMiddleware(); 138 | thunk.withExtraArgument = createThunkMiddleware; 139 | 140 | export default thunk; 141 | ``` 142 | 143 | 重點在第三行,判斷如果 action 是個 function 就幫你執行,然後把參數傳給你,就是這麼簡單而已。 144 | 145 | 好,看起來 redux-thunk 的解法不錯啊,把 action 變成一個 function,就可以把相關邏輯都放在這邊了。 146 | 147 | 但其實還可以更好,那就是把邏輯從 action 裡面拿掉,再拆分出來一層。這樣就可以保持 action 都是 pure action(純 JS 物件),會更方便測試。 148 | 149 | 而這樣做的 middleware 有兩套,一套叫做 [redux-saga](https://github.com/redux-saga/redux-saga),另一套叫做 [redux-observable](https://redux-observable.js.org/)。 150 | 151 | 其實這兩套的底層概念是一樣的,就是 pure action in, pure action out。 152 | 153 | 再次舉上面同樣的例子,改寫成 redux-saga 或是 redux-observable 之後,action 不再是個 function,而是純粹物件形式的 action: 154 | 155 | ``` js 156 | const mapDispatchToProps = dispatch => { 157 | return { 158 | getPosts: function() { 159 | dispatch(actions.getPosts()) 160 | } 161 | } 162 | } 163 | 164 | // actions.js 165 | // pure action object 166 | function getPosts() { 167 | return { 168 | type: 'GET_POSTS' 169 | } 170 | } 171 | ``` 172 | 173 | 那要在哪邊呼叫 API 呢?在 middleware 裡面去針對不同的 action 來做出反應。middleware 的概念會像這樣(非正式程式碼): 174 | 175 | ``` js 176 | function handleAction(action, dispatch) { 177 | if (action.type === 'GET_POSTS') { 178 | WebAPI.getPosts.then(posts => { 179 | dispatch(actions.setPosts(posts)) 180 | }) 181 | } 182 | } 183 | ``` 184 | 185 | 在 middleware 裡面執行任何會造成 side effect 的程式碼(side effect 就是副作用,以程式來講就是像 call API、寫 cookie 或是寫入 local storage 這些額外的操作) 186 | 187 | 如此一來,在 component 裡面就只需要 dispatch 一個 pure action,而非同步的處理就在 middleware 裡面處理就好,就不需要再把 action 變成 function。 188 | 189 | 這是 redux-saga 與 redux-observable 這兩套與 redux-thunk 跟 redux-promise 最不一樣的地方。在這兩套裡面,action 一定是 pure action,不會是 function 也不會是 promise。 190 | 191 | 其實我原本沒有預期大家用 redux-saga 跟 redux-observable 這兩套啦,因為這兩套的門檻我自己覺得頗高,我當初研究的時候研究老半天還是不知道 saga 在衝三小。 192 | 193 | 不過我看到有同學去研究而且使用了,就想說順便提一下這幾個 middleware 的差異。 194 | 195 | 更多細節可以參考我之前 Modern Web 2018 的演講:[輕鬆應付複雜的非同步操作:RxJS Redux Observable - 胡立 (huli)](https://hackmd.io/c/MW18/%2F2X5MCfKoQxWOCOpZ7tqsgA),裡面有附上投影片,可以直接跳到最後面的地方(113~119 頁),忽略前面 RxJS 的部分。 196 | -------------------------------------------------------------------------------- /examples/week3/challenges.md: -------------------------------------------------------------------------------- 1 | ### 挑戰題 2 | 3 | 如果你的方法跟我不太一樣,那很有可能得不出正確答案。最簡單的測試方法是去 [LIOJ](https://oj.lidemy.com/problem/1053),答案對就是對,錯就是錯。 4 | 5 | 另外,這題不會也沒關係,如果看了我的講解看不懂也沒關係,因為這是以後我在演算法課程裡面想要教的,放在這邊其實太早。而且都說了這是超級挑戰題,不是給一般的學生解的。 6 | 7 | 先大概講一下這題的解題方向,其實就是「我把每個點走過一遍,求出走到每個點的最少步驟就好了」。 8 | 9 | 我們可以先宣告一個二維陣列 `ans[y][x]` 來儲存「從起點走到 (x,y)」的最短距離。所以預設值可以設成無限大,方便我們之後來做判斷。再來的話 ans[0][0] 顯然是 0,因為從起點走到起點最短當然是 0 步。 10 | 11 | 接下來呢,我們可以宣告一個 Queue,把想要走的點都丟進去,所以我們先把起點丟進去。 12 | 13 | 然後我們會一直把 Queue 裡面的點拿出來看,試著往四個方向去走走看,可是要怎麼決定是不是該走? 14 | 15 | 假設現在 Queue 裡面的點是 A,我們想要走到 B 點。 16 | 17 | 如果: 18 | 19 | 1. B 點可以走(不是牆壁)或是 B 點沒走過 20 | 2. 而且「從起點走到 A 加上從 A 走到 B 的」距離比「從起點走到 B」的距離還要近的話 21 | 22 | 那就應該走,反之就不該走 23 | 24 | 如果該走的話,就更新走到 B 的最短距離,然後把點放到 Queue 裡面,等 Queue 清空就代表所有可能都已經找過,ans 就是答案了。 25 | 26 | 程式碼: 27 | 28 | ``` js 29 | function solve(map) { 30 | let h = 10 31 | let w = 10 32 | let startX = 0 33 | let startY = 0 34 | let endX = w - 1 35 | let endY = h - 1 36 | let ans = [] 37 | for(let i=0; i= w || newX < 0 || newY >= h || newY < 0 || map[newY][newX] !== '.') continue 55 | if (ans[y][x] + 1 >= ans[newY][newX] && ans[newY][newX] !== undefined) continue 56 | ans[newY][newX] = ans[y][x] + 1 57 | queue.push({x: newX, y: newY}) 58 | } 59 | } 60 | console.log(ans[endY][endX]) 61 | } 62 | 63 | solve([ 64 | '..########', 65 | '#........#', 66 | '########.#', 67 | '#........#', 68 | '#.########', 69 | '#........#', 70 | '########.#', 71 | '#........#', 72 | '#.######.#', 73 | '########..' 74 | ]) 75 | ``` 76 | 77 | ### 超級挑戰題 78 | 79 | 這題我還沒想好怎樣講解會比較好,所以先不講大家查到的「動態規劃」應該怎麼講。我先給一個暴力搜尋的版本: 80 | 81 | ``` js 82 | var readline = require('readline'); 83 | 84 | var lines = [] 85 | var rl = readline.createInterface({ 86 | input: process.stdin 87 | }); 88 | 89 | rl.on('line', function (line) { 90 | lines.push(line) 91 | }); 92 | 93 | rl.on('close', function() { 94 | let [n, weight] = lines[0].split(' ').map(Number) 95 | let w = [] 96 | let p = [] 97 | for(let i=1; i= w.length) 114 | // 就把 max 回傳,目前的最大價值就是答案 115 | if (index >= w.length) return max 116 | 117 | // 如果不拿第 index 項 118 | // 最大的價值就是 dfs(w, p, max, index + 1, left) 119 | let a = dfs(w, p, max, index + 1, left) 120 | 121 | let b = -1 122 | // 如果這項物品能放進去,才去算放進去之後可以獲得多少價值 123 | // 否則的話根本不用算,因為放不進去 124 | if (left >= w[index]) { 125 | // 如果拿了第 index 項 126 | // 最大的價值就會是:dfs(w, p, max + p[index], index + 1, left-w[index]) 127 | b = dfs(w, p, max + p[index], index + 1, left-w[index]) 128 | } 129 | 130 | // 拿跟不拿兩個做比較,回傳比較大的那個就是答案 131 | return a > b ? a : b 132 | } 133 | ``` 134 | 135 | ### 超級超級挑戰題 136 | 137 | 等 week3 結束再來寫XD 138 | -------------------------------------------------------------------------------- /examples/week4/challenge.js: -------------------------------------------------------------------------------- 1 | /* 2 | 這一題的重點是,與其把所有東西都用原生的 https 來改寫 3 | 不如只改寫 request 這個 function,這樣剩下的都不用改 4 | 超級方便XD 5 | */ 6 | 7 | // 用原生套件寫一個陽春版的 request 套件 8 | const https = require('https'); 9 | function request(url, callback) { 10 | sendRequest(url, { 11 | method: 'GET' 12 | }, callback); 13 | } 14 | 15 | request.delete = function(url, callback) { 16 | sendRequest(url, { 17 | method: 'DELETE' 18 | }, callback); 19 | } 20 | 21 | request.post = function({ url, form }, callback) { 22 | sendRequest(url, { 23 | method: 'POST', 24 | data: form, 25 | }, callback); 26 | } 27 | 28 | request.patch = function({ url, form }, callback) { 29 | sendRequest(url, { 30 | method: 'PATCH', 31 | data: form, 32 | }, callback) 33 | } 34 | 35 | // 附註:這個方法目前對於 POST 跟 PATCH 只支援 json 格式的輸入 36 | // 其他格式(例如說 application/x-www-form-urlencoded)不支援XD 37 | function sendRequest(url, { 38 | method, 39 | data 40 | }, callback) { 41 | const options = { 42 | method, 43 | }; 44 | 45 | if (data) { 46 | options.headers = { 47 | 'Content-Type': 'application/json', 48 | }; 49 | } 50 | 51 | https.request(url, options, res => { 52 | let body = ''; 53 | res.on('data', chunk => body += chunk); 54 | res.on('end', () => { 55 | callback(null, res, body); 56 | }); 57 | res.on('err', err => { 58 | callback(err, res, body); 59 | }) 60 | }) 61 | .end(data && JSON.stringify(data)); 62 | } 63 | 64 | // 底下這邊的程式碼完全沒變,跟原本的 hw2.js 一模一樣 65 | const args = process.argv; 66 | const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com'; 67 | 68 | const action = args[2]; 69 | const params = args[3]; 70 | 71 | switch(action) { 72 | case 'list': 73 | listBooks(); 74 | break; 75 | case 'read': 76 | readBook(params); 77 | break; 78 | case 'delete': 79 | deleteBook(params); 80 | break; 81 | case 'create': 82 | createBook(params); 83 | break; 84 | case 'update': 85 | updateBook(params, args[4]); 86 | break; 87 | default: 88 | console.log('Available commands: list, read, delete, create and update'); 89 | } 90 | 91 | function listBooks() { 92 | request(`${API_ENDPOINT}/books?_limit=20`, (err, res, body) => { 93 | if (err) { 94 | return console.log('抓取失敗', err); 95 | } 96 | const data = JSON.parse(body); 97 | for (let i = 0; i < data.length; i += 1) { 98 | console.log(`${data[i].id} ${data[i].name}`); 99 | } 100 | }) 101 | } 102 | 103 | function readBook(id) { 104 | request(`${API_ENDPOINT}/books/${id}`, (err, res, body) => { 105 | if (err) { 106 | return console.log('抓取失敗', err); 107 | } 108 | const data = JSON.parse(body); 109 | console.log(data) 110 | }) 111 | } 112 | 113 | function deleteBook(id) { 114 | request.delete(`${API_ENDPOINT}/books/${id}`, (err, res, body) => { 115 | if (err) { 116 | return console.log('刪除失敗', err); 117 | } 118 | console.log('刪除成功!'); 119 | }) 120 | } 121 | 122 | function createBook(name) { 123 | request.post({ 124 | url: `${API_ENDPOINT}/books`, 125 | form: { 126 | name 127 | } 128 | }, (err, res) => { 129 | if (err) { 130 | return console.log('新增失敗', err); 131 | } 132 | console.log('新增成功!'); 133 | }) 134 | } 135 | 136 | function updateBook(id, name) { 137 | request.patch({ 138 | url: `${API_ENDPOINT}/books/${id}`, 139 | form: { 140 | name 141 | } 142 | }, (err, res) => { 143 | if (err) { 144 | return console.log('更新失敗', err); 145 | } 146 | console.log('更新成功!'); 147 | }) 148 | } -------------------------------------------------------------------------------- /examples/week4/hw1.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com'; 3 | 4 | request(`${API_ENDPOINT}/books?_limit=10`, (err, res, body) => { 5 | if (err) { 6 | return console.log('抓取失敗', err); 7 | } 8 | let data 9 | try { 10 | data = JSON.parse(body); 11 | } catch(err) { 12 | console.log(err); 13 | return 14 | } 15 | for (let i = 0; i < data.length; i += 1) { 16 | console.log(`${data[i].id} ${data[i].name}`); 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /examples/week4/hw2.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const args = process.argv; 3 | const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com'; 4 | 5 | const action = args[2]; 6 | const params = args[3]; 7 | 8 | switch(action) { 9 | case 'list': 10 | listBooks(); 11 | break; 12 | case 'read': 13 | readBook(params); 14 | break; 15 | case 'delete': 16 | deleteBook(params); 17 | break; 18 | case 'create': 19 | createBook(params); 20 | break; 21 | case 'update': 22 | updateBook(params, args[4]); 23 | break; 24 | default: 25 | console.log('Available commands: list, read, delete, create and update'); 26 | } 27 | 28 | function listBooks() { 29 | request(`${API_ENDPOINT}/books?_limit=20`, (err, res, body) => { 30 | if (err) { 31 | return console.log('抓取失敗', err); 32 | } 33 | const data = JSON.parse(body); 34 | for (let i = 0; i < data.length; i += 1) { 35 | console.log(`${data[i].id} ${data[i].name}`); 36 | } 37 | }) 38 | } 39 | 40 | function readBook(id) { 41 | request(`${API_ENDPOINT}/books/${id}`, (err, res, body) => { 42 | if (err) { 43 | return console.log('抓取失敗', err); 44 | } 45 | const data = JSON.parse(body); 46 | console.log(data) 47 | }) 48 | } 49 | 50 | function deleteBook(id) { 51 | request.delete(`${API_ENDPOINT}/books/${id}`, (err, res, body) => { 52 | if (err) { 53 | return console.log('刪除失敗', err); 54 | } 55 | console.log('刪除成功!'); 56 | }) 57 | } 58 | 59 | function createBook(name) { 60 | request.post({ 61 | url: `${API_ENDPOINT}/books`, 62 | form: { 63 | name 64 | } 65 | }, (err, res) => { 66 | if (err) { 67 | return console.log('新增失敗', err); 68 | } 69 | console.log('新增成功!'); 70 | }) 71 | } 72 | 73 | function updateBook(id, name) { 74 | request.patch({ 75 | url: `${API_ENDPOINT}/books/${id}`, 76 | form: { 77 | name 78 | } 79 | }, (err, res) => { 80 | if (err) { 81 | return console.log('更新失敗', err); 82 | } 83 | console.log('更新成功!'); 84 | }) 85 | } -------------------------------------------------------------------------------- /examples/week4/hw3.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const args = process.argv; 3 | const API_ENDPOINT = 'https://restcountries.eu/rest/v2'; 4 | 5 | const name = args[2]; 6 | 7 | if (!name) { 8 | return console.log('請輸入國家名稱'); 9 | } 10 | 11 | request(`${API_ENDPOINT}/name/${name}`, (err, res, body) => { 12 | if (err) { 13 | return console.log('抓取失敗', err); 14 | } 15 | const data = JSON.parse(body); 16 | if (data.status === 404) { 17 | return console.log('找不到國家資訊') 18 | } 19 | 20 | for (let i = 0; i < data.length; i++) { 21 | console.log('============') 22 | console.log('國家:' + data[i].name); 23 | console.log('首都:' + data[i].capital); 24 | console.log('貨幣:' + data[i].currencies[0].code); 25 | console.log('國碼:' + data[i].callingCodes[0]); 26 | } 27 | }) -------------------------------------------------------------------------------- /examples/week4/hw4.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | 3 | const CLIENT_ID = 's44s145uexjgeu9mqqa1s93oc1bnli'; 4 | const BASE_URL = 'https://api.twitch.tv/kraken'; 5 | 6 | 7 | request({ 8 | method: 'GET', 9 | url: `${BASE_URL}/games/top`, 10 | headers: { 11 | 'Client-ID': CLIENT_ID, 12 | 'Accept': 'application/vnd.twitchtv.v5+json' 13 | } 14 | }, function(err, res, body) { 15 | if (err) { 16 | return console.log(err) 17 | } 18 | 19 | const data = JSON.parse(body) 20 | const games = data.top 21 | for(let game of games) { 22 | console.log(game.viewers + ' ' + game.game.name) 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /examples/week4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "week4", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "twitch.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "request": "^2.88.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/week4/race-condition.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | 3 | const API_ENDPOINT = 'https://lidemy-book-store.herokuapp.com'; 4 | 5 | request(`${API_ENDPOINT}/books/1`, (err, res, body) => { 6 | console.log('1', body); 7 | }); 8 | 9 | request(`${API_ENDPOINT}/books/2`, (err, res, body) => { 10 | console.log('2', body); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/week4/race.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week4/race.png -------------------------------------------------------------------------------- /examples/week4/twitch.js: -------------------------------------------------------------------------------- 1 | // 備註:這個程式碼還需要別的東西才能跑,因為 Twitch API 規則有變 2 | // 所以基本上只是讓大家看個概念而已,概念絕對是相通的,可以自己換成舊的 API 試試看 3 | const request = require('request'); 4 | 5 | const BATCH_LIMIT = 4; 6 | const TOTAL_STREAMS = 13; 7 | const CLIENT_ID = 'ylx39xtx1ne6q7my9p4m50aadd190z'; 8 | const BASE_URL = 'https://api.twitch.tv/helix'; 9 | 10 | /* 11 | 搜尋遊戲 12 | */ 13 | function searchGame(name, callback) { 14 | request({ 15 | method: 'GET', 16 | url: `${BASE_URL}/games?name=${name}`, 17 | headers: { 18 | 'Client-ID': CLIENT_ID 19 | } 20 | }, callback) 21 | } 22 | 23 | /* 24 | callback 會回傳「全部」的實況列表 25 | gameId: 遊戲 id 26 | limit: 一次要抓幾筆 27 | total: 總共要抓幾筆 28 | callback: 抓完之後要呼叫的 function 29 | */ 30 | function getAllStreams(gameId, limit, total, callback) { 31 | let streams = []; 32 | function handleStreams(err, res, body) { 33 | // 錯誤發生的話直接把 err 傳回去 34 | if (err) { 35 | return callback(err) 36 | } 37 | 38 | const json = JSON.parse(body); 39 | const cursor = json.pagination.cursor; 40 | 41 | // 拼接上新的資料 42 | streams = streams.concat(json.data); 43 | 44 | // 如果還沒抓夠的話 45 | if (streams.length < total) { 46 | // 就再抓一次,這次傳進 cursor 47 | getStreams(gameId, limit, cursor, handleStreams) 48 | } else { 49 | // 抓完了,把 streams 傳回去,記得用 slice 切出正確數量 50 | // 如果沒有 slice 的話,一次抓 10 筆,要抓 21 筆,最後會回傳 30 筆 51 | callback(null, streams.slice(0, total)); 52 | } 53 | } 54 | 55 | // 抓第一次資料 56 | getStreams(gameId, limit, null, handleStreams); 57 | } 58 | 59 | /* 60 | callback 會根據參數回傳部分實況列表 61 | */ 62 | function getStreams(gameId, first, after, callback) { 63 | let url = `${BASE_URL}/streams?game_id=${gameId}&first=${first}` 64 | if (after) { 65 | url += '&after=' + after 66 | } 67 | request({ 68 | method: 'GET', 69 | url: url, 70 | headers: { 71 | 'Client-ID': CLIENT_ID 72 | } 73 | }, callback) 74 | } 75 | 76 | // 先搜尋遊戲 77 | // node twitch.js "league of legends" 78 | searchGame(process.argv[2], (err, res, body) => { 79 | if (err) { 80 | return console.log(err); 81 | } 82 | 83 | // 再利用 gameId 去獲得所有 streams 84 | const gameId = JSON.parse(body).data[0].id; 85 | getAllStreams(gameId, BATCH_LIMIT, TOTAL_STREAMS, (err, streams) => { 86 | if (err) { 87 | return console.log(err); 88 | } 89 | for (let i = 0; i < streams.length; i += 1) { 90 | console.log(streams[i].user_name, streams[i].id); 91 | } 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /examples/week4/twitch2.js: -------------------------------------------------------------------------------- 1 | // 備註:這個程式碼還需要別的東西才能跑,因為 Twitch API 規則有變 2 | // 所以基本上只是讓大家看個概念而已,概念絕對是相通的,可以自己換成舊的 API 試試看 3 | const request = require('request'); 4 | 5 | const BATCH_LIMIT = 4; 6 | const TOTAL_STREAMS = 13; 7 | const CLIENT_ID = 'ylx39xtx1ne6q7my9p4m50aadd190z'; 8 | const BASE_URL = 'https://api.twitch.tv/helix'; 9 | 10 | /* 11 | 搜尋遊戲 12 | */ 13 | function searchGame(name, callback) { 14 | request({ 15 | method: 'GET', 16 | url: `${BASE_URL}/games?name=${name}`, 17 | headers: { 18 | 'Client-ID': CLIENT_ID 19 | } 20 | }, callback) 21 | } 22 | 23 | /* 24 | callback 會回傳「全部」的實況列表 25 | gameId: 遊戲 id 26 | limit: 一次要抓幾筆 27 | total: 總共要抓幾筆 28 | callback: 抓完之後要呼叫的 function 29 | */ 30 | function getAllStreams(gameId, limit, total, callback) { 31 | let streams = []; 32 | function handleStreams(err, res, body) { 33 | // 錯誤發生的話直接把 err 傳回去 34 | if (err) { 35 | return callback(err) 36 | } 37 | 38 | const json = JSON.parse(body); 39 | const cursor = json.pagination.cursor; 40 | 41 | // 拼接上新的資料 42 | streams = streams.concat(json.data); 43 | 44 | // 如果還沒抓夠的話 45 | if (streams.length < total) { 46 | // 就再抓一次,這次傳進 cursor 47 | getStreams(gameId, limit, cursor, handleStreams) 48 | } else { 49 | // 抓完了,把 streams 傳回去,記得用 slice 切出正確數量 50 | // 如果沒有 slice 的話,一次抓 10 筆,要抓 21 筆,最後會回傳 30 筆 51 | callback(null, streams.slice(0, total)); 52 | } 53 | } 54 | 55 | // 抓第一次資料 56 | getStreams(gameId, limit, null, handleStreams); 57 | } 58 | 59 | /* 60 | callback 會根據參數回傳部分實況列表 61 | */ 62 | function getStreams(gameId, first, after, callback) { 63 | let url = `${BASE_URL}/streams?game_id=${gameId}&first=${first}` 64 | if (after) { 65 | url += '&after=' + after 66 | } 67 | request({ 68 | method: 'GET', 69 | url: url, 70 | headers: { 71 | 'Client-ID': CLIENT_ID 72 | } 73 | }, callback) 74 | } 75 | 76 | // 先搜尋遊戲 77 | // node twitch.js "league of legends" 78 | searchGame(process.argv[2], (err, res, body) => { 79 | if (err) { 80 | return console.log(err); 81 | } 82 | 83 | // 再利用 gameId 去獲得所有 streams 84 | const gameId = JSON.parse(body).data[0].id; 85 | getAllStreams(gameId, BATCH_LIMIT, TOTAL_STREAMS, (err, streams) => { 86 | if (err) { 87 | return console.log(err); 88 | } 89 | for (let i = 0; i < streams.length; i += 1) { 90 | console.log(streams[i].user_name, streams[i].id); 91 | } 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /examples/week5/README.md: -------------------------------------------------------------------------------- 1 | # Week5 show time! 2 | 3 | ## 公車動態爬蟲 4 | 5 | 程式碼放在檔案 `bus.js` 裡面。 6 | 7 | 不過這種爬蟲如果原始資料變更就壞掉了,希望這幾個月內不要變XD 8 | 9 | ## 發信小程式 10 | 11 | 程式碼放在 `send.js` 裡面。 12 | 13 | 運用的網站是 [mailgun](https://www.mailgun.com/),只是設定有一點小麻煩(不過其實每間都很麻煩啦) 14 | 15 | -------------------------------------------------------------------------------- /examples/week5/bus.js: -------------------------------------------------------------------------------- 1 | var request = require('request') 2 | 3 | const url = 'https://pda.5284.gov.taipei/MQS/businfo2.jsp?routename=' 4 | 5 | function getBusData(number, cb) { 6 | request(url + number, (err, response) => { 7 | if (err){ 8 | cb(err) 9 | return 10 | } 11 | 12 | cb(null, response.body) 13 | }) 14 | } 15 | 16 | getBusData(process.argv[2], (err, html) => { 17 | if (err) { 18 | return console.log(err) 19 | } 20 | 21 | let result = getStopInfo(html) 22 | console.log(result.join('\n')) 23 | }) 24 | 25 | function getStopInfo(html) { 26 | let result = [] 27 | let start = 0 28 | let end 29 | 30 | while(start >= 0) { 31 | start = html.indexOf(' data !== '') 12 | .map(data => { 13 | let temp = data.split(' ') 14 | return { 15 | to: temp[0], 16 | name: temp[1] 17 | } 18 | }) 19 | 20 | const subjectTemplate = `Long time no see, {{name}}!` 21 | 22 | const messageTemplate = ` 23 | Hello, {{name}} 24 | How are you today? 25 | https://google.com 26 | ` 27 | 28 | sendEmailIndex(0) 29 | 30 | // 我要寄給 emails[index] 的人 31 | function sendEmailIndex(index) { 32 | if (emails.length <= index) return 33 | let {to, name} = emails[index] 34 | console.log('sending email to', to) 35 | send( 36 | to, 37 | subjectTemplate.replace('{{name}}', name), 38 | messageTemplate.replace('{{name}}', name), 39 | function(err, body) { 40 | if (err) { 41 | console.log('QQ', err) 42 | return 43 | } 44 | console.log(body) 45 | sendEmailIndex(index + 1) 46 | } 47 | ) 48 | } 49 | 50 | function send(to, subject, text, cb) { 51 | const data = { 52 | from: 'Excited User ', 53 | to, 54 | subject, 55 | text 56 | }; 57 | mg.messages().send(data, function (error, body) { 58 | cb(error, body) 59 | }); 60 | } -------------------------------------------------------------------------------- /examples/week6/README.md: -------------------------------------------------------------------------------- 1 | # Week6 作業自我檢討 2 | 3 | ## 有關切版 4 | 5 | 我有點忘記我在影片中怎麼示範了,但我以前都會從桌面版開始切,然後再切平板,最後是手機,不過現在我改變方式了。 6 | 7 | 我現在習慣先從手機版開始切,先確保手機版是 ok 的,然後再針對 tablet 去寫不同的 CSS(用 `@media only screen and (min-width: 768px)`),最後是 desktop,一樣再用這種方式去寫。 8 | 9 | 這樣可以保證你東西不會壞掉,因為新增上去的 CSS 都是用 media query 放上去的,所以是新增,不是去修改本來的東西,我自己覺得這樣的切版方式比較順一點,大家可以參考看看,先從手機開始,然後才是平板再來桌面。 10 | 11 | ## 檢查作業 12 | 13 | 我在看作業的時候通常都會檢查一下作業的正確性,做一些很簡單的檢查,例如說: 14 | 15 | 1. 把 Chrome 視窗縮小,看看 RWD 有沒有實現好 16 | 17 | 但儘管只是這麼簡單的動作,卻發現還是滿多作業都有問題。如果是小問題倒還好,有些就是很明顯的跑版。像這種的話我真的滿好奇大家在作業做完之後有沒有檢查,是有檢查但沒看到,還是根本就忘記檢查。 18 | 19 | 再次提醒大家寫完作業記得檢查一下,那種很明顯的跑版就自己把它修掉再傳上來。 20 | 21 | 如果你是因為時間因素來不及微調,但又想要趕快進到下一週,麻煩在交作業的時候在 PR 裡面註明一下。 22 | 23 | ## 關於 position 24 | 25 | 未看先猜有一題你有可能答錯,那就是 position 那一題的 absolute。 26 | 27 | 常見錯誤是寫成「absolute 的定位點是父層」,或者是定位點是「上層的 absolute 元素」,這些都是錯的。 28 | 29 | 最完整的回答是:「absolute 的定位點是往上找第一個 position 不是 static 的元素」,有些人會把非 static 的屬性也都列出來(fixed/relative/absolute),但少了一個 sticky,而且未來也有可能會新增其他屬性,所以建議大家直接說「非 static」就好,輕鬆方便。 30 | 31 | ## class 命名慣例 32 | 33 | 命名 class 跟命名變數差不多,都會有個慣例。 34 | 35 | 大原則是只要用一套就好,不要混用。例如說你有些叫 `nav` 有些叫 `Header`,有些叫 `nav__image` 有些叫 `nav-text`,這就是混用。 36 | 37 | 然後在 class 的命名上面,比較常見的應該是用 `-` 來分隔而不是用我們在 JS 時會用的駝峰式命名。 38 | 39 | 舉例來說,比起 `mediumIcon`,`medium-icon`會是比較常見的命名方式。如果你發現你用了很多不同 style 的命名方式,麻煩自己修正完以後再交作業。 40 | -------------------------------------------------------------------------------- /examples/week6/examples.md: -------------------------------------------------------------------------------- 1 | # Week6 作業參考解答 2 | 3 | 這週的作業範例請參考底下的資料夾。 4 | 5 | ## 簡答題回答重點 6 | 7 | 這週的簡答題其實是面試常考題,學校老師會要你打三顆星星的那種。 8 | 9 | 盒模型那題的重點其實是 `box-sizing`,你要知道寬度的算法跟一般人認知中的不太一樣,並且知道很多人都喜歡設置: 10 | 11 | ``` css 12 | * { 13 | box-sizing: border-box; 14 | } 15 | ``` 16 | 17 | 就是這個原因,把全部都改成 `border-box` 比較接近我們認知中的寬度計算。 18 | 19 | display 的三種比較要考的是你知不知道三種的差異以及哪些屬性可以調整,最完整的回答會包含哪些可以調整 margin、padding,哪些不行。 20 | 21 | position 那一題主要考兩個,一個是你知不知道預設的定位是 static,另一個是你知不知道 absolute 的定位點怎麼找。 22 | 23 | 24 | ## 擴充性與過度工程化 25 | 26 | 在這週作業裡面看到很多同學都會直接對一些標籤下 CSS,優點當然就是快速,但缺點是什麼?缺點就是一旦你的 HTML 標籤變了,一旦你的結構小小改動一下,你的 CSS 就要重寫。 27 | 28 | 這也是為什麼你會看到一堆網站幾乎每個元素都有加 class,因為針對 class 去下 CSS,版面比較不容易壞掉。 29 | 30 | 不過這會面臨到一個兩難,幫大家介紹一個名詞,叫做過度工程化(Over Engineering),簡單來說就是把一個簡單的東西變得太複雜。 31 | 32 | 例如說要你寫一個 Hello world 的程式,結果你用物件導向包起來,還能夠支援一堆現在用不到的功能之類的。 33 | 34 | 在實務上我們會寫很多 class,那是因為我們為了未來的擴充性做考量,考量到之後這邊有可能會改動,為了讓未來的改動不會破壞掉太多現有的東西,所以選擇在當時比較複雜的方式。可是在作業裡,規模太小,東西太簡單,程式碼也就這幾行而已。 35 | 36 | 如果你在作業裡面寫了一大堆 class,似乎就有點過度工程化的感覺。 37 | 38 | 這邊提供一個滿好用的準則:當今天需求改動時,你要花多少力氣去調整? 39 | 40 | 舉例來說,當今天需要把一個 span 變成 div,如果你原本寫 CSS 是直接針對標籤,這時候你就要: 41 | 42 | 1. 調整 CSS,把 span 改成 div 43 | 44 | 可是如果你原本就有幫 HTML 標註 class,這時候你只要把 HTML 裡面的 span 改成 div 就沒事了,完全不用調整 CSS。 45 | 46 | 如果今天這個 project 有一百個地方要調整,顯然後者是花比較少力氣的,但如果今天只要調整一個地方,其實多出來的一個步驟根本不算什麼。這也是為什麼我會說「規模」很重要,在作業的規模裡面,似乎很多東西都不算什麼。 47 | 48 | 在寫作業的時候其實我覺得你要寫哪一種都可以,你要全部針對標籤下 CSS,或者是你要全部都加 class 都可以,因為是作業嘛!還可以順便實驗兩種的差別在哪邊,簡直是一舉兩得。 49 | 50 | 現在會提出來這件事只是希望大家心裡先把這個放著,知道背後有著這種考量,以後出去工作就會多注意這一塊。 51 | 52 | ## 如何精簡切版 53 | 54 | 在第六週的時候你可能會覺得切起來卡卡的,想要尋找怎麼樣持續進步的方法。 55 | 56 | 我會說,先緩緩吧! 57 | 58 | 因為之後第七週一路到第二十四週,每一週的作業都會要你切版,所以你不用特別準備什麼,利用每週作業就可以練習到切版了。所以不用特地去尋找什麼方法,先好好練習就好。 59 | 60 | 然後參考同學的作業也是個很棒的方法! 61 | -------------------------------------------------------------------------------- /examples/week6/hw1/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week6/hw1/avatar.png -------------------------------------------------------------------------------- /examples/week6/hw1/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week6/hw1/bg.jpg -------------------------------------------------------------------------------- /examples/week6/hw1/f-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week6/hw1/f-001.png -------------------------------------------------------------------------------- /examples/week6/hw1/f-002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week6/hw1/f-002.png -------------------------------------------------------------------------------- /examples/week6/hw2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 表單 8 | 9 | 10 | 11 | 95 | 96 | 97 | 98 |
99 |
100 |

新拖延運動報名表單

101 |

102 | 活動日期:2020/12/10 ~ 2020/12/11
103 | 活動地點:台北市大安區新生南路二段1號 104 |

105 |

106 | * 必填 107 |

108 |
109 |
110 | 暱稱 111 |
112 |
113 | 114 |
115 |
116 |
117 |
118 | 電子郵件 119 |
120 |
121 | 122 |
123 |
124 |
125 |
126 | 手機號碼 127 |
128 |
129 | 130 |
131 |
132 |
133 |
134 | 報名類型 135 |
136 |
137 | 140 | 141 | 145 |
146 |
147 |
148 |
149 | 怎麼知道這個活動的? 150 |
151 |
152 | 153 |
154 |
155 |
156 |
157 | 其他 158 |
159 |
160 | 對活動的一些建議 161 |
162 |
163 | 164 |
165 |
166 | 167 |

168 | 請勿透過表單送出您的密碼。 169 |

170 |
171 |
172 |
© 2020 © Copyright. All rights Reserved.
173 | 174 | 175 | -------------------------------------------------------------------------------- /examples/week7/README.md: -------------------------------------------------------------------------------- 1 | # Week7 作業自我檢討 2 | 3 | ## 捕獲與冒泡 4 | 5 | 可能是之前沒有講得很清楚,有些人會對捕獲與冒泡有一個誤解。那就是以為你可以透過 `addEventListener` 的第三個參數來「改變」先捕獲還是先冒泡,例如說傳 true 就會先捕獲,反之則會先冒泡之類的。 6 | 7 | 這邊強調一下,捕獲與冒泡是「無論如何」都會發生的,而且順序永遠不會改變的一個東西。當你點擊某個按鈕時,就會先從 window 一路把事件傳遞下去,再從按鈕一路把事件傳遞回來。 8 | 9 | 所以,儘管你什麼 event listener 都沒有加,背後的捕獲與冒泡還是存在。 10 | 11 | 但存在歸存在,你卻感覺不到,因為你沒有加上監聽器(listener)!大概就像是你的床或枕頭可能有一堆塵蟎,但直到你用什麼很厲害的儀器觀察之前你都不會特別注意到,但不代表他們不存在。 12 | 13 | 事件機制也是這樣的,捕獲跟冒泡這個流程永遠都在,但如果你沒有加監聽器,你是察覺不到的。所以 addEventListener 的第三個參數只是覺得你要在「哪邊」加上這個監聽器,而不是改變原本事件傳遞的流程。 14 | 15 | ## 多用 class,少直接改變 style 16 | 17 | 在用 JS 去操作 DOM 又想要改變畫面上的 style 的時候,很多人都會很直覺地直接寫下 `node.style.xxx = xxx` 這種程式碼。這樣當然可以,但其實有更好的做法。 18 | 19 | 更好的作法就是你先把改變後要的 style 寫到 css 裡面,然後寫成一個新的 class,在 JS 裡面你就只要新增這個 class 上去就好。 20 | 21 | 舉例來說: 22 | 23 | ``` css 24 | .btn-disable { 25 | color: white; 26 | background: black; 27 | cursor: none; 28 | } 29 | ``` 30 | 31 | 好處有幾個,第一個是方便觀察。 32 | 33 | 照以前的寫法,如果你想觀察某個狀態下的按鈕長什麼樣子,你只能再跑一次 JS,觸發到程式碼內改變 style 的地方。但是用 class 的話,你只要自己把這個元素加上 class 就可以看到它長什麼樣子了。 34 | 35 | 第二個好處是好維護。 36 | 37 | 當你要改變 style 的時候你只要改 css 就好,不需要再去改 JS。 38 | 39 | 再來,程式碼看起來也會乾淨很多,不會再有一大堆 JS 去改變 style。 40 | 41 | 相似的概念也可以用在一些元素的新增跟隱藏,有時候畫面要出現某個東西時你可能會用 JS 來新增。 42 | 43 | 但其實仔細想想,你可以先把這個按鈕寫好,然後用 `display: none` 藏起來,等需要的時候再把這個按鈕顯現出來就好!或者是融合我們上面所說的,把按鈕加上一個 `hide` 的 class,在需要時你只要:`btn.classList.remove('hide')`,按鈕就會顯示出來了,這樣程式碼乾淨很多。 44 | 45 | 46 | ## 程式的狀態儲存 47 | 48 | 我看到有滿多人都喜歡用 HTML 上面的元素來做一些判斷,或者是用 class 本身。 49 | 50 | 假設今天有一個點了畫面會變色的網頁,有些人變色之後可能幫背景加上一個 `bg-changed` 的 class,然後點畫面時根據背景有沒有這個 class 來決定變色了沒。 51 | 52 | 這其實都是很奇怪的做法,如果沒有一個好的理由,你不該用 HTML 元素的狀態或者是 class 去判斷現在程式的狀態。原因是,你有 JS 的變數可以用,而這邊是最適合儲存程式狀態的地方。 53 | 54 | 例如說你可以宣告一個變數叫做 `isBackgroundChanged` 來取代上面的 class 的判斷,這樣子才能把介面跟邏輯給切開,介面歸介面,邏輯歸邏輯,政治歸政治,忍者龜忍者(?)。 55 | 56 | 一旦把這些東西混在一起,當你要做改動的時候就會痛苦萬分。所以請大家慢慢學著程式邏輯相關的東西就是寫在 JS,就算你要多宣告一個變數也無妨,其實程式碼反而還會更容易讀。 57 | 58 | 其實上一點講的也是,只是是把 style 跟 JS 切開,透過 CSS 跟 class 把兩者連結在一起。 59 | 60 | ## Event listener 61 | 62 | 想一下,以底下的程式碼來說,按下按鈕以後會發生什麼事? 63 | 64 | ``` js 65 | function run() { 66 | document.querySelector('button').addEventListener('click', () => { 67 | console.log('click!') 68 | }) 69 | } 70 | run() 71 | run() 72 | run() 73 | ``` 74 | 75 | 答案是,會印出三次 `click`,因為你每呼叫一次函式,就會加上一個 event listener,所以有三個 onClick 的 event listenr 在 button 上面。每按下一次,就會依序執行這三個 listener。 76 | 77 | 這點是新手很常忽略的一點,有些會以為看起來是同一個 function,以為只會加一次。沒有,你呼叫幾次就會加幾次。 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/week7/hw3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Todo List 8 | 9 | 10 | 11 | 44 | 45 | 46 | 47 |
48 | 49 |
50 |
51 |
52 | 96 | 97 | -------------------------------------------------------------------------------- /examples/week8/README.md: -------------------------------------------------------------------------------- 1 | # Week8 作業自我檢討 2 | 3 | 可以觀看這個影片:https://www.youtube.com/watch?v=PNGRPYFcAms 4 | 5 | 不過這已經滿舊的了,裡面用的 Twitch API 跟這週實際用的也有些許不同,只是希望大家看個概念,實作的部分就輕輕看過去就好。 6 | 7 | ## 跨網域問題 8 | 9 | 大家一定要很清楚的知道一件事,那就是「跨網域」這個問題基本上只會在瀏覽器發生,因為安全性的關係。 10 | 11 | 這是我們第四週在自己的電腦上用自己寫的程式發 request,跟第八週透過瀏覽器來發 request 最大最大的差別。 12 | 13 | 這週唯一的重點就是這個,就是要讓你知道這件事。 14 | -------------------------------------------------------------------------------- /examples/week8/backup.md: -------------------------------------------------------------------------------- 1 | # Week8 作業自我檢討 2 | 3 | 背著:這是第三期其中一個作業的檢討,雖然這一期沒有這個作業了,但這一份檢討還是滿實用的,於是就先留著。 4 | 5 | https://github.com/Lidemy/mentor-program-3rd/tree/master/homeworks/week8#hw2%E7%95%99%E8%A8%80%E6%9D%BF 6 | 7 | ## URL encode 8 | 9 | 大概九成九的同學都有犯這個錯,但我沒有特別點出來,我想說一次點出來比較有效率。 10 | 11 | 這個錯是什麼呢?就是你在新增留言時這樣寫: 12 | 13 | ``` js 14 | `content=${content}` 15 | ``` 16 | 17 | 你把留言內容直接放進去,然後就 POST 出去了。 18 | 19 | 問題在哪邊?問題在於如果我想要發表的內容是 `1&a=2` 怎麼辦?就會變成 `content=1&a=2`,最後出來的 content 就只有 1 而已,因為後半段被判定成另外一個參數。 20 | 21 | 解法是「編碼」,這邊 JS 有現成的函式可以用:[encodeURIComponent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)。編碼的意思就是把一些特殊字元給編碼成另外一種表示方法: 22 | 23 | ``` js 24 | encodeURIComponent('1&a=2') // 1%26a%3D2 25 | ``` 26 | 27 | 它會把那些干擾到的部分用其他方式取代掉,在 Server 時則會自動轉成正確地表示方法,所以內容還是會是正確的,這邊不用擔心。 28 | 29 | 這就是新增留言時必須特別注意的地方。 30 | 31 | ## Request 的重新使用 32 | 33 | 有很多人在留言板那題新增完留言之後都會重新讀取一次,有些人可能以為節省資源,就會沿用同一個 request: 34 | 35 | ``` js 36 | request.open('/posts?content=123') 37 | request.send() 38 | 39 | request.open('/posts') 40 | request.send() 41 | ``` 42 | 43 | 這樣是不對的,而且會造成一些問題。最常見的就是第一個 request 會被取消掉,因為還沒發出去就要發第二個了。 44 | 45 | 正確的解法是每一個 request 就 new 一個新的 XMLHttpRequest,你直接包成 function 就好了: 46 | 47 | ``` js 48 | function addPost(content) { 49 | const xhr = new XMLHttpRequest() 50 | xhr.open() 51 | xhr.send() 52 | xhr.onload = function() { 53 | //... 54 | } 55 | } 56 | ``` 57 | 58 | 59 | 這樣就不會有問題了。 60 | 61 | ## Race condition 62 | 63 | (這一段要講的觀念超級無敵重要,不懂的話請在 slack 提問) 64 | 65 | 有些人可能聽過這名詞,但不知道是什麼,剛好這一次作業就能夠讓你體會到。誠如我上面所說的,很多人在留言板那題都會在新增留言之後立刻抓取新的留言: 66 | 67 | ``` js 68 | newRequest.open('/posts?content=123') 69 | newRequest.send() 70 | 71 | getRequest.open('/posts') 72 | getRequest.send() 73 | ``` 74 | 75 | 那結果會是什麼? 76 | 77 | > 我先新增留言,再抓取留言,這樣應該沒什麼問題吧? 78 | 79 | 不,問題可大了。 80 | 81 | 你 Request 先發歸先發,但「先發不代表會先到達」,這點超級重要。所以兩個 Request 如果第二個先到了,那你拿到的就還是舊的留言。 82 | 83 | 再來,儘管第一個先到,但你其實是「立刻」就發了第二個 Request,兩個相差的時間可能只有 1ms 而已,這根本不是什麼差距。而 Server 處理第一個新增留言的時間很有可能大於這個差距,因此你新增了留言沒錯,但你第二個 Request 拿到的東西依然是舊的。 84 | 85 | 簡單來說好了,「從你電腦發 Request 到 Server 的時間」跟「Server 的處理時間」以及「從 Server 發 Response 傳到你電腦的時間」這三者都是「無法估計」的,所以什麼事都有可能發生,有可能快有可能慢。 86 | 87 | 接下來我們直接假設幾種狀況就好。 88 | 89 | ### 狀況一 90 | 91 | 1. 先發第一個 request,並且第一個 request 先抵達 92 | 2. 第二個 request 抵達 server 的時候,第一個 request 已經處理完成並且傳回 response 93 | 3. 第二個 response 抵達 94 | 95 | 這是你原本心裡所想的情況,新增完留言之後你才抓取留言列表,而你拿到的就已經是最新的列表,有你剛剛留的留言。 96 | 97 | ### 狀況二 98 | 99 | 1. 先發第一個 request,並且第一個 request 先抵達 100 | 2. 第二個 request 抵達 server 的時候,第一個 request 還在處理 101 | 3. 回傳第二個 response,拿到當下的留言 102 | 4. 留言新增完成 103 | 104 | 在這種情形下,因為第二個 response 處理的時候,你其實是還沒有新增留言的,因此拿到的結果會是舊的,不會有你剛剛新增的留言。 105 | 106 | 除了以上兩種,還可以再假設超級多種,而且每一種情況都有可能發生。 107 | 108 | 或是再舉一個例子,我這樣寫: 109 | 110 | ``` js 111 | for(let i=1; i<=5; i++) { 112 | let xhr = new XMLHttpRequest() 113 | xhr.open('/posts/' + i) 114 | xhr.send() 115 | xhr.onload = function() { 116 | console.log('response: ' + i) 117 | } 118 | } 119 | ``` 120 | 121 | 其實就是拿第一篇到第五篇文章的內容,那請問我最後 log 出來的結果會是什麼? 122 | 123 | 12345 嗎? 124 | 125 | 不是,結果是我不知道。有可能是 12345,也有可能是 54321,甚至是 13542,每一種排列組合都有可能。原因就是我上面講過的:「從你電腦發 Request 到 Server 的時間」跟「Server 的處理時間」以及「從 Server 發 Response 傳到你電腦的時間」這三者都是「無法估計」的。 126 | 127 | 所以如果我拿出下面那段程式碼: 128 | 129 | ``` js 130 | newRequest.open('/posts?content=123') 131 | newRequest.send() 132 | 133 | getRequest.open('/posts') 134 | getRequest.send() 135 | ``` 136 | 137 | 問你說結果會是什麼,答案是:「不知道」。 138 | 139 | 這種情況就叫做 race condition,最後的產出完全憑當下他們競爭的結果,你在事前無法預料結果是什麼。你有可能先新增留言,也有可能先拿到結果,每一種都有可能,所以結果變得無法預期。 140 | 141 | 所以這種情況當然要避免。 142 | 143 | 那怎麼避免?最好的方法當然就是:「確保第一個 request 處理完成時,我才發送第二個 request」。 144 | 145 | 那我怎樣才知道第一個處理完了?當然就是從我拿到第一個的 response 的時候,我自然就知道第一個處理完了,不然我不可能拿得到 response 嘛。 146 | 147 | 所以呢,如果你要先新增文章,然後拿到最新的文章列表,你要這樣寫: 148 | 149 | ``` js 150 | newRequest.open('/posts?content=123') 151 | newRequest.send() 152 | newRequest.onload = function() { 153 | getPosts() // 這邊才去拿文章列表 154 | } 155 | 156 | function getPosts() { 157 | getRequest.open('/posts') 158 | getRequest.send() 159 | } 160 | 161 | ``` 162 | 163 | 這樣是唯一能保證順序的方法。 164 | 165 | 之前還有看過同學偷吃步: 166 | 167 | ``` js 168 | newRequest.open('/posts?content=123') 169 | newRequest.send() 170 | 171 | setTimeout(() => { 172 | getRequest.open('/posts') 173 | getRequest.send() 174 | }, 1500) // 等個 1.5 秒 175 | 176 | ``` 177 | 178 | 但這樣依然無法保證結果是正確的,因為我們可以假設第一個 request 處理完成時已經超過 1.5 秒了(而且這完全有可能發生),這樣你拿到的文章列表依然還是錯的。 179 | 180 | 牽扯的網路的東西都是非同步的,而非同步就代表著順序是無法被預知的。你只能靠著自己寫 code 來掌握正確的順序。 181 | 182 | ## 有關參考資料 183 | 184 | 有些我附的資料其實我本來就沒有預期你全部看懂,例如說這一週放的「RESTful API Design by TritonHo」,我只是想大家有個粗淺的理解而已,至少看過一些名詞有個印象,你不必完全理解也沒關係,只要大略看過去就好。 185 | 186 | ## 表單的 submit 187 | 188 | 留言板那題有個很有趣的現象,有很多人都在按下 submit 按鈕時送出新增留言的 request,然後整題的程式碼就這樣子,但按下送出之後卻一樣會顯示新的留言。 189 | 190 | 送出留言之後又沒有特地寫 code 去抓新的留言,這些新的是哪來的? 191 | 192 | 這是因為你表單送出了,所以就換頁了,換到同一頁就幾乎等於重新整理,所以留言自然就更新了,因為頁面重新載入一遍。 193 | 194 | 但這當然是錯誤的,錯誤的第一個點是如果要換頁,那我幹嘛要你用 ajax;錯誤的第二個點是可能會發生問題。 195 | 196 | 這問題就是上面提到的 race condition,如果我換頁的時候,新增留言的 request 還沒發出去,那不就不會新增留言了嗎?有人跟我說他試了很多次都沒試出來,對,這不一定試的出來,但你要知道理論上是有可能的,那就要避免掉這種情形。 197 | 198 | 所以這題應該要 ajax 送出留言成功之後,再用 ajax 去把最新的留言撈回來並顯示。 199 | 200 | 還有,表單要記得 e.preventDefault 阻止送出。 201 | 202 | ## 第一題獎項機率 203 | 204 | first 5% 205 | second 20% 206 | third 30% 207 | none 40% 208 | error 5% 209 | -------------------------------------------------------------------------------- /examples/week8/challenge.md: -------------------------------------------------------------------------------- 1 | # Week8 作業參考解答 2 | 3 | ## 第一題獎項機率 4 | 5 | first 5% 6 | second 20% 7 | third 30% 8 | none 40% 9 | error 5% 10 | 11 | ## 怎麼算出來的? 12 | 13 | 你可以寫一隻程式一直去發 request 然後做統計,越多次數量越準,以下是我寫的簡單版本: 14 | 15 | [連結](./week8-challenge.html) 16 | 17 | JS 主程式碼: 18 | 19 | ``` js 20 | const data = { 21 | total: 0, 22 | first: 0, 23 | second: 0, 24 | third: 0, 25 | none: 0, 26 | error: 0 27 | } 28 | 29 | const delay = 200 30 | const max = 200 31 | const errorMessage = 'error' 32 | 33 | const apiUrl = 'https://dvwhnbka7d.execute-api.us-east-1.amazonaws.com/default/lottery' 34 | 35 | // 抽獎 36 | function getPrize(cb) { 37 | let xhr = new XMLHttpRequest() 38 | xhr.open('GET', apiUrl, true) 39 | xhr.onload = function() { 40 | if (xhr.status >= 200 && xhr.status < 400) { 41 | let data 42 | try { 43 | data = JSON.parse(xhr.response) 44 | } catch(err) { 45 | cb(errorMessage) 46 | return 47 | } 48 | 49 | if (!data.prize) { 50 | cb(errorMessage) 51 | return 52 | } 53 | 54 | cb(null, data) 55 | } else { 56 | cb(errorMessage) 57 | } 58 | 59 | } 60 | xhr.onerror = function() { 61 | cb(errorMessage) 62 | } 63 | xhr.send() 64 | } 65 | 66 | function loop() { 67 | if (data.total >= max) return 68 | getPrize((err, response) => { 69 | data.total++ 70 | if (err) { 71 | data.error++ 72 | } else { 73 | data[response.prize.toLowerCase()]++ 74 | } 75 | update() 76 | setTimeout(loop, delay) 77 | }) 78 | } 79 | 80 | loop() 81 | 82 | function update() { 83 | const keys = ['first', 'second', 'third', 'none', 'error'] 84 | const total = data.total 85 | document.querySelector('.total').innerText = total 86 | for(let key of keys) { 87 | document 88 | .querySelector('.' + key) 89 | .innerText = (Number(data[key])*100) / total 90 | } 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /examples/week8/examples.md: -------------------------------------------------------------------------------- 1 | # Week8 作業參考解答 2 | 3 | 請參考 Lidemy 上的影片,有 live coding 示範,程式碼請參考 hw1 資料夾。 4 | 5 | 挑戰題機率部分請參考 [challenge.md](./challenge.md) 6 | 7 | Twitch API 的部分也有示範影片,只是這次改為提醒一些重點而不是 live coding,大家也可以自己看程式碼來觀摩,檔案在 hw2 的資料夾底下。 8 | -------------------------------------------------------------------------------- /examples/week8/hw1/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw1/bg.jpg -------------------------------------------------------------------------------- /examples/week8/hw1/first.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw1/first.jpg -------------------------------------------------------------------------------- /examples/week8/hw1/tv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw1/tv.jpg -------------------------------------------------------------------------------- /examples/week8/hw1/yt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw1/yt.jpg -------------------------------------------------------------------------------- /examples/week8/hw2/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw2/bg.jpg -------------------------------------------------------------------------------- /examples/week8/hw2/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw2/bg1.jpg -------------------------------------------------------------------------------- /examples/week8/hw2/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw2/logo.png -------------------------------------------------------------------------------- /examples/week8/hw2/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/examples/week8/hw2/preview.jpg -------------------------------------------------------------------------------- /examples/week8/week8-challenge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 計算機率 8 | 9 | 10 | 11 | 12 | 13 | total: 1
14 | first: 0
15 | second: 0
16 | third: 0
17 | none: 0
18 | error: 0
19 | 20 | 94 | 95 | -------------------------------------------------------------------------------- /examples/week9/README.md: -------------------------------------------------------------------------------- 1 | # Week9 作業自我檢討 2 | 3 | ## 關於驗證 4 | 5 | 以前我們在寫 JS 的時候有寫過表單驗證,這週 PHP 我們從後端來進行一些資料的驗證,這兩種到底差在哪邊呢? 6 | 7 | 關於驗證欄位的方式,仔細想想你會發現其實真的必備的只有一個,其他都是增進使用者體驗。 8 | 9 | 必備的是 PHP 後端那邊的驗證。 10 | 11 | 為什麼呢?假設只有前端驗證,是沒有用的,仔細想想第四週的內容,就算前端有驗證,我還是可以直接寫一個 node.js 程式發送資料給 PHP,依舊可以傳壞掉的資料。 12 | 13 | 因此,後端的驗證是必備的,才能真正防止不合法的資料寫進資料庫。 14 | 15 | 前端只是為了增進使用者體驗,不用去到後端發現錯誤才回來,而是在前端 submit 時就能夠找到錯誤並顯示出來。所以要多少驗證,端看你想要達成怎樣的使用者體驗。 16 | 17 | ### 注意,以下內容是參考解答,還沒寫完請不要看喔! 18 | 19 | . 20 | . 21 | . 22 | . 23 | . 24 | . 25 | . 26 | . 27 | . 28 | . 29 | . 30 | . 31 | 32 | ## 資料庫欄位型態 VARCHAR 跟 TEXT 的差別是什麼 33 | 34 | 網路上查可以查到很多相關資料,但我個人覺得最重要的一個點在於 VARCHAR 可以設長度但是 TEXT 不行。 35 | 36 | 意思就是說,當你本來就知道大概會需要多少字元的時候,就用 VARCHAR,真的逼不得已東西很長(例如說要存文章)的時候才用 TEXT,才能節省空間。 37 | 38 | ## Cookie 跟 Session 不要搞混了 39 | 40 | 這週其實還沒講到 Session 的概念,為的其實就是希望大家不要搞混。但因為之前 CS101 火球術其實已經提到了,所以反而有點弄巧成拙(?),還是滿多人搞混的。 41 | 42 | 請大家注意這一週作業問的問題: 43 | 44 | > Cookie 是什麼?在 HTTP 這一層要怎麼設定 Cookie,瀏覽器又會以什麼形式帶去 Server? 45 | 46 | 如果是我的話大概會這樣答: 47 | 48 | Cookie 是個儲存在瀏覽器的小型文字檔案,在 HTTP 這層 Server 可以透過 Set-Cookie 這個 response header 來讓瀏覽器儲存相對應的 Cookie。而瀏覽器發送 Request 時,會把相對應的 Cookie 放在 `Cookie` 這個 Header,Server 就可以拿到資料。 49 | 50 | 「相對應的」指的是每一個 Cookie 都有一些選項可以設置,要符合條件才能寫入以及傳送,例如說你無法寫入其他 domain 的 cookie。 51 | 52 | 所以我這邊在談的是 Cookie 的本質:儲存資料。而另一個重點就是伺服器可以把資料寫在 Cookie,瀏覽器也會幫你把 Cookie 帶給伺服器。 53 | 54 | 至於其他用途(廣告追蹤、身份驗證)那都是再延伸出去的東西了,其實不是這題的重點。可以當作額外補充,但你要知道的是 Cookie 本身就是個儲存容器,身份驗證是其中的用途之一,但還有其他用途。 55 | 56 | ## 來自 client 端的資料都不可信 57 | 58 | 大家千萬不要忘記一件事情了,千萬不要忘記什麼是我們可以自己改的,什麼不行。你要站在攻擊者的角度去想什麼東西可以被偽造。 59 | 60 | GET 跟 POST 的參數值可不可以被偽造?當然可以!我想傳什麼就傳什麼,本來就沒有限制。 61 | 62 | Cookie 的內容可不可以被偽造?當然可以,這也是我想帶什麼就帶什麼。 63 | 64 | 資料庫的值可不可以被偽造?可以,但如果被偽造的話就代表你資料庫已經被駭客拿下了,所以不用擔心這個問題。 65 | 66 | 所以為什麼這週曾經做過的會員系統不 ok?因為我們把會員 id 帶在 cookie,今天只要攻擊者自己改變 cookie 內容(可不可以?當然可以),就可以換一個身份登入,所以是有安全漏洞的。 67 | 68 | 這也是為什麼這一週要換成通行證機制,比起會員 id,我們隨機產生的通行證 id 是沒辦法被猜到的。那攻擊者可不可以竄改通行證 id?當然可以,他可以自己改變 cookie 的值,可是他要改成什麼? 69 | 70 | 通行證 id 是隨機產生的,他怎麼可能隨便猜就猜得到一組正確的? 71 | 72 | 所以在這情景下你不用去考慮什麼「如果他用別人的通行證,不就也可以利用別人的身份登入嗎?」,對啊,當然,但這個機制本來就只認證不認人,用別人的通行證本來就應該用別人的身份登入。 73 | 74 | 今天的重點是攻擊者沒辦法「拿到別人的通行證」或者是猜到,所以這個機制還是可靠的。 75 | 76 | 有些人作業把通行證的產生規則弄固定了,就是多此一舉。例如說通行證就是 `md5(username)`,一但這個規則被猜到了,我不就也可以產生出別人的通行證嗎?那這機制就沒用了。 77 | 78 | 所以我才強調要隨機產生,隨機就代表猜不到(精確一點的說法是很難猜啦,例如說機率是兩億分之一,就可以想成猜不到) 79 | 80 | 然後,這個通行證機制就叫做 Session。 81 | 82 | 你產生的通行證 ID 就是 session id,你在 users_certificate 這個 table 放的資料就是 session data。 83 | 84 | 那 $_SESSION 又是什麼?跟 session 有什麼關係? 85 | 86 | 在回答這個之前,先問你一個問題: 87 | 88 | > cookie 跟 $_COOKIE 有什麼關係? 89 | 90 | Cookie 是瀏覽器存資料的地方,它是一個 browser 跟 server 交換資料的機制。而 $_COOKIE 是 PHP 要用來操作 cookie 時的語法。 91 | 92 | session 也是一樣的。 93 | 94 | Session 就是通行證機制,可以在 server 端存放資料,在 client 端只透過 session id 來驗證身份;而 $_SESSION 是 PHP 用來操作 session 時的語法。 95 | 96 | session 只是個機制,就像是投票那樣。但它本身不會規定投票要怎麼投。你可以每個人發紙條寫要投誰;你可以線上電子投票;你可以在想投的箱子放一個球,這些都叫做投票。 97 | 98 | session 也是一樣的,這個機制也能有很多不同的實作方式,你可以像我們一樣自己用 users_certificate 這個 table 來實作,也可以用 PHP 內建的 $_SESSION 來實作,這些都叫做 session,只是實作方法不同。 99 | 100 | 更多細節可以參考 Session 與 Cookie 三部曲: 101 | 102 | 1. [白話 Session 與 Cookie:從經營雜貨店開始](https://github.com/aszx87410/blog/issues/45) 103 | 2. [淺談 Session 與 Cookie:一起來讀 RFC](https://github.com/aszx87410/blog/issues/45) 104 | 3. [深入 Session 與 Cookie:Express、PHP 與 Rails 的實作](https://github.com/aszx87410/blog/issues/46) 105 | 106 | ## 這週會碰到的問題 107 | 108 | 這週的作業主要會碰到的問題有兩個(當然還有更多啦,先提兩個): 109 | 110 | 1. 密碼存明碼,當駭客入侵把你資料庫偷走之後,你家會員的密碼就全部被駭客知道了,解法在之後會講。 111 | 2. 利用 cookie 裡的 user id 來判斷登入的人是誰,我只要把 cookie 裡面的值一改掉,就可以偽造別人身份登入,所以後來才需要改成 session 機制。 112 | -------------------------------------------------------------------------------- /homeworks/week1/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | 附註:.md 代表文章格式為 markdown,可自行上網搜尋相關教學,檔案內容請盡可能遵守[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)。 4 | 5 | ## hw1:交作業流程 6 | 7 | 請用文字一步步敘述應該如何交作業。 8 | 9 | 範例: 10 | 11 | 1. 新開一個 branch:`git branch hw1` 12 | 2. 切換到 branch:`git checkout hw1` 13 | 14 | 請將答案寫在 [hw1.md](hw1.md)。 15 | 16 | ## hw2:理解放鬆很重要 17 | 18 | 大腦會在兩種不同的工作模式中切換,所以在卡關時讓大家適當休息一下是很重要的,否則只會越卡越深,陷入泥淖之中。 19 | 20 | 因此,這個作業希望讓大家學會休息的重要性。請你找個時間出去散步(像是家裡附近的公園之類的,反正哪裡都可以),並且把休息一天的心得寫在每日進度上面。 21 | 22 | ## hw3:教你朋友 CLI 23 | 24 | 學了一項東西之後若是想驗證自己是不是真的懂,教別人是最快的方法。 25 | 26 | 有天,你的麻吉 h0w 哥跑來找你說:「欸!能不能教我 command line 到底是什麼,然後怎麼用啊?我想用 command line 建立一個叫做 wifi 的資料夾,並且在裡面建立一個叫 afu.js 的檔案。就交給你了,教學寫好記得傳給我,ㄅㄅ」 27 | 28 | 可...可惡,居然這樣子就跑走了。但因為他是你的麻吉,所以你也沒辦法拒絕。 29 | 30 | 因此這個作業要請你寫一篇簡短的文章,試圖教會 h0w 哥什麼是 command line 以及如何使用,並且要教他如何達成他想要的功能。 31 | 32 | 請將答案寫在 [hw3.md](hw3.md)。 33 | 34 | ## hw4:跟你朋友介紹 Git 35 | 36 | 因為你的人實在是太好,時不時就會有朋友跑來找你來幫忙。 37 | 38 | 這次來的是一個叫做菜哥的朋友,會叫做菜哥是因為家裡賣菜,跟你認識的其他人同名的話純屬巧合。 39 | 40 | 菜哥:「就是啊,我最近有一個煩惱。因為我的笑話太多了,所以我目前都用文字檔記錄在電腦裡,可是變得越來越多之後很難紀錄,而且我的笑話是會演進的。會有版本一、版本二甚至到版本十,這樣我就要建立好多個不同的檔案,弄得我頭很痛,聽說你們工程師都會用一種程式叫做 Git 來做版本控制,可以教我一下嗎?」 41 | 42 | 『好吧,我試試看』 43 | 44 | 菜哥:「謝啦,話說你來參加這個計畫學程式真的選對了欸,之後就不會有貧血的困擾了」 45 | 46 | 『為什麼』 47 | 48 | 「因為你會寫程式」 49 | 50 | 『...』 51 | 52 | 「喔...原來是血乘四的部分啊(拍手)」 53 | 54 | 就是這樣,在一陣尬聊之中你答應了菜哥的要求,要教他怎麼使用 Git 來管理他的笑話。 55 | 56 | 因此,你必須教他 Git 的基本概念以及基礎的使用,例如說 add 跟 commit,若是還有時間的話可以連 push 或是 pull 都講,菜哥能不能順利成為電視笑話冠軍,就靠你了! 57 | 58 | 請將答案寫在 [hw4.md](hw4.md)。 59 | 60 | ## hw5:簡答題 61 | 請將答案寫在 [hw5.md](hw5.md)。 62 | 63 | 1. 請解釋後端與前端的差異。 64 | 2. 假設我今天去 Google 首頁搜尋框打上:JavaScript 並且按下 Enter,請說出從這一刻開始到我看到搜尋結果為止發生在背後的事情。 65 | 3. 請列舉出 3 個「課程沒有提到」的 command line 指令並且說明功用。 66 | 67 | ## 挑戰題 68 | 69 | 有一種東西叫做 [shell script](http://linux.vbird.org/linux_basic/0340bashshell-scripts.php),可以用 command line 指令以及一些語法寫成一個腳本,執行之後可以很方便地做很多事。 70 | 71 | 舉例來說,下面這個檔案我們存檔並取名叫做 test.sh: 72 | 73 | ``` bash 74 | #!/bin/bash 75 | 76 | touch "$1.js"; 77 | echo "檔案建立完成"; 78 | ``` 79 | 80 | 接著為了讓他可以執行,我們要更改檔案權限:`chmod +x test.sh`。 81 | 82 | 最後執行它:`./test.sh abc`,傳入參數`abc`。 83 | 84 | 就會建立一個叫做 abc.js 的檔案,這就是一個很簡單的 shell script。 85 | 86 | 現在請你寫一個 shell script,可以傳入一個數字 n,然後會產生 1~n 個檔案,檔名是 `{number}.js`。 87 | 88 | 舉例來說:`./num.sh 10`會產生`1.js`、`2.js`...`10.js`。 89 | 90 |
91 | 提示 #1 92 | 去找找看怎麼在 shell script 裡面寫迴圈吧! 93 |
94 | 95 | ## 超級挑戰題 96 | 97 | 請寫一個`github.sh`,可以傳入一個參數 username,執行之後就會輸出這個 GitHub 使用者的暱稱、介紹、地點跟個人網站。 98 | 99 | 範例: 100 | 101 | ``` 102 | ./github.sh aszx87410 103 | 104 | 輸出: 105 | Huli 106 | Love coding, teaching, and writing. Believe sharing can make the world a better place. 107 | Taipei, Taiwan 108 | https://medium.com/@hulitw 109 | ``` 110 | 111 |
112 | 提示 #1 113 | 你知道嗎?用這個網址可以取得使用者的資料:https://api.github.com/users/aszx87410 114 |
115 | 116 |
117 | 提示 #2 118 | cut, grep, sed, awk 這些指令都是字串處理的好夥伴 119 |
120 | -------------------------------------------------------------------------------- /homeworks/week1/hw1.md: -------------------------------------------------------------------------------- 1 | ## 交作業流程 2 | 3 | -------------------------------------------------------------------------------- /homeworks/week1/hw3.md: -------------------------------------------------------------------------------- 1 | ## 教你朋友 CLI 2 | 3 | -------------------------------------------------------------------------------- /homeworks/week1/hw4.md: -------------------------------------------------------------------------------- 1 | ## 跟你朋友介紹 Git 2 | 3 | -------------------------------------------------------------------------------- /homeworks/week1/hw5.md: -------------------------------------------------------------------------------- 1 | ## 請解釋後端與前端的差異。 2 | 3 | 4 | ## 假設我今天去 Google 首頁搜尋框打上:JavaScript 並且按下 Enter,請說出從這一刻開始到我看到搜尋結果為止發生在背後的事情。 5 | 6 | 7 | 8 | ## 請列舉出 3 個「課程沒有提到」的 command line 指令並且說明功用 -------------------------------------------------------------------------------- /homeworks/week10/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:六到十週心得與解題心得 4 | 5 | 第二次的複習週終於來了,在這週裡面可以好好整理一下自己前面幾週學到的東西,因此這一週不會有什麼新的進度。 6 | 7 | 這次的作業希望大家整理一下前面幾週的心得,畢竟我們終於開始進入到網頁前後端的領域了,應該會有滿多心得可以寫。 8 | 9 | 而複習週的慣例就是會提供闖關遊戲讓大家玩,也可以一併把心得寫下來 10 | 11 | 請將答案寫在 [hw1.md](hw1.md)。 12 | -------------------------------------------------------------------------------- /homeworks/week10/hw1.md: -------------------------------------------------------------------------------- 1 | ## 六到十週心得與解題心得 2 | 3 | 4 | -------------------------------------------------------------------------------- /homeworks/week11/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:完成並加強留言板 4 | 5 | 第九週的作業只做到「利用 Session 實作登入機制」,而這一週要把後續的影片都看完(看到:「真正的實戰:留言板 - 再次修正問題篇」結束),並且把留言板的資安漏洞給修掉。 6 | 7 | 在修掉這些漏洞之前,你可以試試看入侵自己所寫的留言板,體驗一下當駭客的感覺。接著就是要把這些漏洞給修掉,包括: 8 | 9 | 1. 密碼沒有經過 hash 10 | 2. SQL Injection 11 | 3. XSS 12 | 13 | 把漏洞修補完以後,記得跟著 BE101 的課程(「真正的實戰:留言板 - 新增功能篇」與「真正的實戰:留言板 - 再次修正問題篇」),加上編輯、刪除留言的功能,也加上留言板的分頁,然後把權限的漏洞也修掉。 14 | 15 | 上面這些都在 BE101 裡面可以找到怎麼做以及範例程式碼,是屬於「跟著影片做」的部分,而 hw1 的另一個部分是「自己動手做」。 16 | 17 | 所以第二個任務就是,請幫這個留言板加上新功能:身份系統與管理後台。一共有三種不同的身份,每個人只會有一個身份,不會同時具有兩種以上的身份: 18 | 19 | 1. 管理員(可以新增留言,也可以編輯與刪除任意留言) 20 | 2. 一般使用者(可以新增留言,且編輯與刪除自己的留言) 21 | 3. 遭停權使用者(不能新增留言,但是可以編輯與刪除自己的留言) 22 | 23 | 你需要新增一個只有管理員進得去的後台頁面,在那邊可以看到留言板的所有使用者,而管理員可以調整使用者的身份。 24 | 25 |
26 | 提示 #1 27 | 可以在 users table 裡面新增一個叫做 role 的欄位 28 | 裡面存放使用者的身份,資料格式可以自訂,例如說你可以存成數字 29 | 1 代表一般使用者,0 代表被停權的使用者,2 代表 admin 之類的 30 | 也可以用字串或是 ENUM 來存 31 |
32 | 33 | ### 延伸挑戰題 34 | 35 | 原本只有固定三種身份,而且每個身份的權限都是固定的。 36 | 37 | 現在請你加強這個身份系統,需要支援的東西有: 38 | 39 | 1. 新增以及編輯身份,例如說你可以新增一個身份叫做:「editor」,可以瀏覽以及編輯所有文章,但不能刪除 40 | 2. 更彈性的權限設計,你可以任意組合「新增文章」、「刪除自己的文章」、「刪除任意文章」、「編輯自己的文章」、「編輯任意文章」這些權限 41 | 42 | 舉例來說,管理員要可以更改遭停權使用者這個身份的權限,可以變成只能編輯文章不能刪除等等。 43 | 44 | ## hw2:陽春部落格 45 | 46 | 這是一個要靠你自己獨立完成的作業,我們只會提供設計稿還有範例。這個部落格需要有以下功能: 47 | 48 | 1. 要有登入機制,讓管理員能夠登入到管理後台 49 | 2. 身為一個管理員,要能夠新增文章 50 | 3. 身為一個管理員,要能夠編輯文章 51 | 4. 身為一個管理員,要能夠刪除文章 52 | 5. 身為一個管理員,新增文章時要有標題以及內文(如果有時間的話,可以去串 CKEditor) 53 | 6. 身為一個訪客,在首頁要能看到最新的五篇文章 54 | 7. 身為一個訪客,可以從導覽列點入:文章列表,並看到所有文章 55 | 56 | 要怎麼建置資料庫,要怎麼設計程式碼,這都是你可以自己決定的,那就加油囉! 57 | 58 | 設計稿:https://zpl.io/aXBw5PE 59 | 60 | (這一週作業的主軸是程式而不是切版,不一定要跟設計稿長得一模一樣,也可以自由發揮,如果時間不夠,請先忽略細節,先把功能做出來) 61 | 62 | 如果時間不夠的話,這邊有已經切好版的 html,你只需要幫它套上 PHP 即可:[範例線上看](https://lidemy.github.io/mentor-program-4th/examples/week11/hw2/static/index.html), 63 | 檔案:[https://github.com/Lidemy/mentor-program-4th/tree/master/examples/week11/hw2/static](https://github.com/Lidemy/mentor-program-4th/tree/master/examples/week11/hw2/static) 64 | 65 | (附註:在這週的範例裡面沒有實作 6 跟 7,而是直接在首頁上顯示所有文章) 66 | 67 | ### 延伸挑戰題 68 | 69 | 在設計稿裡面的一些東西其實是加分項目,不在作業的基本需求裡面,這些加分項目為: 70 | 71 | 1. 串接 CKEditor 72 | 2. 實作分類功能 73 | 3. 實作 view more 功能 74 | 4. 實作分頁機制 75 | 5. 新增關於我頁面 76 | 6. 支援 RWD 77 | 78 | 你不需要把這些功能全部做完,就算只做完一項也可以繳交挑戰題作業。再次強調,如果你有時間的話再來挑戰,沒有的話完成基本項目即可。 79 | 80 | ## hw3:簡答題 81 | 82 | 1. 請說明雜湊跟加密的差別在哪裡,為什麼密碼要雜湊過後才存入資料庫 83 | 2. `include`、`require`、`include_once`、`require_once` 的差別 84 | 3. 請說明 SQL Injection 的攻擊原理以及防範方法 85 | 4. 請說明 XSS 的攻擊原理以及防範方法 86 | 5. 請說明 CSRF 的攻擊原理以及防範方法 87 | 88 | 請將答案寫在 [hw3.md](hw3.md)。 89 | -------------------------------------------------------------------------------- /homeworks/week11/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week11/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week11/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week11/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week11/hw3.md: -------------------------------------------------------------------------------- 1 | ## 請說明雜湊跟加密的差別在哪裡,為什麼密碼要雜湊過後才存入資料庫 2 | 3 | 4 | ## `include`、`require`、`include_once`、`require_once` 的差別 5 | 6 | 7 | ## 請說明 SQL Injection 的攻擊原理以及防範方法 8 | 9 | 10 | ## 請說明 XSS 的攻擊原理以及防範方法 11 | 12 | ## 請說明 CSRF 的攻擊原理以及防範方法 -------------------------------------------------------------------------------- /homeworks/week12/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:增強版 JavaScript 留言板 4 | 5 | 之前在 BE101 的最後,有做了一個留言板的 API,並且自己利用前端 JS 去串接,有顯示留言跟新增留言這兩個功能,如果你忘記的話可以回去複習一下(真正的實戰:留言板 - API 篇)。 6 | 7 | 而這一週的第一個作業會由我帶著你做,我們會做出: 8 | 9 | 1. 留言板的 API,能夠新增留言以及顯示留言 10 | 2. 寫一個前端頁面,串接自己寫的 API 11 | 12 | 帶你做的影片請參考 MTR04 這堂課程。 13 | 14 | ~~帶你做完以後,你要自己從頭實作一遍並且加上一個新功能:分頁。~~ 15 | 16 | ~~留言板每一頁最多顯示 5 個留言,網站最底下需要顯示分頁,要能夠顯示現在在哪一頁、總共有幾頁還有前往上一頁跟下一頁的功能。~~ 17 | 18 | 接著是這一週你要自己實作的新功能。 19 | 20 | 原本要讓大家實作分頁功能,所以你在影片中可能會看到上面舊的作業敘述。 21 | 22 | 但後來我想了一下,改做:「載入更多」的功能就好。 23 | 24 | 如果留言大於 5 筆的話,下面會出現「載入更多」的按鈕,按下去就會載入新的 5 筆留言。因為留言是從新排到舊,所以越下面的留言會越舊。當沒有更多留言可以載入時,就不會出現載入更多的按鈕。 25 | 26 | 介面的部分請盡量使用 Bootstrap,JS 的部分也請盡量用 jQuery,版面可以隨自己的喜好調整。 27 | 28 | ![](comments.png) 29 | 30 |
31 | 提示 #1 32 | 33 | 在實作以前可以搜尋關鍵字:「cursor based pagination」,或者是參考底下資料: 34 | 35 | 1. [API做翻页的两种思路](https://www.cnblogs.com/cgzl/p/10706881.html) 36 | 2. [How to do Pagination?](https://b96016.gitlab.io/post/how-to-pagination/) 37 | 3. [Pagination with Relative Cursors](https://engineering.shopify.com/blogs/engineering/pagination-relative-cursors) 38 | 39 |
40 | 41 | ## hw2:Todo List 42 | 43 | 之前在第七週的時候有實作過一個 todo list,那時只有支援新增、刪除已經標記完成,但比較完整的 todo list 應該會長這個樣子: 44 | 45 | ![](todo.png) 46 | 47 | 參考連結:http://todomvc.com/examples/vanillajs/ 48 | 49 | 需要支援的功能有: 50 | 51 | 1. 新增 todo 52 | 2. 編輯 todo 53 | 3. 刪除 todo 54 | 4. 標記完成/未完成 55 | 5. 清空 todo 56 | 6. 篩選 todo(全部、未完成、已完成) 57 | 58 | 以上的東西都是純前端的,跟後端完全沒有任何關係。 59 | 60 | 接著的這個功能才跟後端有關,那就是那就是會有一個「儲存」的按鈕,按下去以後會把目前 todo 的狀態送到 server 去儲存,並且回傳一個獨特的 id,以後使用者如果有帶這個 id,就自動把它的 todo 載入進來。 61 | 62 | 舉例來說,原本的網址可能是`https://example.com/todos.php`,按下儲存以後網址變成:`https://example.com/todos.php?id=5`,下次我用同樣網址進來時,就可以看到我之前儲存好的 todo item。 63 | 64 | 這邊一樣是前後端串接,你必須要用 ajax 來傳遞資料。所以你要思考的問題是: 65 | 66 | 1. 怎麼把 todo 的狀態變成字串傳到 server 67 | 2. 怎麼樣偵測網址上的 id,並且送出 request 到後端抓取 todos 68 | 3. 怎麼樣把 todos 顯示在前端 69 | 70 | 介面的部分請盡量使用 Bootstrap,JS 的部分也請盡量用 jQuery,版面隨自己喜好設置就可以了,不需要跟上面那個圖片長得一樣(請不要因為要把版面弄得好看或是要跟圖片一樣而花太多時間,這個作業的重點在於功能而不是版面)。 71 | 72 | (如果你沒什麼時間或是覺得這個有夠難做,也可以只做前端的 todo list 就好,不需要做後端的部分) 73 | 74 |
75 | 提示 #1 76 | 77 | 你可能會思考說要怎麼把 todos 的狀態存起來,其實你只要在前端用 JSON.stringify,把 todos 變成一個 JSON 字串送到後端存起來就好。 78 | 79 | 要恢復時就可以從後端拿資料,JSON.parse 之後你就有了 todos 的狀態。 80 | 81 |
82 | 83 | ## hw3:簡答題 84 | 85 | 1. 請簡單解釋什麼是 Single Page Application 86 | 2. SPA 的優缺點為何 87 | 3. 這週這種後端負責提供只輸出資料的 API,前端一律都用 Ajax 串接的寫法,跟之前透過 PHP 直接輸出內容的留言板有什麼不同? 88 | 89 | 請將答案寫在 [hw3.md](hw3.md)。 90 | -------------------------------------------------------------------------------- /homeworks/week12/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week12/comments.png -------------------------------------------------------------------------------- /homeworks/week12/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week12/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week12/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week12/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week12/hw3.md: -------------------------------------------------------------------------------- 1 | ## 請簡單解釋什麼是 Single Page Application 2 | 3 | 4 | ## SPA 的優缺點為何 5 | 6 | 7 | ## 這週這種後端負責提供只輸出資料的 API,前端一律都用 Ajax 串接的寫法,跟之前透過 PHP 直接輸出內容的留言板有什麼不同? 8 | -------------------------------------------------------------------------------- /homeworks/week12/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week12/todo.png -------------------------------------------------------------------------------- /homeworks/week13/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:改寫陽春部落格 4 | 5 | 請用這週學到的 CSS 預處理器重新改寫之前第十一週做的陽春部落格的 CSS 檔案。作業只需要把寫好的 SCSS 檔案複製過來就好,不需要放其他 PHP 的檔案。 6 | 7 | ## hw2:留言版 plugin 8 | 9 | 這是一個「帶你動手做」的作業,在 MTR04 裡面會有一個教學,一步步帶你改寫上一週寫的 SPA 留言板,把它改成一個 plugin 的形式,並且運用到 Webpack 以及其它這週所學到的工具。 10 | 11 | 這個作業跟第九週留言板比較像,一樣都會有教學帶著你做,但是這個作業的難度比較高,所以這個作業你並不需要真的 100% 理解。大家只要稍微知道 webpack 有哪些基本設定以及目的即可。 12 | 13 | ## hw3:改寫第八週 Twitch 作業 14 | 15 | 第八週有一個作業是串接 Twitch API,當時我們是用 XMLHttpRequest 這個 WebAPI 來做的。但是在新的標準中,有一個東西叫做 `fetch`,能夠用不同的語法發出 request 並且串接 API。 16 | 17 | 而這個作業呢,就是要把第八週的那個 Twitch API 的作業從 XMLHttpRequest 改成用 fetch 來串接 API。 18 | 19 | ## hw4:簡答題 20 | 21 | 1. Webpack 是做什麼用的?可以不用它嗎? 22 | 2. gulp 跟 webpack 有什麼不一樣? 23 | 3. CSS Selector 權重的計算方式為何? 24 | 25 | 請將答案寫在 [hw4.md](hw4.md)。 26 | -------------------------------------------------------------------------------- /homeworks/week13/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week13/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week13/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week13/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week13/hw3/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week13/hw3/index.html -------------------------------------------------------------------------------- /homeworks/week13/hw4.md: -------------------------------------------------------------------------------- 1 | ## Webpack 是做什麼用的?可以不用它嗎? 2 | 3 | 4 | ## gulp 跟 webpack 有什麼不一樣? 5 | 6 | 7 | ## CSS Selector 權重的計算方式為何? 8 | 9 | -------------------------------------------------------------------------------- /homeworks/week14/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:短網址系統設計 4 | 5 | 請你畫出一張短網址服務的後端系統架構圖,越詳細越好,可以考慮到如何增進效能、scaling 以及備份資料。 6 | 7 | 沒靈感的話可參考:[短网址(short URL)系统的原理及其实现](https://hufangyun.com/2017/short-url/),或是用「短網址 系統設計」之類的關鍵字去搜尋。 8 | 9 | 圖片可參考下圖(這是一張 Mobile 與 Web 前端如何跟後端溝通的圖,這示意圖只是大概講一下應該要怎麼畫,但你實際畫出來一定跟這個長得不一樣): 10 | ![](http://ithelp.ithome.com.tw/upload/images/20161211/20091346nyV3Lex42r.jpg) 11 | 12 | ## hw2:部署 13 | 14 | 請把你之前寫的 PHP 程式部署到自己的機器上面,並且對應到自己購買的網域,並且寫下部署的心得。 15 | 16 | 請將答案寫在 [hw2.md](hw2.md)。 17 | 18 | ## hw3:簡答題 19 | 20 | 1. 什麼是 DNS?Google 有提供的公開的 DNS,對 Google 的好處以及對一般大眾的好處是什麼? 21 | 2. 什麼是資料庫的 lock?為什麼我們需要 lock? 22 | 3. NoSQL 跟 SQL 的差別在哪裡? 23 | 4. 資料庫的 ACID 是什麼? 24 | 25 | 請將答案寫在 [hw3.md](hw3.md)。 26 | 27 | ## 挑戰題 28 | 29 | 原本在主機上你應該是使用 Apache 來當 Server,現在請你試試看改用 nginx 來當伺服器。 30 | 31 | ## 超級挑戰題 32 | 33 | 原本你部署的方式應該是把 PHP 檔案都上傳到主機上面,現在請你試試看用 Docker 把 PHP 留言板以及資料庫都包進去,並且使用 Docker 來部署。 34 | -------------------------------------------------------------------------------- /homeworks/week14/hw2.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week14/hw2.md -------------------------------------------------------------------------------- /homeworks/week14/hw3.md: -------------------------------------------------------------------------------- 1 | ## 什麼是 DNS?Google 有提供的公開的 DNS,對 Google 的好處以及對一般大眾的好處是什麼? 2 | 3 | 4 | ## 什麼是資料庫的 lock?為什麼我們需要 lock? 5 | 6 | 7 | ## NoSQL 跟 SQL 的差別在哪裡? 8 | 9 | 10 | ## 資料庫的 ACID 是什麼? -------------------------------------------------------------------------------- /homeworks/week15/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:十一到十五週心得 4 | 5 | 第三次的複習週東西變得比以往還多,需要一些時間好好消化這些資訊。若是有什麼心得的話,都歡迎寫下來。覺得複習已經夠累了,懶得寫心得的話也沒關係。 6 | 7 | 請將答案寫在 [hw1.md](hw1.md)。 8 | -------------------------------------------------------------------------------- /homeworks/week15/hw1.md: -------------------------------------------------------------------------------- 1 | ## 十一到十五週心得 2 | 3 | 4 | -------------------------------------------------------------------------------- /homeworks/week16/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:Event Loop 4 | 5 | 在 JavaScript 裡面,一個很重要的概念就是 Event Loop,是 JavaScript 底層在執行程式碼時的運作方式。請你說明以下程式碼會輸出什麼,以及盡可能詳細地解釋原因。 6 | 7 | ``` js 8 | console.log(1) 9 | setTimeout(() => { 10 | console.log(2) 11 | }, 0) 12 | console.log(3) 13 | setTimeout(() => { 14 | console.log(4) 15 | }, 0) 16 | console.log(5) 17 | ``` 18 | 19 | 請將答案寫在 [hw1.md](hw1.md)。 20 | 21 | ## hw2:Event Loop + Scope 22 | 23 | 請說明以下程式碼會輸出什麼,以及盡可能詳細地解釋原因。 24 | 25 | ``` js 26 | for(var i=0; i<5; i++) { 27 | console.log('i: ' + i) 28 | setTimeout(() => { 29 | console.log(i) 30 | }, i * 1000) 31 | } 32 | ``` 33 | 34 | 請將答案寫在 [hw2.md](hw2.md)。 35 | 36 | ## hw3:Hoisting 37 | 38 | 請說明以下程式碼會輸出什麼,以及盡可能詳細地解釋原因。 39 | 40 | ``` js 41 | var a = 1 42 | function fn(){ 43 | console.log(a) 44 | var a = 5 45 | console.log(a) 46 | a++ 47 | var a 48 | fn2() 49 | console.log(a) 50 | function fn2(){ 51 | console.log(a) 52 | a = 20 53 | b = 100 54 | } 55 | } 56 | fn() 57 | console.log(a) 58 | a = 10 59 | console.log(a) 60 | console.log(b) 61 | ``` 62 | 63 | 請將答案寫在 [hw3.md](hw3.md)。 64 | 65 | ## hw4:What is this? 66 | 67 | 請說明以下程式碼會輸出什麼,以及盡可能詳細地解釋原因。 68 | 69 | ``` js 70 | const obj = { 71 | value: 1, 72 | hello: function() { 73 | console.log(this.value) 74 | }, 75 | inner: { 76 | value: 2, 77 | hello: function() { 78 | console.log(this.value) 79 | } 80 | } 81 | } 82 | 83 | const obj2 = obj.inner 84 | const hello = obj.inner.hello 85 | obj.inner.hello() // ?? 86 | obj2.hello() // ?? 87 | hello() // ?? 88 | ``` 89 | 90 | 請將答案寫在 [hw4.md](hw4.md)。 91 | 92 | ## hw5:簡答題 93 | 94 | 1. 這週學了一大堆以前搞不懂的東西,你有變得更懂了嗎?請寫下你的心得。 95 | 96 | 請將答案寫在 [hw5.md](hw5.md)。 97 | 98 | ## 練習題 99 | 100 | 事實上 this, hoisting 這些東西在實際開發上會用到的機會比較少,最常用到的是 class 與 closure 的觀念,這邊提供三個題目給大家練習:[Week16 練習題](https://github.com/Lidemy/mentor-program-4th/issues/16),裡面有附上解答 101 | 102 | 如果覺得題目不清楚,可以直接看測試檔會比較快 103 | 104 | ## 挑戰題 105 | 106 | 看完 [Dmitry Soshnikov 這個部落格](http://dmitrysoshnikov.com/)的兩個系列:ECMA-262-3 in detail 與 ECMA-262-5 in detail。 107 | 108 | ## 進階挑戰題 109 | 110 | 大略讀過 [ES3](https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf) 文件(共 188 頁),至少知道裡面大概有哪些東西以及專有名詞。 111 | -------------------------------------------------------------------------------- /homeworks/week16/hw1.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week16/hw1.md -------------------------------------------------------------------------------- /homeworks/week16/hw2.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week16/hw2.md -------------------------------------------------------------------------------- /homeworks/week16/hw3.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week16/hw3.md -------------------------------------------------------------------------------- /homeworks/week16/hw4.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week16/hw4.md -------------------------------------------------------------------------------- /homeworks/week16/hw5.md: -------------------------------------------------------------------------------- 1 | ## 這週學了一大堆以前搞不懂的東西,你有變得更懂了嗎?請寫下你的心得。 2 | 3 | -------------------------------------------------------------------------------- /homeworks/week17/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | 這一週的作業繳交時需要附上底下兩個專案的網址,你可以選擇: 4 | 5 | 1. 部署到免費空間 heroku 6 | 2. 部署到自己買的主機 7 | 8 | 相關部署教學請參考 BE201 的後半段 9 | 10 | ## hw1:部落格 11 | 12 | 還記得第十一週我們完成的部落格嗎?費盡千辛萬苦與 PHP 奮戰之後才完成的部落格。 13 | 14 | 現在請你把這個部落格改成 Express + Sequelize,驗收一下這週學習的成果,順便在過程中體會一下用 JS 寫後端跟用 PHP 的差異。 15 | 16 | 幫大家回憶一下,11 週的部落格必須要有: 17 | 18 | 1. 登入機制,管理員才可以登入 19 | 2. 可以新增、編輯、刪除文章 20 | 21 | ## 延伸挑戰題 22 | 23 | 這週的延伸挑戰題跟 11 週一樣: 24 | 25 | 1. 串接 CKEditor 26 | 2. 實作分類功能 27 | 3. 實作 view more 功能 28 | 4. 實作分頁機制 29 | 5. 新增關於我頁面 30 | 6. 支援 RWD 31 | 32 | ## hw2:抽獎囉 33 | 34 | 之前第八週的時候有串過一個抽獎的 API,現在你的目標就是要自己把這個 API 實作出來! 35 | 36 | 不過第八週的 API 只有回一個獎項名稱而已,這週我希望大家改得更動態,抽獎品項的名字、圖片還有說明,都是 API 傳回來的,前端只負責 call API 然後顯示就好。 37 | 38 | 另外,你必須有一個後台,可以在後台新增抽獎品項並且設定機率。 39 | 40 | 詳細需求如下: 41 | 42 | 1. 身為一個管理員,我希望有一個抽獎頁面讓我管理獎項 43 | 2. 身為一個管理員,我希望可以在後台新增抽獎的品項(名字、圖片網址以及說明)以及機率 44 | 3. 身為一個管理員,我希望可以在後台編輯抽獎的品項(名字、圖片網址以及說明)以及機率 45 | 4. 身為一個管理員,我希望可以在後台刪除抽獎的品項 46 | 5. 身為一個管理員,我希望在前台能夠抽出我在後台所設定的獎項 47 | 48 | 由於 API 的規則會跟第八週的 API 長不一樣,所以第八週的東西可能會有小幅調整。 49 | 50 | 總而言之呢,這週就是要做出一個抽獎的網站!如果你不想沿用第八週的作業的話,可以自由發揮。 51 | 52 | ## 延伸挑戰題 53 | 54 | 如果你有注意到的話,上面的作業說明我有特別講說獎項可以設定的是「圖片網址」,這是因為如果是「圖片」的話,會涉及到圖片上傳,就會比較複雜一點。 55 | 56 | 因此呢,這題的延伸挑戰題就是要支援圖片上傳,可以考慮串接現成的 API 會比較容易,例如說 imgur,另外也推薦搭配 multer 這個 middleware 來做上傳功能。 57 | 58 | ## hw3:簡答題 59 | 60 | 1. 什麼是 MVC? 61 | 2. 請寫下這週部署的心得 62 | 3. 寫 Node.js 的後端跟之前寫 PHP 差滿多的,有什麼心得嗎? 63 | 64 | 請將答案寫在 [hw3.md](hw3.md)。 65 | -------------------------------------------------------------------------------- /homeworks/week17/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week17/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week17/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week17/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week17/hw3.md: -------------------------------------------------------------------------------- 1 | ## 什麼是 MVC? 2 | 3 | 4 | ## 請寫下這週部署的心得 5 | 6 | 7 | ## 寫 Node.js 的後端跟之前寫 PHP 差滿多的,有什麼心得嗎? 8 | -------------------------------------------------------------------------------- /homeworks/week18/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | 這一週的作業繳交時需要附上底下餐廳專案的網址,你可以選擇: 4 | 5 | 1. 部署到免費空間 heroku 6 | 2. 部署到自己買的主機(推薦) 7 | 8 | ## hw1:移植餐廳網站 9 | 10 | 請把第六七八週寫的那個餐廳網站移植到 Express 上面(就像第十週 show time 那樣,把整個變成一個網站),因為這週的其他兩個作業都會需要先把餐廳網站移植過來。 11 | 12 | 另外,上一週寫的抽獎後台也請放進這個專案當中。 13 | 14 | 簡單來說呢,你要做的就是用 Express 打造一個餐廳網站還有餐廳後台,但應該會有大部分的資源都可以沿用之前的作業。 15 | 16 | ## hw2:餐廳網站 menu 頁面 17 | 18 | 設計稿:https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5ef4ac17650383802281e906 19 | 20 | (可以忽略購物車相關的部分,那是挑戰題) 21 | 22 | 這是之前的餐廳網站其中一個沒有做的頁面,也就是 menu 頁面。 23 | 24 | 有看到底下那些品項嗎?包含了: 25 | 26 | 1. 名稱 27 | 2. 價格 28 | 3. 圖片 29 | 30 | 而你的任務就是要在後台加上一個頁面,讓管理員能夠輕鬆管理這些品項,必須要能夠: 31 | 32 | 1. 新增品項 33 | 2. 刪除品項 34 | 3. 編輯品項 35 | 36 | 而前台的頁面也必須顯示出這些品項。 37 | 38 | 有關於上傳圖片的部分請參考 week17 的延伸挑戰題,如果上傳圖片不好做的話,也可以改成填入圖片網址就好。 39 | 40 | ## hw3:餐廳網站常見問題後台 41 | 42 | 還記得我們在第七週做的常見問題頁面嗎?那時候是直接把內容寫在 HTML 裡面寫死,這種行為我們稱做 [hard code](https://zh.wikipedia.org/zh-tw/%E5%AF%AB%E6%AD%BB),缺點就是沒有辦法動態修改。 43 | 44 | 那要怎樣才能動態修改呢?就是把這些問題的標題跟內容放在資料庫裡面,然後在程式中載入進來。這樣做的話,也能在後台新增一個頁面來管理這些常見問題的標題跟內容,就能夠動態修改了! 45 | 46 | 因此呢,這個作業的需求如下: 47 | 48 | 1. 身為一個管理員,我希望常見問題的內容可以儲存在資料庫,這樣我才能方便修改 49 | 2. 身為一個管理員,我希望管理後台可以管理常見問題,這樣我才能方便修改 50 | 3. 身為一個管理員,我希望在後台可以新增常見問題,會有標題跟內容以及順序 51 | 4. 身為一個管理員,我希望在後台可以編輯常見問題,包括標題跟內容以及順序 52 | 5. 身為一個管理員,我希望在後台可以刪除常見問題 53 | 6. 身為一個管理員,我希望前端頁面的資料是從後端拿的,這樣才能跟後台連動 54 | 55 | 簡單來說就是要做個後台可以管理常見問題列表,然後在前台的部分動態載入這些資料。 56 | 57 | ## hw4:簡答題 58 | 59 | 1. 什麼是反向代理(Reverse proxy)? 60 | 2. 什麼是 ORM? 61 | 3. 什麼是 N+1 problem? 62 | 63 | 請將答案寫在 [hw4.md](hw4.md)。 64 | 65 | ## 挑戰題 66 | 67 | 學習系統的後端就是用 Express + Sequlize 寫的,有興趣的話可以研究一下程式碼:[Lidemy/lidemy-learning-backend](https://github.com/Lidemy/lidemy-learning-backend) 68 | 69 | ## 挑戰題 70 | 71 | 其實原本的課綱規劃裡面是有個購物車以及訂單的功能,但是礙於時間因素以及其他考量,暫時把它拿掉了。 72 | 73 | 設計稿都在 zeplin 上面:https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5ef4ac0c2409f97a972078f2 74 | 75 | 如果你有餘裕的話,也可以試著自己把購物車以及訂單功能給實作出來。 76 | 77 | ## 超級挑戰題 78 | 79 | 請試著加上「email 通知」的功能,在訂單成立以後會寄一封信給使用者,信裡面提供訂單資訊以及一個連結,點了連結之後可以查看訂單資訊。 80 | 81 | 有關於寄信,你可以試著研究以下的服務: 82 | 83 | 1. https://sendgrid.com/ 84 | 2. https://www.mailgun.com/ 85 | 86 | ## 超級挑戰題 87 | 88 | 請試著串接金流服務,讓結帳頁面真的有結帳的功能(可以用測試帳號來串接,就不會真的收錢)。 89 | 90 | 關於金流服務,可以參考以下幾間: 91 | 92 | 1. https://www.newebpay.com/ 93 | 2. https://www.ecpay.com.tw/ 94 | 3. https://www.opay.tw/Landing 95 | -------------------------------------------------------------------------------- /homeworks/week18/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week18/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week18/hw3.md: -------------------------------------------------------------------------------- 1 | ## 什麼是反向代理(Reverse proxy)? 2 | 3 | 4 | ## 什麼是 ORM? 5 | 6 | 7 | ## 什麼是 N+1 problem? 8 | 9 | -------------------------------------------------------------------------------- /homeworks/week19/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:規劃期末專案 4 | 5 | 下一週就是 20 週複習週了,結束之後就是連續四周的前端 React,再來就要做期末專案了! 6 | 7 | 因此,我想要讓大家在開始忙碌之前先規劃一下自己的期末專案要做些什麼東西。在這一週裡面有學到了一些產品開發的流程,帶大家看了 Lidemy 學習系統開發時的一些記錄文件,也有稍微學到 user story 這個東西。 8 | 9 | 這些工具都是來幫助你規劃產品用的,但不代表沒有這些工具你就沒辦法。這個作業可以用任何你自己喜歡的方式來規劃,如果沒有靈感的話可以參考課綱中後半段 Final project 的段落,有給大家一些範例。 10 | 11 | 這個作業就是希望大家先規劃一下自己之後要做什麼期末專案,然後把點子寫下來,可以試試看採用 user story 的方式來開需求,或者是自己畫 wireframe!有任何問題都可以找我或是助教討論。 12 | 13 | 另外,期末專案不是強制的,所以如果沒有想要做,可以直接跳過這一週。再者,如果你的進度落後不少,建議先跳過這個作業,先去做之後的 React 課程,然後邊求職再邊回來做期末專案。 14 | 15 | 請將期末專案的規劃寫在 [hw1.md](hw1.md)。 16 | -------------------------------------------------------------------------------- /homeworks/week19/hw1.md: -------------------------------------------------------------------------------- 1 | ## 期末專案規劃 2 | 3 | -------------------------------------------------------------------------------- /homeworks/week2/hw1.js: -------------------------------------------------------------------------------- 1 | function printStars(n) { 2 | 3 | } 4 | 5 | printStars(5); 6 | -------------------------------------------------------------------------------- /homeworks/week2/hw2.js: -------------------------------------------------------------------------------- 1 | function capitalize(str) { 2 | 3 | } 4 | 5 | console.log(capitalize('hello')); 6 | -------------------------------------------------------------------------------- /homeworks/week2/hw3.js: -------------------------------------------------------------------------------- 1 | function reverse(str) { 2 | 3 | } 4 | 5 | reverse('hello'); 6 | -------------------------------------------------------------------------------- /homeworks/week2/hw4.js: -------------------------------------------------------------------------------- 1 | function printFactor(n) { 2 | 3 | } 4 | 5 | printFactor(10); 6 | -------------------------------------------------------------------------------- /homeworks/week2/hw5.js: -------------------------------------------------------------------------------- 1 | function join(arr, concatStr) { 2 | 3 | } 4 | 5 | function repeat(str, times) { 6 | 7 | } 8 | 9 | console.log(join(['a'], '!')); 10 | console.log(repeat('a', 5)); 11 | -------------------------------------------------------------------------------- /homeworks/week2/hw6.md: -------------------------------------------------------------------------------- 1 | ``` js 2 | function isValid(arr) { 3 | for(var i=0; i 25 | 提示 #1 - 不知道怎麼存棋盤的 state 看這邊 26 | 27 | 這邊用二維陣列來做比較好做,用一個 board[y][x] 來存每一個的棋子,左上角是 board[0][0],往下 y + 1,往右 x + 1,所以第二行一個棋子就是 board[1][0] 28 | 29 | 利用 `Array(19).fill(null)` 可以產生 `[null, null, ....]` 一共 19 個 null 的陣列,這就是每一個橫排的內容。所以整個棋盤可以用:`Array(19).fill(橫排內容)`來產生,就是 `Array(19).fill(Array(19).fill(null))` 30 | 31 | ``` js 32 | const [board, setBoard] = useState(Array(19).fill(Array(19).fill(null))) 33 | ``` 34 | 35 | 要改變棋盤的時候要記得不能直接去改變 state,所以有兩種方式,第一種最簡單,就是把整個陣列做 deep clone 然後改值就好 36 | 37 | ``` js 38 | function updateBoard(x, y, newValue) { 39 | const newBoard = JSON.parse(JSON.stringify(board)) 40 | newBoard[y][x] = newValue 41 | setBoard(newBoard) 42 | } 43 | ``` 44 | 45 | 第二種則是用我們在課程中教過的 map,不過因為有兩層所以會比較複雜一點: 46 | 47 | ``` js 48 | function updateBoard(x, y, newValue) { 49 | setBoard( 50 | board.map((row, currentY) => { 51 | // 如果這一個橫排不是我要改的,直接回傳即可 52 | if (currentY !== y) return row; 53 | 54 | // 如果是的話,找到我要改的那個 x 的位置 55 | return row.map((col, currentX) => { 56 | if (currentX !== x) return col 57 | return newValue 58 | }) 59 | }) 60 | ) 61 | } 62 | ``` 63 | 64 | 65 | 66 | ### 延伸挑戰題 67 | 68 | 1. 你的棋子是下在線的交叉點嗎?如果你是把棋子畫在格子內,試試看畫在線的交叉點吧! 69 | 2. 試著做一個可以返回任意步驟的功能,可以回到之前任何一個步驟 70 | 3. 試著做一個紀錄棋譜的功能,在下完棋之後可以把對局分享給朋友,朋友就能看到這一局棋的棋譜 71 | 72 | ### 延伸進階挑戰題 73 | 74 | 1. 試著做一個簡單的 AI,可以選擇跟電腦下棋 75 | 76 | ## hw3:報名表單改寫 77 | 78 | 之前第七週有做了一個報名表單,表單在 React 當中是個滿需要練習的東西,因此這個作業就是要請你把第七週的報名表單用 React 改寫,然後一樣需要有驗證的功能! 79 | 80 | 你可能會需要用到的參考資料(舊版 class component):https://reactjs.org/docs/forms.html 81 | 82 | ## hw4:簡答題 83 | 84 | 1. 為什麼我們需要 React?可以不用嗎? 85 | 2. React 的思考模式跟以前的思考模式有什麼不一樣? 86 | 3. state 跟 props 的差別在哪裡? 87 | -------------------------------------------------------------------------------- /homeworks/week21/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week21/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week21/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week21/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week21/hw3/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week21/hw3/index.html -------------------------------------------------------------------------------- /homeworks/week21/hw4.md: -------------------------------------------------------------------------------- 1 | ## 為什麼我們需要 React?可以不用嗎? 2 | 3 | ## React 的思考模式跟以前的思考模式有什麼不一樣? 4 | 5 | 6 | ## state 跟 props 的差別在哪裡? 7 | 8 | -------------------------------------------------------------------------------- /homeworks/week22/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:SPA 部落格 4 | 5 | 在課程中我們有做了一個簡單的 blog 系統,完成了部分功能,而這一週的作業就是由你把剩下的功能完成,讓整個 blog 的功能變得更完整。 6 | 7 | 請做出一個簡單的 Blog SPA,會有以下幾個頁面: 8 | 9 | 1. 登入頁面:輸入帳號密碼後可以登入 10 | 2. 註冊頁面:可以開放使用者註冊 11 | 3. About 頁面:隨意顯示一些關於這個部落格的話 12 | 4. 文章列表頁面:可以看到所有文章,一頁只會顯示 5 筆,需要支援分頁功能,可以換頁 13 | 5. 單篇文章頁面:點進去文章以後可以看到文章完整內容 14 | 6. 發表文章頁面:可以輸入標題跟內文發文 15 | 16 | Route 的部分請使用 React Router,資料請串接課程中提到的 API 17 | 18 | https://github.com/Lidemy/lidemy-student-json-api-server 19 | 20 | 會用到的是 Users 跟 Posts 這兩個的資料。 21 | 22 | 課程中做了一半的 React App:https://github.com/aszx87410/react-board-test 23 | 24 | ## hw2:簡答題 25 | 26 | 1. 請列出 React 內建的所有 hook,並大概講解功能是什麼 27 | 2. 請列出 class component 的所有 lifecycle 的 method,並大概解釋觸發的時機點 28 | 3. 請問 class component 與 function component 的差別是什麼? 29 | 4. uncontrolled 跟 controlled component 差在哪邊?要用的時候通常都是如何使用? 30 | 31 | -------------------------------------------------------------------------------- /homeworks/week22/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week22/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week22/hw2.md: -------------------------------------------------------------------------------- 1 | ## 請列出 React 內建的所有 hook,並大概講解功能是什麼 2 | 3 | ## 請列出 class component 的所有 lifecycle 的 method,並大概解釋觸發的時機點 4 | 5 | ## 請問 class component 與 function component 的差別是什麼? 6 | 7 | ## uncontrolled 跟 controlled component 差在哪邊?要用的時候通常都是如何使用? 8 | -------------------------------------------------------------------------------- /homeworks/week23/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:Redux 版 Todo List 4 | 5 | 沒錯,大家又愛又恨的 todo list 又出現了,而且是在這課程裡面最後一次出現了! 6 | 7 | 在 redux 的課程中我有示範了如何用 redux 來實作新增以及刪除 todo 這兩個功能,而這週的作業呢,就是要你把 week21 寫的 todo list 改寫成用 redux 來實作! 8 | 9 | 所以你的 filter 跟 todos 都會存在 redux store 裡面,如果做不出來或是沒有想法,可以參考[官方教學](https://react-redux.js.org/introduction/basic-tutorial)。 10 | 11 | 另外提醒大家一件事,用了 redux 不代表你需要把所有 state 都放到 redux store 裡面去,像是我在示範 add todo 功能時,input 的 value 就是放在 component state 而不是 redux store,通常只有需要被其他元件共用到的狀態會放到 store 裡面去。 12 | 13 | ## hw2:簡答題 14 | 15 | 1. 為什麼我們需要 Redux? 16 | 2. Redux 是什麼?可以簡介一下 Redux 的各個元件跟資料流嗎? 17 | 3. 該怎麼把 React 跟 Redux 串起來? 18 | -------------------------------------------------------------------------------- /homeworks/week23/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week23/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week23/hw2.md: -------------------------------------------------------------------------------- 1 | ## 為什麼我們需要 Redux? 2 | 3 | 4 | ## Redux 是什麼?可以簡介一下 Redux 的各個元件跟資料流嗎? 5 | 6 | 7 | ## 該怎麼把 React 跟 Redux 串起來? 8 | 9 | -------------------------------------------------------------------------------- /homeworks/week24/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:SPA 部落格加強版 4 | 5 | 之前的作業我們有做了一個簡單的部落格,而這週的作業會繼續加強它的功能。 6 | 7 | 我們需要增加的功能有: 8 | 9 | 1. 刪除文章 10 | 2. 編輯文章 11 | 12 | 除了增加這兩個功能以外,我們原本 user 的資料是存在 context,這週的作業要請你把 context 拔掉,改用 redux 來存這個資訊。所以資料的更新也必須透過 redux。 13 | 14 | 除此之外,在發 API 的部分我們原本是在 component 裡面直接用 fetch,現在請你改用 redux-thunk 來完成,所以 API 的 loading 狀態以及 response 都會存在 store 裡面。 15 | 16 | ## hw2:簡答題 17 | 18 | 1. Redux middleware 是什麼? 19 | 2. CSR 跟 SSR 差在哪邊?為什麼我們需要 SSR? 20 | 3. React 提供了哪些原生的方法讓你實作 SSR? 21 | 4. 承上,除了原生的方法,有哪些現成的框架或是工具提供了 SSR 的解決方案?至少寫出兩種 22 | -------------------------------------------------------------------------------- /homeworks/week24/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week24/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week24/hw2.md: -------------------------------------------------------------------------------- 1 | ## Redux middleware 是什麼? 2 | 3 | 4 | ## CSR 跟 SSR 差在哪邊?為什麼我們需要 SSR? 5 | 6 | 7 | ## React 提供了哪些原生的方法讓你實作 SSR? 8 | 9 | 10 | ## 承上,除了原生的方法,有哪些現成的框架或是工具提供了 SSR 的解決方案?至少寫出兩種 11 | 12 | 13 | -------------------------------------------------------------------------------- /homeworks/week3/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | 在交這週的作業以前,請務必確認每一題都已經通過 [Lidemy OJ](https://oj.lidemy.com/) 的測試並拿到 AC。 4 | 5 | ## hw1:好多星星 6 | 7 | [LIOJ1021 - 好多星星](https://oj.lidemy.com/problem/1021) 8 | 9 | ## hw2:水仙花數 10 | 11 | [LIOJ1025 - 水仙花數](https://oj.lidemy.com/problem/1025) 12 | 13 | ## hw3:判斷質數 14 | 15 | [LIOJ1020 - 判斷質數](https://oj.lidemy.com/problem/1020) 16 | 17 | ## hw4:判斷迴文 18 | 19 | [LIOJ1030 - 判斷迴文](https://oj.lidemy.com/problem/1030) 20 | 21 | ## hw5:聯誼順序比大小 22 | 23 | [LIOJ1004 - 聯誼順序比大小](https://oj.lidemy.com/problem/1004) 24 | 25 | ## hw6:簡答題 26 | 請將答案寫在 [hw6.md](hw6.md)。 27 | 28 | 1. 請寫下以上五題的解題心得 29 | 30 | ## 挑戰題 31 | 32 | [LIOJ1053 - 走迷宮](https://oj.lidemy.com/problem/1053) 33 | 34 |
35 | 提示 #1 36 | BFS,廣度優先搜尋法 37 |
38 | 39 | ## 超級挑戰題 40 | 41 | [LIOJ1052 - 貪婪的小偷 Part2](https://oj.lidemy.com/problem/1052) 42 | 43 |
44 | 提示 #1 45 | 這題可以暴力解,試著舉出每一種可能的組合 46 |
47 | 48 |
49 | 提示 #2 50 | 請 Google:「背包問題 DP」 51 |
52 | 53 | ## 超級超級挑戰題 54 | 55 | [Advent of Code](https://adventofcode.com/) 是一個每年都會舉辦的活動,固定從 12 月開始,邊解題邊倒數,迎接著聖誕節的到來,詳細的介紹可以參考:[重拾程式解題的樂趣 - Advent of Code](https://13h.tw/2019/12/04/adventofcode.html)。 56 | 57 | 從 12/1 到 12/25,一共有 25 天,每天會有兩道題目,第一題解完之後才能解第二題,所以第二題難度會更高一點。你不一定要從第一天開始解,但是到後面的題目會越來越難。 58 | 59 | 它的解題模式不是走 OJ 那種,而是它會直接把測試資料給你,你在自己電腦上解題就好,跑出答案之後再丟回上面,它會驗證你的答案對不對。每個人都會拿到不同的測資,所以不會有抄別人答案的問題。 60 | 61 | 這次的挑戰題想要大家挑戰的是 day20 的題目:[Day 20: Donut Maze](https://adventofcode.com/2019/day/20) 62 | 63 | 能把第一題解開其實就很棒了,但如果你能把第二題也解開,那我真心佩服!就祝大家順利了! 64 | 65 |
66 | 提示 #1(第一題的提示) 67 | 一樣是 BFS,廣度優先搜尋法 68 |
69 | 70 |
71 | 提示 #2(第二題的提示,跟第一題無關) 72 | 一樣是 BFS,但你可以多加一個維度試試看 73 |
74 | -------------------------------------------------------------------------------- /homeworks/week3/hw1.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week3/hw1.js -------------------------------------------------------------------------------- /homeworks/week3/hw2.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week3/hw2.js -------------------------------------------------------------------------------- /homeworks/week3/hw3.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week3/hw3.js -------------------------------------------------------------------------------- /homeworks/week3/hw4.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week3/hw4.js -------------------------------------------------------------------------------- /homeworks/week3/hw5.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week3/hw5.js -------------------------------------------------------------------------------- /homeworks/week3/hw6.md: -------------------------------------------------------------------------------- 1 | ## hw1:好多星星 2 | 3 | ## hw2:水仙花數 4 | 5 | ## hw3:判斷質數 6 | 7 | ## hw4:判斷迴文 8 | 9 | ## hw5:聯誼順序比大小 10 | -------------------------------------------------------------------------------- /homeworks/week4/hw1.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week4/hw1.js -------------------------------------------------------------------------------- /homeworks/week4/hw2.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week4/hw2.js -------------------------------------------------------------------------------- /homeworks/week4/hw3.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week4/hw3.js -------------------------------------------------------------------------------- /homeworks/week4/hw4.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week4/hw4.js -------------------------------------------------------------------------------- /homeworks/week4/hw5.md: -------------------------------------------------------------------------------- 1 | ## 請以自己的話解釋 API 是什麼 2 | 3 | 4 | 5 | ## 請找出三個課程沒教的 HTTP status code 並簡單介紹 6 | 7 | 8 | 9 | ## 假設你現在是個餐廳平台,需要提供 API 給別人串接並提供基本的 CRUD 功能,包括:回傳所有餐廳資料、回傳單一餐廳資料、刪除餐廳、新增餐廳、更改餐廳,你的 API 會長什麼樣子?請提供一份 API 文件。 10 | 11 | -------------------------------------------------------------------------------- /homeworks/week5/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:前四週心得與解題心得 4 | 5 | 複習週除了複習以往的內容以外,也可以好好整理一下自己這四週學到了些什麼,因此這次的作業就是寫一下自己對於前四週學習的心得感想。 6 | 7 | 除此之外,這一週的兩個小挑戰應該也會讓你們有一些心得,可以一併把它記錄下來。 8 | 9 | 請將答案寫在 [hw1.md](hw1.md)。 10 | -------------------------------------------------------------------------------- /homeworks/week5/hw1.md: -------------------------------------------------------------------------------- 1 | ## 前四週心得與解題心得 2 | 3 | 4 | -------------------------------------------------------------------------------- /homeworks/week6/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:餐廳頁面 4 | 5 | 圖片參考: 6 | 7 | ![](res1.png) 8 | 9 | ![](res2.png) 10 | 11 | ![](res3.png) 12 | 13 | 設計稿:https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5eab888623964b1b1214a9d3 14 | 15 | (目前權限僅供公開給第四期學生,如果第四期學生發現沒有權限請找 @huli) 16 | 17 | 這個作業屬於「跟著教學動手做」的作業,目的是強迫你跟著影片練習一次,觀察一下我的切法以及讓你對切版更熟練。 18 | 19 | 因此,請根據這一週切版教學裡面的步驟,切出這個餐廳頁面。 20 | 21 | 然後你會在設計稿裡面發現一個「網友評論」的區塊,並沒有在切版教學影片裡面出現,這就是要留給大家自己實作的地方,RWD 的部分可以自行發揮。 22 | 23 | ## hw2:活動報名表單 24 | 25 | 圖片參考: 26 | 27 | ![](form.png) 28 | 29 | 設計稿:https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5eb779c00efe004a516fe796 30 | 31 | (目前權限僅供公開給第四期學生,如果第四期學生發現沒有權限請找 @huli) 32 | 33 | 請根據範例,切出一個活動的報名表單,並且要支援 RWD,RWD 要長什麼樣子請自行發揮。 34 | 35 | 這個作業屬於「自己動手做」的作業,目的是讓你自己從無到有實作出一個東西。可以抱持著「先求有,再求好」的心態來進行。例如說先切桌面版,然後版面不符的地方就先放著,至少先把內容跟基本版面弄好,接著再來微調,然後再來調整手機版的版面。 36 | 37 | ## hw3:簡答題 38 | 39 | 請將答案寫在 [hw3.md](hw3.md)。 40 | 41 | 1. 請找出三個課程裡面沒提到的 HTML 標籤並一一說明作用。 42 | 2. 請問什麼是盒模型(box modal)? 43 | 3. 請問 display: inline, block 跟 inline-block 的差別是什麼?什麼時機點會用到? 44 | 4. 請問 position: static, relative, absolute 跟 fixed 的差別是什麼?分別各舉一個會用到的場合 45 | 46 | ## 挑戰題 47 | 48 | [Hacker News](https://news.ycombinator.com/) 是一個很知名的網站,上面有著各種與科技相關的資訊。現在請你看著這個網站,把它切出來。 49 | 50 | 或是如果你想把它變得更美,也可以試試看。 51 | 52 | ## 超級挑戰題 53 | 54 | 打開你的 Facebook,看著它。 55 | 56 | 打開你的文字編輯器,開始依樣畫葫蘆,切出一個 Facebook。 57 | 58 | 你只要切出你現在看得到的版面就好,不用考慮往下滑什麼的。但如果你切完還是心有餘力,那也可以試試看。 59 | -------------------------------------------------------------------------------- /homeworks/week6/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week6/form.png -------------------------------------------------------------------------------- /homeworks/week6/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week6/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week6/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week6/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week6/hw3.md: -------------------------------------------------------------------------------- 1 | ## 請找出三個課程裡面沒提到的 HTML 標籤並一一說明作用。 2 | 3 | 4 | ## 請問什麼是盒模型(box modal) 5 | 6 | 7 | ## 請問 display: inline, block 跟 inline-block 的差別是什麼? 8 | 9 | 10 | ## 請問 position: static, relative, absolute 跟 fixed 的差別是什麼? 11 | 12 | -------------------------------------------------------------------------------- /homeworks/week6/res1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week6/res1.png -------------------------------------------------------------------------------- /homeworks/week6/res2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week6/res2.png -------------------------------------------------------------------------------- /homeworks/week6/res3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week6/res3.png -------------------------------------------------------------------------------- /homeworks/week7/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:表單驗證 4 | 5 | 圖片範例: 6 | 7 | ![](form.png) 8 | 9 | (版面已經在上一週做好了,所以這一週不用重新切版) 10 | 11 | 還記得上一週裡面做的活動報名表單嗎?這一週要來幫報名表單加上驗證,在表單送出的時候會針對以下幾個欄位做檢查: 12 | 13 | 1. 暱稱是否為空 14 | 2. 電子郵件是否為空 15 | 3. 手機號碼是否為空 16 | 4. 報名類型是否有勾選 17 | 5. 怎麼知道這個活動的是否為空 18 | 19 | 如果沒有通過檢查,請在欄位的 input 下方顯示紅字提醒,有通過的話跳出一個 alert 展示使用者填寫的資料。 20 | 21 | ## hw2:餐廳 FAQ 頁面 22 | 23 | 圖片範例: 24 | 25 | ![](faq.png) 26 | 27 | 設計稿:https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5eabfdb4c6cf53190a8a37db 28 | 29 | 上一週的作業裡面,我們完成了餐廳的首頁,接下來要幫他加上一個常見問題的頁面,回答一些常見問題。 30 | 31 | 在這頁面上會出現許多常見問題,點開之後就能夠看到回答。現在要實作的就是這個頁面以及「點開後展開答案」的功能。上面的字你可以隨便亂寫隨便亂找,或是直接找個購物網站之類的來參考。 32 | 33 | 請注意,這一題為第六週的延伸作業,所以你大部分的版面跟 CSS 其實都可以重新利用,可以直接複製過來,這樣就只需要做 FAQ 的部分。 34 | 35 | ## hw3:Todo List 36 | 37 | 範例(來自第三期同學 shuanshuan030913 的[作業](https://lidemy.github.io/mentor-program-3rd-shuanshuan030913/homeworks/week13/hw2/)): 38 | 39 | ![](todo.gif) 40 | 41 | Todo List 是一個很經典的範例,之所以這麼經典就是因為可以完整的學習到如何實作出新增、刪除、編輯以及篩選等等的功能,但大家還只是初學者,所以我們並沒有要做出所有功能。 42 | 43 | 這一個作業要讓大家實作出基本的 todo list,功能包括: 44 | 45 | 1. 可以新增 todo 46 | 2. 可以刪除 todo 47 | 3. 可以標記 todo 為完成/未完成 48 | 49 | 介面可以參考範例也可以自己想,重點是功能要做出來。 50 | 51 | ## hw4:簡答題 52 | 53 | 1. 什麼是 DOM? 54 | 2. 事件傳遞機制的順序是什麼;什麼是冒泡,什麼又是捕獲? 55 | 3. 什麼是 event delegation,為什麼我們需要它? 56 | 4. `event.preventDefault()` 跟 `event.stopPropagation()` 差在哪裡,可以舉個範例嗎? 57 | 58 | 請將答案寫在 [hw4.md](hw4.md)。 59 | 60 | ## 挑戰題 61 | 62 | 參考 Bootstrap,實作出一個 Carousel 元件,可參考:https://getbootstrap.com/docs/4.3/components/carousel/#with-captions 63 | 64 | 備註:只能使用 HTML、CSS 與 JavaScript,禁止使用任何套件 65 | 66 | ![](carousel.gif) 67 | -------------------------------------------------------------------------------- /homeworks/week7/carousel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/carousel.gif -------------------------------------------------------------------------------- /homeworks/week7/faq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/faq.png -------------------------------------------------------------------------------- /homeworks/week7/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/form.png -------------------------------------------------------------------------------- /homeworks/week7/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week7/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week7/hw3/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/hw3/index.html -------------------------------------------------------------------------------- /homeworks/week7/hw4.md: -------------------------------------------------------------------------------- 1 | ## 什麼是 DOM? 2 | 3 | 4 | ## 事件傳遞機制的順序是什麼;什麼是冒泡,什麼又是捕獲? 5 | 6 | 7 | ## 什麼是 event delegation,為什麼我們需要它? 8 | 9 | 10 | ## event.preventDefault() 跟 event.stopPropagation() 差在哪裡,可以舉個範例嗎? 11 | -------------------------------------------------------------------------------- /homeworks/week7/todo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week7/todo.gif -------------------------------------------------------------------------------- /homeworks/week8/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:餐廳抽獎活動 4 | 5 | 圖片範例: 6 | 7 | ![](p2.png) 8 | 9 | ![](p1.png) 10 | 11 | 設計稿: 12 | 13 | 1. https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5eaba473e21be61d702230b2 14 | 2. https://app.zeplin.io/project/5eab7fd61be0341bdeed0db0/screen/5eabf815ce89761c108bbdfc 15 | 16 | 之前幫忙做的餐廳網站要推出新的活動了,老闆已經委託一間公司寫好了抽獎的 API,而你要幫忙的地方是實作前端介面並且跟 API 串接,這樣子就是一個完整的抽獎活動頁面了! 17 | 18 | 這是 API 的網址:https://dvwhnbka7d.execute-api.us-east-1.amazonaws.com/default/lottery 19 | 20 | 用 GET 即可,API 會回傳一個 JSON 格式的物件,內容為: 21 | 22 | ``` 23 | { 24 | prize: "獎項名稱" 25 | } 26 | ``` 27 | 28 | API 會按照機率回傳不同的獎項名稱,請你針對不同的獎項名稱做處理。 29 | 30 | 獎項名稱一共有四種:FIRST、SECOND、THIRD 以及 NONE。 31 | 32 | 1. FIRST,頭獎,在網頁上顯示字樣:「恭喜你中頭獎了!日本東京來回雙人遊!」,並且把背景改成[這張圖片](https://pixabay.com/photos/flight-plane-close-look-airplane-4315953/)。 33 | 2. SECOND,二獎,在網頁上顯示字樣:「二獎!90 吋電視一台!」,並且把背景換成[這張圖片](https://pixabay.com/photos/living-room-tv-table-a-drawer-home-1872192/)。 34 | 3. THIRD,三獎,在網頁上顯示字樣:「恭喜你抽中三獎:知名 YouTuber 簽名握手會入場券一張,bang!」,並且在網頁上放[這張圖片](https://pixabay.com/photos/youtube-iphone-smartphone-mobile-2617510/)。 35 | 4. NONE,銘謝惠顧,在網頁上顯示字樣:「銘謝惠顧」,並且把圖片的部分變成黑底,文字顏色變成白色。 36 | 37 | 有一點要特別注意,API 偶爾可能會不太穩定,會回傳錯誤。如果發生任何預期之外的情形(回傳的獎項不是以上四種,或是 Server 直接回傳錯誤),請跳出提示視窗(alert):「系統不穩定,請再試一次」。 38 | 39 | 這也是第六週的延伸,所以版面跟大多數部分都可以重新使用,直接複製貼上改一改就好。 40 | 41 | ### 進階挑戰題 42 | 43 | 請問這四種獎項的機率為何? 44 | 45 | ## hw2:再戰 Twitch API 46 | 47 | ![](lol.png) 48 | 49 | 還記得之前你幫果凍做的小程式嗎?可以看到 Twitch 上面熱門的遊戲,讓他從中挑選一個並且直播。這個成效很不錯,但是身為一個實況主,他又面臨了新的挑戰。 50 | 51 | 最近又太多太多實況主了,每個都有不同的特色,除了精進自己的實力以外,也要觀察一下競爭對手在做什麼。於是,他想拜託你寫一個網頁,可以顯示出某個特定遊戲的一些熱門實況,好讓他能夠方便觀察競爭對手。 52 | 53 | 請串接 [Twitch API](https://dev.twitch.tv/docs/v5),顯示出目前最熱門的 5 個遊戲,點下去之後可以顯示正在直播這遊戲的前 20 個實況(要剛好 20 個)。可以切換不同的遊戲,顯示不同遊戲的熱門實況。 54 | 55 | 如果你做完還有時間的話,可以加一些 transition 的效果,例如說: 56 | 57 | ![](menu.gif) 58 | 59 | 以及 60 | 61 | ![](card.gif) 62 | 63 |
64 | 提示 #1 65 | 66 | [Twitch API](https://dev.twitch.tv/docs/v5/) 裡面有一個 API 可以拿到 Live Streams 的資料,API 的描述是「Gets a list of live streams.」,看到這行就代表你找對 API 了。 67 |
68 | 69 |
70 | 提示 #2 71 | 72 | API 要帶的參數有一個 `game` 的欄位,請帶遊戲名稱,還有要記得帶 limit 這個參數 73 |
74 | 75 | 附註:Twitch API 有兩個版本,v5 是舊版,這個作業跟第四週一樣,用的會是**舊版**,請特別留意 76 | 77 | 78 | ## 進階挑戰題 79 | 80 | 這個頁面最底下加上一個按鈕:「載入更多」,點了之後會繼續載入之後的 20 個實況。按鈕不會消失,除非已經沒有更多的實況可以載入了。 81 | 82 | ## hw3:簡答題 83 | 84 | 1. 什麼是 Ajax? 85 | 2. 用 Ajax 與我們用表單送出資料的差別在哪? 86 | 3. JSONP 是什麼? 87 | 4. 要如何存取跨網域的 API? 88 | 5. 為什麼我們在第四週時沒碰到跨網域的問題,這週(觀看 JS102 影片的時候)卻碰到了? 89 | 90 | 請將答案寫在 [hw3.md](hw3.md)。 91 | -------------------------------------------------------------------------------- /homeworks/week8/card.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/card.gif -------------------------------------------------------------------------------- /homeworks/week8/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week8/hw2/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/hw2/index.html -------------------------------------------------------------------------------- /homeworks/week8/hw3.md: -------------------------------------------------------------------------------- 1 | ## 什麼是 Ajax? 2 | 3 | 4 | ## 用 Ajax 與我們用表單送出資料的差別在哪? 5 | 6 | 7 | ## JSONP 是什麼? 8 | 9 | 10 | ## 要如何存取跨網域的 API? 11 | 12 | 13 | ## 為什麼我們在第四週時沒碰到跨網域的問題,這週卻碰到了? 14 | 15 | -------------------------------------------------------------------------------- /homeworks/week8/lol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/lol.png -------------------------------------------------------------------------------- /homeworks/week8/menu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/menu.gif -------------------------------------------------------------------------------- /homeworks/week8/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/p1.png -------------------------------------------------------------------------------- /homeworks/week8/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week8/p2.png -------------------------------------------------------------------------------- /homeworks/week9/README.md: -------------------------------------------------------------------------------- 1 | # 作業 2 | 3 | ## hw1:留言板 4 | 5 | ![](./board.png) 6 | 7 | 這一週大家才剛接觸 PHP 而已,一定會需要一段時間才能上手,所以這週的作業會比較輕鬆,留多一點時間讓大家來練習。 8 | 9 | 在 [BE101] 用 PHP 與 MySQL 學習後端基礎這堂課裡面有一系列的教學,其中有一部分「真正的實戰:留言板」,會一步步教大家如何寫出一個留言板,並且逐漸改善它。 10 | 11 | 請跟著影片動手做並且看到「PHP 內建 session 機制」的單元,並且自己親手把留言板給做出來。第一次做的時候你可以邊跟著影片邊打程式碼,寫完之後自己再看一遍。如果做一次不夠,你可以做很多次。 12 | 13 | 這週的作業理想上的程度是:「刪掉全部程式碼重來,你有辦法從零開始自己把留言板寫出來(可以查資料但不能看我寫的範例)」,做到這樣你就通過這週作業的標準了。 14 | 15 | ## hw2:簡答題 16 | 17 | 1. 資料庫欄位型態 VARCHAR 跟 TEXT 的差別是什麼 18 | 2. Cookie 是什麼?在 HTTP 這一層要怎麼設定 Cookie,瀏覽器又是怎麼把 Cookie 帶去 Server 的? 19 | 3. 我們本週實作的留言板,你能夠想到什麼潛在的問題嗎? 20 | 21 | 請將答案寫在 [hw2.md](hw2.md)。 22 | -------------------------------------------------------------------------------- /homeworks/week9/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week9/board.png -------------------------------------------------------------------------------- /homeworks/week9/hw1/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lidemy/mentor-program-4th/4f72d5379052ebe6f51430fe17232000b96787dc/homeworks/week9/hw1/index.html -------------------------------------------------------------------------------- /homeworks/week9/hw2.md: -------------------------------------------------------------------------------- 1 | ## 資料庫欄位型態 VARCHAR 跟 TEXT 的差別是什麼 2 | 3 | 4 | 5 | ## Cookie 是什麼?在 HTTP 這一層要怎麼設定 Cookie,瀏覽器又是怎麼把 Cookie 帶去 Server 的? 6 | 7 | 8 | 9 | 10 | ## 我們本週實作的會員系統,你能夠想到什麼潛在的問題嗎? 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mentor-program-4th", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "lint": "eslint ./homeworks/**/*.js", 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "husky": { 10 | "hooks": { 11 | "pre-commit": "lint-staged" 12 | } 13 | }, 14 | "lint-staged": { 15 | "*.js": [ 16 | "eslint", 17 | "git add" 18 | ] 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/Lidemy/mentor-program-4th.git" 23 | }, 24 | "author": "", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/Lidemy/mentor-program-4th/issues" 28 | }, 29 | "homepage": "https://github.com/Lidemy/mentor-program-4th#readme", 30 | "dependencies": { 31 | 32 | }, 33 | "devDependencies": { 34 | "eslint": "^5.15.1", 35 | "eslint-config-airbnb": "^17.1.0", 36 | "eslint-plugin-import": "^2.16.0", 37 | "eslint-plugin-jsx-a11y": "^6.2.1", 38 | "eslint-plugin-react": "^7.12.4", 39 | "husky": "^1.3.1", 40 | "lint-staged": "^8.1.5" 41 | } 42 | } 43 | --------------------------------------------------------------------------------