# 統整筆記 [toc] ## JWT - jwt - 通常用來將 json 的驗證資訊編碼後存在 client 端 (ex. cookie),client reqeust 會將 jwt token 給 server 做驗證 - 組成 - header - 包含 signature 採用的非對稱式加密的演算法種類(ex. SHA) - 以 base64 編碼 - payload - 實際要傳輸的 json object - 以 base64 編碼 - base64 編碼是可逆的,不能存敏感資訊 - signature - 將 base64 編碼後的 header, payload, secret 以 header 的演算法做 hash - 優 - 可以確保 payload 沒有被更改 - 可以設 exp,過期期限 - 不用存 session 在 server - 省空間, 查詢時間 - 如果有多台 server 不須同步 session - 缺 - 無法像 session 一樣主動讓 token 無效 - sol : - exp 設短一點 - 限制使用次數 - docker swarm vs k8s - docker sawrm - 可以讓多台 host 組成 cluster 共同管理 container - 有基本的 ha, load balance 的功能 - service 可以用多個 task 部屬在不同台 host - 且其中的 host 有問題,manager node 將有問題的 host 的 container 移到別台部屬 - 預設用 round-robin loaf balance service 的流量到不同 task - 指令簡單 - 基本上和 docker 指令差不多,部屬也是用 `docker-compose.yml` - 會 docker 上手就很快 - docker swarm 原生少了一些進階的功能 - service 沒有 auto scaling 的功能 - 所以如果要有這個功能就要自己寫腳本去監控(ex. 如果某個服務目前的 cpu, memory 使用量超過一定百分比就擴展多少個…等等的方式) - k8s rbac 的機制較好管理存取 k8s api 的權限 - k8s 的 ingress 可以統一 host 對外的 port 號,並且再用 hostname or path name 去區分 service(service 不用一定要開在 host ip 的 port) - 雖然 docker swarm 也可以用 traefik 做到,但我覺得 ingress 設定比較方便 - https://blog.aotoki.me/posts/2022/07/08/rails-deployment-in-practice-deploy-to-docker-swarm/ - k8s 將功能模組化的較完整,降低模組的耦合,較好針對特定功能調整 - ex. 可以用 service 決定如何存取 deployment 下的 pod,service 又有很多不同的 type (clusterip, nodeport, load balancer) - ex. k8s rbac 由 ServiceAccount, ClusterRole, ClusterRoleBinding 組成 - 因為一個 ClusterRole 可以綁定(ClusterRoleBinding)給多個 Service account,也可以一個 Service account 綁定多個 ClusterRole,或是多對多。 - k8s 背後是 google 且有強大的社群維護 - Google, Azure, and AWS 都支援 k8s instance,不用另外再下載和安裝 ## `==` vs `===` vs `Object.is` - `==` vs `===` vs `Object.is` - `===` : 先比較 type,再比較 value - `==` : 先做型別轉換,再比較 value - `Object.is` : 和 `===` 差在下面兩個而已 - `Object.is(NaN,NaN)` 為 true - `Object.is(+0,-0)` 為 false - object type 的比較 : object value 是記憶體位址 ```js= a = new User("a"); b = new User("b"); a == b; // false a === b; // false ``` - 所以除了用 for loop 一個一個跑 check,還可以用 `JSON.stringfy` check 相等 ```js= a = new User("a"); b = new User("b"); JSON.stringfy(a) === JSON.stringfy(b); // true ``` - ex. ```js= var yiifaa = 'yiifaa' str1 = new String('yiifaa') str2 = String('yiifaa') yiifaa === str2; // true, Object.is 也是 str1 === str2; // false, Object.is 也是 str1 == st2; // true, 會先 str1.toString() ``` - setTimeout vs setInterval - setTimeout = 過幾秒執行一次(只會執行一次 - setInterval = 每過幾秒就執行一次(會一直執行 ## es6 - es6 - `array.map` - 將 array 中 element 都跑過一遍,不修改原本的 array,會回傳新的 array(pure function) - ![](https://hackmd.io/_uploads/r1m0ZlWe6.png) - ![](https://hackmd.io/_uploads/HyqlfgZg6.png) - note : array 的 deepcopy - `Array.from(a)` - ![](https://hackmd.io/_uploads/ry_QLHSGp.png) - `array.filter` - ![](https://hackmd.io/_uploads/HJtYQOiza.png) - `array.foreach` - ![](https://hackmd.io/_uploads/Sy3jQusGp.png) - arrow function - es7 - ![](https://hackmd.io/_uploads/BJljC-gzp.png) - https://ithelp.ithome.com.tw/articles/10249963 - es8 - async & await - `Object.keys` - `Object.values` - 把 Object 變成二維陣列:`Object.entries` ```js= for (let [key, value] of Object.entries(menu)) { console.log(`${key} : ${value}`) } ``` - es9 - `finally` - ![](https://hackmd.io/_uploads/HyXmJMgzT.png) - Event Bubbling - 由內到外觸發 event handler - Event capturing - 由外到內觸發 event handler - 127.0.0.1 vs localhost vs 0.0.0.0 - 127.0.0.1 : loopback address,指向本機 - localhost : domain name,通常在 `/etc/hosts` 下都會指向 127.0.0.1 - 0.0.0.0 : 在 server 的部屬上,代表指向 host 中所有的 interface。所以如果把服務開在 0.0.0.0:5000,代表可以用這台 host 上的所有 ip 存取。 - 在 route 中,是代表 default gateway - selinux - 基本上如果只有一個 user 有 root 權限關掉沒什麼關係 - Git rebase 可以達成什麼問題? - es6 const, let, var 之間差別是什麼? - 用過哪些後端框架? - 用過哪些前端框架? - CORS 是什麼? - Content secure policy 是什麼? - Content Security Policy (CSP) - 用來限制網頁對外部的 request 的 origin,只能送白名單的 origin - 防止被 XSS - 去執行依些惡意的腳本 - 要注意一些 cdn 不要被擋掉 - 可以在 apache, php, html 設定 - ex. html ```html= <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src ‘self’ http://js.devco.re;img-src 'self' data:;"> <title>test</title> </head> <body> <p>test</p> <img src="example.jpg"> </body> </html> ``` - User 和 Group 如何雙向查詢?設計資料庫的結構? - 若 user 是可以加入多個 group 就 3 張 table - 只能加一個就 2 張 - 設計教授實驗室和負責人的 RESTFUL API 怎麼設計 - Restful API - url 內容的部分的 root = api - ![](https://hackmd.io/_uploads/ByQlqHG-T.png) - get - 將傳送的資料轉為 query string 加再 url 後面 - 如果用 get 來更新資料會有點危險,因可能別人隨便傳給你連結就點,結果是亂帶資料的 - 或是不小心點到錯誤網址 - 好處是可以藉由 url 傳參數,使用者也方便知道參數 - post - 將資料放入 request 的 body - 用正確的 method 可以增加 SEO - get - 獲得所有實驗室資料 - `/api/lab` - 獲得單一實驗室資料 - `/api/lab/{lab_id}` - post - 建立新實驗室資料 - `/api/lab` - put - 更新單一實驗室資料 - `/api/lab/{lab_id}` - 更新單一實驗室部分資料 - 更新實驗室負責人資料 - `/api/lab/{lab_id}/manager` - delete - 刪除單一實驗室資料 - `/api/lab/{lab_id}` - ref - https://igouist.github.io/post/2021/05/newbie-2-webapi/ - 圖書館的系統,會把 email serialize 出來,但不知道為什麼會這麼慢? - 列出可能的原因,並探討可能的解決方法。 - mock spy stub 之間的差異是什麼 ? - function 和 arrow function 之間的差別? - SEO (search engine optimization) - 有其他連結連到此網站 - 把 HTML 弄單純一點 - CSS - Specificity 優先順序 : `!important` > element inline > id > class > element > `*` - 相同優先權,看後者(較下面的 code - ![](https://hackmd.io/_uploads/ByjhWoK0n.png) - 通常不會一開始就用 id 而是 class,因若之後要擴充不容易(id 權限比 class 大) - 針對 atrribute (只要 element 有這個 attribute) - ![](https://hackmd.io/_uploads/BJXvhsFC3.png) - 這邊若是 `<input/>` : 沒有設 `value` 的 attribute 就不會是紅色 - 其它自己取的 attribute 也可以 - 針對 attribute 的 value (attribut 值須相同) - ![](https://hackmd.io/_uploads/HJnd3jF0n.png) - selector - `A, B` : 有 A or B - `A B` : A 之後(裡面)的所有 B - ![](https://hackmd.io/_uploads/S1cCfUHGa.png) - ![](https://hackmd.io/_uploads/B1RFzIHGa.png) - ![](https://hackmd.io/_uploads/ryPJ78Sza.png) - `.A.B` : 同時有 class A and class B - `<div class = "A B">123</div>` - 這個 element 同時有兩個 class - class 可多個,但只能有一個 id - `A > B` : A 之後的下一層 B,可用來指定哪一層 - ![](https://hackmd.io/_uploads/SyC_GUrz6.png) - ![](https://hackmd.io/_uploads/B1RFzIHGa.png) - ![](https://hackmd.io/_uploads/Byy3fIHMp.png) - 但要注意的是裡面的所有 element color 也會被改到 - `A + B` : A 後面的一個 B (A B 同層) ```html= <div class = "a"> test </div> <div class = "b"> 被選 </div> <div class= "b"> 沒被選 </div> ``` - `A ~ B` : A 後面的全部 B (A B 同層) - ![](https://hackmd.io/_uploads/HkB1aBjG6.png) - ![](https://hackmd.io/_uploads/B1a46Hofp.png) - ![](https://hackmd.io/_uploads/HyM4TSoMa.png) - SCSS - 巢狀結構的 CSS - display:none vs visibility:hidden - none : 直接讓 element 位置消失 - hidden : 只會讓 element 內容消失 - display - `block` : 會佔一整區塊 ex. `div` - `inline` : 只佔自己元素的大小 ex. `span` - `flex` : 便於排版 - position 1. `static` : 預設的 2. `relative` : 可以設定元素相對位置,用 `top`, `bottom`,`left`, `right` 可以調整 element 位置 3. `absolute` : 自己從文件中抽離而不佔位置,而是會去佔有定位(relative, absolute, fixed)的上層 element 的位置,若上層沒有就一直往上找直到有就停,若都沒有最後就會定位在 window,也就是初始的視窗(不會定位在整份 html,所以滾動視窗不會像 `fixed` 一樣會跟著滾動) - https://www.youtube.com/watch?v=JOdZdHnuGmM 4. `fixed` : 隨著 scroll 移動位置 - `z-index` : element 顯示的前後順序 - css box model 1. `margin` - element <b>之間</b> 的距離,隔開相鄰的 element 多少 - ![](https://hackmd.io/_uploads/S1WgPIHMp.png) - ![](https://hackmd.io/_uploads/rkq7DISMp.png) 2. `padding` - element <b>內部</b> 內容(可能是其他 element)的距離,把 element 往裡面擠多少,同時會把外面的 element 撐大 ```html= <html> <style> #test2 { color : hsl(120, 100%, 25%); padding: 100px 50px 50px 500px; // 上右下左 } </style> <body> <div id = "test2"> <div id = "test3">test3</div> </div> </body> ``` - ![](https://hackmd.io/_uploads/r1bv3ydkT.png) 3. `border` - 邊線本身的設定 - bootstrap - grid system - 一行總共可以切 12 格,可以不同大小的裝置設定 - ![](https://hackmd.io/_uploads/SJ524DdyT.png) - 常用的套件 - sidebar - pagination - button - drop-down 下拉選單 - rowspan - ![](https://hackmd.io/_uploads/BJgN-q9fT.png) - :before 跟 :after 用途? - 把 content 放在元素前後面 - JS - handler 處理 event 發生後的事情. ex. `onclick` - bun : 一樣是 runtime, 但比 nodejs 快 - `__proto__` vs `prototype` - `__proto__` - 念作 dunder prototype - 每一個 object 都會有一個隱藏屬性 `[[Prototype]]`,可以透過 `__proto__` 存取 - 如果 object 的 attribute 中找不到對應的 attribute,就會去 `__proto__` 找直到找到或到達終點 `null` 為止,期間可能會經過多個 `__proto__`,途中經過的就是這個 object 的 protoype chain - ![](https://hackmd.io/_uploads/SJwp398k6.png) - ![](https://hackmd.io/_uploads/SyTR3cLkp.png) - 指向繼承的 `function.prototype` (原型鏈) - Object 就是任何東西原型鏈的頂點,再上去就是 `null` - 所以基本上就只有 object, null, undefined 3 種類型 - ex. - ![](https://hackmd.io/_uploads/B1uKMPxyp.png) - `prototype` - 只有 function 有,包含此 function prototype 的 attribute,代表用此 function new 的 instance 的 `__proto__` 會指向 `function.prototype` - `prototype` 的屬性(variable, function)都是共享的,可以減少資源浪費 ```js= function DOG(name){ this.name = name; } DOG.prototype = { species : '犬科' }; var dogA = new DOG('大毛'); var dogB = new DOG('二毛'); console.log(dogA.species); // 犬科 console.log(dogB.species); // 犬科 DOG.prototype.species = "貓科"; console.log(dogA.species); // 貓科 console.log(dogB.species); // 貓科 ``` - 但若有 object 把設自己的屬性且和共享屬性相同,就會覆蓋掉共享屬性(因為先找到自己的) - ![](https://hackmd.io/_uploads/SylOQwHza.png) - ![](https://hackmd.io/_uploads/SySDXDSGT.png) - function 建立時會被加一個 `prototype` 且 constructer 為自己 - ![](https://hackmd.io/_uploads/S1oIK9xyp.png) - 若用 `new` 去 create instance,該 instance 的 `__proto__` 就會繼承 fcuntion 的 `prototype` - ![](https://hackmd.io/_uploads/rJh9q9xJp.png) - ![](https://hackmd.io/_uploads/rk2Spt8Ja.png) - new operator 跟 Object.create 都做了繼承物件 - 先建一個空 object - `const obj = {};` - 再把 function 的 `this` 的 property(屬性) 給這個 object ```js= function User(name) { this.name = name; pig = "456"; this.hello1 = function() { console.log("hello1", pig); } } const a = new User("test"); console.log(a.name); // test console.log(a.pig); // undefined a.hello1(); // hello1 456 ``` - `this` 的值是動態改變的, 會隨著呼叫 function 的 object(function 需綁訂於 object) 的 property 而變 ```js= function User(name) { this.name = name; pig = "456"; this.hello1 = function() { console.log("hello1", pig); } } User.prototype.hello = function () { return this.name; }; function speak() { console.log('Hello world! My name is ' + this.name); } const user = new User('Peter'); user.speak = speak; user.speak(); // Hello world! My name is Peter ``` - https://www.shubo.io/javascript-this/#this-%E7%9A%84%E5%80%BC%E6%98%AF%E5%8B%95%E6%85%8B%E6%B1%BA%E5%AE%9A%E7%9A%84 - 直接呼叫 function 的 this 會指向 global - ![](https://hackmd.io/_uploads/H15YWWZla.png) - https://chupai.github.io/posts/2008/js_this/ - https://www.shubo.io/javascript-new/ - 再把 `obj.__proto__` = `function.prototype` - 差別在於會不會執行 constructor - ![](https://hackmd.io/_uploads/HJ84U5IkT.png) - no new - 不用 new 代表不會建立一個新的 object,而是單純 call function 而已 - 如果 function return `this` - textContent vs innerContent vs innerHTML vs innerText - textContent : 選取所有字元,包含空白 - innerContent : 選取所有字元,不包含空白 - innerHTML : 選取所有字元,包含 html 標籤 - XSS 漏洞 : 雖然 innerHTML 不會去執行內容的 `<script>` tag,但可用 button, img 等方式執行 script。但有時候要幫內容加 tag 還是要用 innerHTML - sol : 用 textContent 過濾再放入使用者內容,再用 innerHTML 加上 html tag - innerText : 選取所有字元,僅適用於 IE - 同步 vs 非同步 - 同步 : 等一行執行完才會到下一行 - ex. nodejs : `fs.readFileSync('./README.md')` - 非同步 : 可先去執行別行,等之前的執行完再執行 callback function - ex. `fs.readFile('./README.md',readFileFinished);` - callback - 一個 function 裡面在 call 別的 function,可確保執行順序,目前這個 function 跑完,才去 callback 下一個 function - 常見於封裝 XMLHttpRequest 的 ajax - ![](https://hackmd.io/_uploads/BJ9QBQHyT.png) - callback hell - 如果要連續呼叫多個有上下關係的 api,就會變成巢狀不易維護和閱讀 - ![](https://hackmd.io/_uploads/r1B48XB1a.png) - promise - 為了解決 callback hell 的問題而產生的 object - ![](https://hackmd.io/_uploads/S1eHqft16.png) - 3 種狀態 - pending : 等待中 - fullfilled : 成功,執行 `then` 裡面的 code - rejected : 失敗,執行 `catch` 裡面的 code - ex. ```js= myName() .then((res) => console.log('成功:'+ res)) .catch((error) => console.log('失敗:' + error)); ``` - 多種寫法 1. `Promise.all` : 一次等待所有 promise 完成才做 `then`,且多隻 api 是同時執行(不是一個執行完換下一個),適合打多隻 api 的,但是若有一隻 error 就不會顯示其他正確的 api 的訊息 - ![](https://hackmd.io/_uploads/BkezO7Bya.png) - `resolve` : 回傳成功的訊息 - `reject` : 回傳錯誤的訊息 - 如果上下 api 沒有關係,可以用此來加快速度 - ![](https://hackmd.io/_uploads/ByunEEryp.png) - 需一個一個等待,較慢 - ![](https://hackmd.io/_uploads/HyVA4ESya.png) - 一起做,較快 2. `Promise.allSettled` : 和 `all` 差別在,會回傳所有 api 的訊息(就算有 error),回傳時會多帶一個 `status` 代表成功 or 失敗 - ![](https://hackmd.io/_uploads/rJqS4NrJT.png) 3. `Promise.race` : 只做先完成的 - ![](https://hackmd.io/_uploads/BktddXB1a.png) - 教學 - https://israynotarray.com/javascript/20211128/2950137358/ - async/await - 解決 promise 還要寫 then, catch,還是有可能變巢狀結構,更易讀 - async - function 加上 async,代表會回傳一個 promise 的 object - await - await 會等其後的 promise object 結束(resolved or rejected),再繼續執行 - await 需在 async function 內用 - axios vs ajax vs fetch - ajax - 不支援 promise,要用 callback - fetch - 基於 promise,所以可以用 then、async+await,js 原生支援,不須引入 - request body 的 data 需先 json.stringify - axios - 基於 promise,所以可以用 then、async+await - 較 fetch 易用 - 可直接 `axios.post`,fetch 需寫在裡面 - ![](https://hackmd.io/_uploads/rJ3R14Hyp.png) - 不須 stringify,直接送 json - call stack - js runtime 是單執行緒的(只有 js engine 的 main thread),所以一次只會執行一行程式 - browser js engine 把要執行(有呼叫(call))的 function 和它的環境(ex. return address, local variable) push 到 stack 再做執行,當 function 結束會從 stack 中 pop 掉 - 不限於 js,其他語言也會 - stack 的容量是有限的,因此無止盡的 call function 會導致 stack overflow - ![](https://hackmd.io/_uploads/Hyq4nvI1p.png) - js engine 會把 js 內容包成一個 `main()`,再去 call 這支 main function - ![](https://hackmd.io/_uploads/r1HsmeIM6.png) - ![](https://hackmd.io/_uploads/ByE3mxUf6.png) - ![](https://hackmd.io/_uploads/BJUhveLzp.png) - 但除了 js engine main thread,可以利用 browser 提供的 WebAPIs(ex. DOM(ex. onclick)、setTimeouts、AJAX) 來達成非同步,也就是讓 webapis thread 去做。等它做完後會把 event 對應的 callback function 加到 task queue,而 event loop 會持續檢查<b>當 call stack 為空</b>且 task queue 有 task,就會把 task queue 最前面的 task push 到 callstack - ![](https://hackmd.io/_uploads/H1OQPu8JT.png) - main 代表檔案本身,檔案最初就會載入,所以 setTimeout 的 callback function 會等到 main 結束後(callstack 為空)才會被 push 到 stack 執行 - 所以這是為啥 `setTimeout(0, callback)` 會等到整個 js 執行完才會做,因為要等 stack 中的 main 執行完 event loop 才會把 task queue 中的 setTimeout push 到 call stack - browser 每 16.6 ms 會 render 一次頁面,但若是 callstack 有其他的 js 在跑 ex. for loop,就無法 render,此時頁面就會卡住不能動,因只有一 thread - ![](https://hackmd.io/_uploads/r1zdIKUya.png) - 但如果是用非同步的作法 ex. 每個 loop 都加上 setTimeout,就會被放進 callback queue,和 render queue 輪流執行 - ref - 教學 : https://www.youtube.com/watch?v=8aGhZQkoFbQ - 測試工具 : http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D - strong vs weak type - weak type lanaguage - 會自動轉換 type - ex. js ```js= let a = 1; let b = "1"; console.log(a+b); // 11 ``` - ex. c/c++ : int 會被轉換成 boolean - strong type language - 不會自動轉換 type - ex. java, c# - dynamic vs static type - static - 需先宣告 vairable type - dynamic - 不須,交給編譯器判斷 - ![](https://hackmd.io/_uploads/SyDKOP_yp.png) - javascript = dynamic + 超級 week type - js 的 `+` 有 2 個意思:數學、字串 - 當兩方其中有一方是字串,js 就會設定此 `+` 是字串相加 ```js= 111 + 222 + "333" + 111 + 222; // 333333111222 ``` - 但 php 會分開來 : `+` = 數學, `.` = 字串 ```php= <?php $v1 = 111; $v2 = 222; $x = $v1 + $v2; // 333 $x = $v1 . $v2; // "111222" ?> ``` - js 中,幾乎所有東西都可以相加 - ![](https://hackmd.io/_uploads/S1q_hw_1a.png) - 但 php 雖然是弱型別,但還是有限制 - ![](https://hackmd.io/_uploads/ByCq2vdJ6.png) - 解法 - `typescript` : 可讓 code 的型別更嚴謹(雖然不嚴謹還是可以被編譯成 .js),更易讀、維護 - function 加入回傳 type - function 限制傳入參數 type - 有 `private`, `public`, `protected` - scope - 作用域 1. function scope : variable 只在 function 裡有用 2. global scope - 在 html 是 window object - ![](https://hackmd.io/_uploads/ry0vE_uJp.png) `window.aaa; // 5` - 可以在 function 內宣告全域 : 不宣告(`var`, `let`, `const`)變數就給值 ```js= function test() { aaa = 5; } test(); console.log(aaa); ``` - 在 nodejs 是 global object 3. block scope - 只作用在大括號之中 - ![](https://hackmd.io/_uploads/BJyiIOu1a.png) - var vs let vs const - var 缺點 1. 同 scope 內可以重複宣告 - 且不會初始化原本的值 - ![](https://hackmd.io/_uploads/B1FN8uuJ6.png) - 可能會不小心改到之前有宣告過的同名的變數 2. 不支援 constant variable(不能被改的) 3. 不支援 block scope - ![](https://hackmd.io/_uploads/SJbGDdOk6.png) - 因為 var 是 function scope - ![](https://hackmd.io/_uploads/H1_2ncxzp.png) 4. 會產生 global variable - ![](https://hackmd.io/_uploads/rJBy3dOJT.png) - let 優點 1. 同個 scope 內不可重複宣告 2. 支援 block scope - ![](https://hackmd.io/_uploads/BJLbx95MT.png) - ![](https://hackmd.io/_uploads/Skovgq9za.png) 4. 不會產生 global variable - ![](https://hackmd.io/_uploads/SJMtjbSgT.png) - 原因 - ![](https://hackmd.io/_uploads/r1lo3_OJp.png) - 這樣的好處是進一步管控了 Global 變數,只能透過 Global Object 去存取,提升程式的嚴謹性和安全性。 - const 優點 - 除了 let 優點再加上 : 定義時必須初始化 (Initialization),且之後不能再更改 - ref - https://something-about-js-book.onejar99.com/day08 - hosting - js 會將 variable, function 的 declare 拉到 scope 最上面 - varaiable ```js= console.log(x); var x = 5; // 會變成 var x; // declare console.log(x); x = 5; // 這邊是 init ``` - function ```js= sayHi(); function sayHi(){ console.log('Hi'); } // 會變成 // 整個 function 都被拉到最上面 function sayHi(){ console.log('Hi'); } sayHi ``` - function in variable - ![](https://hackmd.io/_uploads/B19DJKdyp.png) - 因會變成 - ![](https://hackmd.io/_uploads/HkfFJY_Ja.png) - 只有 `var` 會有 undefined 的效果,`let` 和 `const` 雖然也有 hosting,但不會初始化為 undefiend,所以會顯示 `reference error : is not defined` - strict mode - 在程式最上面加上 `"use strict"` - 會讓一些錯誤的語法變成 error - 未宣告變數 - ![](https://hackmd.io/_uploads/Skgzftu1p.png) - function 相同參數 - ![](https://hackmd.io/_uploads/HJ_KGFu16.png) - function name 不能和保留名稱重複 - function 常用寫法 1. 一般 : 有 hosting ```js= a("test"); function a(name) { console.log(name); // test } ``` 2. anonymous function : 無 hosting ```js= a("test"); // error : a is not a function var a = function (name) { console.log(name); } ``` 3. arrow function - 語法更為簡潔 - ![](https://hackmd.io/_uploads/H1_BQZWga.png) - 主要和傳統 function 差在 `this` - 傳統 function 的 this 是指向呼叫他的 object - ![](https://hackmd.io/_uploads/rJlJl7-Zg6.png) - ref - https://ithelp.ithome.com.tw/articles/10207992 - arrow function 的 this 是指向外圍一層的 function(如果一直往外找沒有的話就會指向 window) - 適合用在 `setTimeout`, `setInterval` - object 寫法 - ![](https://hackmd.io/_uploads/B1vmfbWxa.png) - ![](https://hackmd.io/_uploads/SkAEz--gT.png) - function 寫法 - ![](https://hackmd.io/_uploads/By8C_JUfa.png) - ![](https://hackmd.io/_uploads/HJchOyUGT.png) - 不為此 scope 產生新的 `arguments` - 傳統可用 `arguments`,不用管帶入的參數 - ![](https://hackmd.io/_uploads/ryfmttuJa.png) - 改成這樣 - ![](https://hackmd.io/_uploads/BkfvYtdJT.png) - self-invoked function - 自己呼叫自己的 function - 用 `()` 包起來,再用 `()` 執行 ```js= (function () { console.log('Hello'); })(); ``` - 若此段 code 只須執行一次,可以這樣封裝起來避免影響到 global scope(只會影響 function scope) - ![](https://hackmd.io/_uploads/BkY26lIG6.png) - 所以很適合當作初始化資料的用途 - js is pass by value or reference ? - pass by value - 當變數是 primitive(原生型別時) - string - number - boolean - undefined - pass by reference - 當變數是 object - object - array ```js= let a = [1]; let b = a; b[0] = 2; a; // [2] ``` - 但是若是完全重新賦值,就不是用 reference,而是會重新建一個 object,所以不會互相影響 ```js= let a = [1]; let b = a; b = [2]; a; // [1] ``` ```js= let aa = [1] function test(aa) { aa = [2]; } test(aa); console.log(aa); // [1] ``` - closure - 若直接用 global,可能會在其他地方被改到,所以要限制只有在相同 function 可以被改(private 概念) - 且因為變數只要有持續被引用,就不會從記憶體砍掉(會存到引用它的 object 的 scope,讓變數繼續存活),所以只要在 function 中引用,就可以繼續用 - ![](https://hackmd.io/_uploads/ByEE4ftka.png) - 模擬 class 的 private variable 用法 ```js= function testClosure() { let count = 1; this.a = function (num) { count += num; } this.getCount = function () { return count; } } let t = new testClosure(); t.a(8); console.log(t.getCount()); // 9 console.log(t.count); // undefined let t1 = new testClosure(); t1.a(100); console.log(t1.getCount()); // 101 ``` - ![](https://hackmd.io/_uploads/rJHVj-Lfp.png) - 如果 `this.a`, `this.getCount` 沒有引用 `count`,那 `count` 初始化以後就會被砍掉了(因只作用在 function scope 且也沒有用 `this` 指派給 object) - cookie vs localstorage vs sessionstorage - cookie - 由 server 決定失效時間 - 容量小 - 每次 request 都會一起帶過來 server - localstorage - 永久存留在 browser,不因關掉頁面刪除(除非手動刪 - 容量大 - 不會隨 request 帶過來 - sessionstorage - 關掉頁面會刪除 - 容量大 - 不會隨 request 帶過來 - js 遵守 ECMAScript 的規範 - ex. ES6 - ref - https://ithelp.ithome.com.tw/articles/10209656 - `instanceof` - check constuctor 的 `prototype` 是否在 object 的 prototype chain 上 - ![](https://hackmd.io/_uploads/BkMWpxyxT.png) - DOM (document object model) - 定義:提供網頁元素的樹狀結構的表示方法和存取的 api - DOM tree : browser 接收到 Web Server response 的 html,會才從頭開始解析遇到的 html element 和其 attribute 成不同種類的 `Node` 的 object 而建立 DOM tree - ex. `HTMLDivElement`, `HTMLScriptElement` - CSSOM tree (CSS Object Model) : 等 DOM tree 建完,瀏覽器會從所有來源(包含外部引用、內嵌、行內以及瀏覽器的預設樣式等等)讀取 CSS 並建立 CSSOM tree,其 node 由依據 selector 將指定的樣式套用在指定的 element 上而建立,而像是 color 等樣式若沒有被定義就會繼承父 element 的 - Render tree : 結合部分 DOM, CSSOM tree 的 node(但像是 `<script>`, `display:none` 這種不會顯示在畫面上的就不會結合)。 - 當 render tree 建立完成,browser 依 render tree 中的 node 把 element 做 layout/reflow (排版),將 node 相對位置改為絕對位置。 - browser 有了 layout 的絕對位置後再計算 element 的 z-index 建立不同 layer(有前後順序),最後會再依 layout 和 layer 將排版結果利用 GPU paint(顯示)在畫面上 - visibility: hidden 會出現在 Render Tree,但 display: none 不會,因其不屬於版面中會出現的 object - ![](https://hackmd.io/_uploads/H1aKha1eT.png) - 只要當有觸發特定 event. ex, 滾動螢幕、調整視窗大小、call DOM API 都會再做 reflow - 所以要避免一直觸發否則會很耗效能 : Layout Thrashing - ex. 不要一直抓 element style - ![](https://hackmd.io/_uploads/B1s3-kxxp.png) - ![](https://hackmd.io/_uploads/BJryfkllT.png) - ref - https://cythilya.github.io/2018/07/19/styles-and-layout/ - root 為 document : 這份文件的開頭 - 接下來會從最開始的 `<html>` 開始 parse(解析),將剩下的 element parse 成 tree node。element 可以有自己的 attribute ```html= <html> <head> <title>example</title> </head> <body> <h1 class="txt">Hello World</h1> </body> </html> ``` - ![](https://hackmd.io/_uploads/ry79LiK02.png) - node 關係 - 上下層 - Parent Node ,下層為 Child Node - 同層 - Previous 以及 Next - ex. - `<tr>` 之間為同層 - `<tr>` `<td>` 之間為上下層 - DOM API - `document.getElementById('idName')` - 回傳相對應的第一個 element - `document.getElementsBytagName('tag')` - DOM 中所有 tag 相符,集合為 HTMLCollection - `document.getElementsByClassName('className')` - 找尋 DOM 中符合此 class 名稱的所有元素,集合為 HTMLCollection - `document.querySelector('selector')` - 利用 selector 來找尋 DOM 中的元素,並回傳相對應的第一個 element 。 - 不侷限 id, class. 都可以用,還可以用其他 atrribute 選擇,使用較彈性 - ![](https://hackmd.io/_uploads/rJnGhX8zp.png) - `document.querySelectorAll('selector')` - 利用 selector 來找尋 DOM 中的所有元素,集合為 NodeList 。 - HTMLCollection vs NodeList - HTMLCollection : 只回傳 element node - ![](https://hackmd.io/_uploads/HJFr72tAn.png) - Nodelist : 還會回傳 attribute, text, 註解等 node - ![](https://hackmd.io/_uploads/H1VO73tR3.png) - 阻塞 - `<script>` - 嵌入和引入式都會阻塞 main thread 而是會直接去下載並執行後才繼續 DOM 的 parse - 但是瀏覽器會分段 render,所以可能會先印出前面的 element - 所以通常會放在 `<body>` 之後 - `<link>` - 會阻塞 render - 否則頁面會從一開始沒有 css 樣式跳成有 css 樣式(ex. 從黑色字跳成黃色),所以需一次載入,不能邊讀邊 render - 所以通常會放在 `<body>` 之前 - `<img>` : 背景載入,不會阻塞 - `DOMContentLoaded` : DCL - 代表 parse 完 html 的 event - `window.onload` - 已將所有載入事件完成(element 都被 parse 完)後 - 避免 DOM tree 還沒建好就改可能會有點問題 - `src` - 引入資源 - `href` - 一般 `<a>` 會用 `href` 的方式做超連結 - 而會用 `<link href = "xxx.css" rel="stylesheet"/>` 將 css 引入 - `rel="stylesheet"` 可以讓 browser 知道這是 css - 我的理解是 `href` 屬性在 `<a>` 和 `<link>` 是不同東西 - 在 `<a>` 是做超連結 - 在 `<link>` 是做引入資源 - json - ![](https://hackmd.io/_uploads/B1-xQ9qza.png) - ![](https://hackmd.io/_uploads/r17bm59Mp.png) - throttle(節流) - 某個事件觸發後,接下來的 x 秒內不再處理該事件觸發 - ex. 限制 scroll event 不要太頻繁觸發 - debounce(防抖) - 從最後一次觸發開始,x 秒後才會處理事件 - ex. 搜尋輸入框的推薦關鍵字功能,節約查詢推薦關鍵字動作的觸發次數 - MQTT - 適合網路狀況差、需低耗電的設備 - 封包小:header 最小只有 2 byte - keep connection,不需重新建 - 支援多種語言的實作 - 可以有帳號密碼機制 - ![](https://hackmd.io/_uploads/rJm2gSCZa.png) - 但 mqtt 是預設明文傳輸,所以可以搭配 ssl 加密 - ref - https://officeguide.cc/mqtt-broker-server-mosquitto-installation-tutorial-examples/ - 有 3 種 qos 0. 可能會沒收到 1. 一定會收到, 但可能重複 2. 保證收到且不重複 - why use kafka 1. 因為 mqtt 沒有暫存機制,如果中間斷線資料會收不到 2. 在壓力測試中,consumer 出現漏接封包的狀況 - python - logging - 有 5 level,可以設只要看到哪個 level 以上,通常開發用 debug,實際在跑就用 warning, error - debug - info - warning - error - critical - 有一些方便的東西,ex. 印出錯誤行數、檔名 ## NodeJS - sso - ![](https://hackmd.io/_uploads/r1QSytoMT.png) - middleware - 在 request 到 response 中,借助許多 middleware 溝通,用 next 引發下一個 middleware - ex. express routing - ![](https://hackmd.io/_uploads/ryjTpMrkp.png) - request - 預設只能拿到 get 的 query string,所以要拿到 post 的 request.body,就要用 `body-parser` 來 parse request 的 body - `bodyParser.urlencoded()` 處理 UTF-8 編碼的資料,常見的表單(form)提交 例如:application/x-www-form-urlencoded - `bodyParser.json()` 處理 JSON 格式的資料 例如:application/json - `bodyParser.text()` 處理 type 為 text 的資料 例如:text/html, text/css - `bodyParser.raw()` 處理 type 為 application 的資料 例如:application/pdf, application/zip - ex. ```js= app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()) ``` - MVC - ![](https://hackmd.io/_uploads/SkXQKNFk6.png) - controller 就是 express router - controller 可以再細分成 routing 和 邏輯處理的 middleware - 所以 `index.js` 引入 `api` 中 routing 檔案只負責路由回傳由 middleware 處理完的資料 - `index.js` - 引入 routing 的檔案 - routing - ![](https://hackmd.io/_uploads/HkAL6MZe6.png) - middleware - ![](https://hackmd.io/_uploads/HJsQaGZx6.png) - cors (cross origin resource sharing) - express 預設不能跨域 - ![](https://hackmd.io/_uploads/H1rYl6YJa.png) - 和 csp 是不同的 - cors 是網站 A 不允許網站 B 存取網站 A 的資料 - csp 是不允許客戶載入不合規定的網站的資源 - pm2 - 可以用 multithread 分散流量 - ![](https://hackmd.io/_uploads/r1bFuX-ep.png) - ref - https://ithelp.ithome.com.tw/articles/10253083 ## nginx - better than apache - 大量連線時存取效能較好 - 佔較少 ram - 一般 proxy - 就是把服務開在 80 port - reverse proxy - 藉由不同 domain name 導向不同的服務 - 優點 - load balance - 統一在 nginx 設定、過濾 request(就不會影響到真正的 server) - 設定 policy 過濾(白名單、黑名單) - header, body - 含有 sql 字句 - ip, 網段 - 黑名單:`deny 123.123.123.0/28;` - 常用於外部服務 - 白名單:`allow 123.123.123.0/28;` - 常用於內部服務 - ex. 學校 vm - 防止 DDOS - 同個 ip 最大連線數 - `limit_conn conn_limit_per_ip 100;` - 同個 ip 每秒最多 request number - `limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=50r/s` - 最大上傳 - ssl - cache - conf 設定 - `proxy_hide_header X-Powered-By;` - 關掉 `X-Powered-By` : 會顯示 server 用甚麼框架 - `proxy_set_header` - nginx reverse proxy 就是轉發 request 封包,所以要對轉發的封包的 header 設定 - `proxy_set_header Host $host;` - 設定 header 的 host 是原本的 request 的 目的地 的 host - ![](https://hackmd.io/_uploads/BJ44vOqka.png) - 若沒加,轉送的封包目的地就會變成 localhost,而不是 klab.tw,然後因為 wordpress site 有設定 domain name,它就會要求 broweser redirect 到 klab.tw,成為 redirect loop - `X-Forwarded-Proto` - 用來代表此封包是 https or http,因為內部會轉成 http - `proxy_set_header X-Real-IP $remote_addr;` - 讓後端 server 可以取得 <b>上一個封包</b>的 source ip - ex. wordpress 的 wordfence 就可以去判斷 ip 黑名單等 - `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` - 可以取得<b>最初的</b>封包的 source ip + 上一個的 - 因為可能中間有經過其他的 proxy,所以此可以看最初的用戶端的 ip - ref - https://blog.csdn.net/qq_34556414/article/details/106634895 - https://klab.tw/2022/05/nginx-proxy-pass-wordpress/ - upstream - ![](https://hackmd.io/_uploads/Bycio_qyp.png) - round-robin : 輪巡 - weight round-robin : 有權重的輪巡 - ![](https://hackmd.io/_uploads/ryrb6OcyT.png) ## NoSQL - 相較 RDBMS - 特點 - 通常不會正規化,加快存取速度 - 優勢 - 當資料太大時會須擴充 DB,可水平擴充,降低擴充成本 - 不須固定 schema,更改欄位較方便 - 因每筆 data 是獨立的 document 儲存 - 缺點 - 不適合存重要的資料 - 只保證 eventually consistency (最終一致性),cluster 中不同 DB 在相同時間存取的資料可能會不同(因還在同步) - 成熟度不像 RDBMS 一樣 - 所以重要資料或少量資料較適合用 RDMS 存,因可確保交易的 ACID,也較好看出 entity 間的關係,更易維護 - MongoDB - 可以自由定義資料結構 (BSON) - 不用像傳統的 RDBMS (Relational Database Management System) 要先規劃 schema - BSON : 可以放 binary value 的 JSON - 結構 - ![](https://hackmd.io/_uploads/BkAlqPqyT.png) - shard - 水平擴充 - RDBMS 只能垂直擴充、replication(ex. master-slave) - 但 slave 會完全複製 master 資料,所以在效能上還是會比較慢,雖然可以分 request 到不同 replication - 可切割不同資料放在不同 mongodb server - Redis - 用 ram 儲存 ## 前端做 or 後端做 - hash password - 以 https 傳 + backend hash - 因若在前端 hash,我只要知道後端的 hash value 就可以直接送此 hash value 給 server 做比對(可改前端的 js - pagination - 後端做 - 減少 request 的 data 量 - 需頻繁送 request - 前端做 - 減少 request 數量 - 之後每次換頁比較快 - 一開始會載入比較久 - 通常不會把全部的頁面都看完,全部載入會有點浪費 - 都做 - 一開始將最常用的頁面(可能是前面幾頁 or 後面幾頁)載入 - 之後若有沒有的頁面去後端重新撈沒有的頁面的前後幾頁 - 驗證 - 都做 - 前端做可以增加 user 體驗、減少 server 負擔 - 後端做防止改前端 js 或是直接送 ## 備份 - 能還原才是好備份 - 要有異地備份 - 地方 type - on-site backup( 本地備份 ) - off-site backup (異地備份 ) - 備份 type - full backup - 完整備份,不管是否只修改一部分,每次就是全部重新備份 - incremental backup 增量備份 - 一開始會完全備份,之後每次只備份和 <b>第一次完全備份檔</b> 不同的地方 - 還原速度較快 - Differential backup 差異備份 - 一開始會完全備份,之後每次都只備份和上一次備份檔不同的地方 - 還原速度較慢 - GFS 原則 - ![](https://hackmd.io/_uploads/Byp9OXY1p.png) - 備份 db - mysqldump - 手動備份 - mysqldump 會 lock database 1. 可以調成不要 lock(用 readview 的概念) - `mysqldump --single-transaction --skip-lock-tables --databases my_db1 my_db2 > my_database.sql` - `--single-transaction` : 只會從開始 dump 的時候的資料開始拿 - 會幫剛開始的資料做 snapshot (readview 的概念) - https://medium.com/@martin87713/mysql-lock-55ca187e4af2 - `--skip-lock-tables` : 不要 lock table 2. 用 galera 去備份其中一個 db 3. 選在沒人用時備份 - automysqlbackup - 可設定每天固定備份 - rsync - 可做 incremental backup,只傳有差異的地方 ## transaction - 當有連續幾筆有相關的 sql,如果只有其中幾個有成功執行就有點尷尬了 - ex. 有繳費紀錄、欠債紀錄這兩個 table。有人來繳費並還清債務,但繳費紀錄 table 沒有成功紀錄資料,但欠債紀錄 table 有成功還債。 - 指令 - `beginTransaction` - 開始此次交易 - lock table - `commit` - 當此次交易所有 sql 都正確執行,提交此次交易執行的 sql 內容到 db - unlock table - `rollback` - 取消此次交易所有 sql 執行的內容 - unlock table - 特性 : ACID - Atomicity 原子性 - 不可切割,要馬都完成、都不完成 - Consistency 一致性 - 交易後,db 仍是正常 - Isolation 隔離性 - 交易之間要隔離,不能有 A 交易時 db 的內容是 B 交易到一半的內容 - 等級 1. Read Uncommitted - 同個交易中,query 結果會因其他交易的改動(其他交易不須 commit)而改變 3. Read Committed - 同個交易中,query 結果會因其他交易的 commit 而改變 4. Repeatable Read (RR) - mysql InnoDB 的 default - 同個交易中,相同的 query 結果會一樣(即使其他 transaction commit),因交易開始時會做 snapshot 5. Serializable - 交易 sql 開始執行時,會 lock 住 table,其他交易不能讀寫,直到此交易 commit, rollback 才 unlock - 效率極差,同時只能有一交易,不適合搶票系統等 - lock - share mode : 一個 transaction 鎖了 share mode 後,其他 transaction 在其 commit 前只能 select 不能改。可以解決非序列化的交易錯誤問題,但較有可能 deadlock。 - `lock in share mode` - ex. ```sql= start transaction; select * from test where id = 1 lock in share mode; ``` - 避免非序列化問題 - ![](https://hackmd.io/_uploads/B1kHatLza.jpg) - 實做 : https://medium.com/dean-lin/%E7%9C%9F%E6%AD%A3%E7%90%86%E8%A7%A3%E8%B3%87%E6%96%99%E5%BA%AB%E7%9A%84%E6%82%B2%E8%A7%80%E9%8E%96-vs-%E6%A8%82%E8%A7%80%E9%8E%96-2cabb858726d - exclusive mode : 一個 transaction 鎖了 exclusive mode 後,其他 transaction 在其 commit 前啥都不能做(堵塞),可避免同時執行有 query 結果和實際上不同的情形(因預設 RR 會讓相同 transaction 的 query 保持一樣)。 - `for update` - ex. ```sql= start transaction; select * from test where id = 1 for update; ``` - lock 可解決同時查詢的問題 - ![](https://hackmd.io/_uploads/S13kokkgT.png) - 若沒有 lock,且票數只有一張,兩個交易就會都認為當下是還有剩的,然後都可以買了 - deadlock 問題 - 若 `for update` 指向空的資料,會是用 gap lock 把查詢條件的範圍 lock 起來(防止寫入,還是可以讀) - 這樣其實就變成 share lock(可讀不能寫) - ![](https://hackmd.io/_uploads/ryi70yJxa.png) - 沒有 `show_id` = 5 的 data - 第三步交易 3 gap lock `show_id` 4 ~ 6 - 第四步交易 4 gap lock `show_id` 4 ~ 6 - 第五步交易 3 被 交易 4 阻塞 - 第六步交易 4 被 交易 3 阻塞,deadlock 被踢掉而 rollback - sol 1. Serializable : 但太慢 2. 不要讓 where 找到空的值 3. optimistic lock : 交易更新時判斷更新期間 value 是否有被別的交易更動,有就更新失敗 - 總結 - share lock 比較容易 deadlock - exclusive lock 會降低吞吐量 - 但他們都可以解決非序列化的問題 - Durability 永久性 - 一旦 commit,永久存在 db - ref - https://notes.andywu.tw/2021/select-for-update%E5%86%8Dinsert%E9%80%A0%E6%88%90deadlock%E7%9A%84%E9%99%B7%E9%98%B1/ - https://mgleon08.github.io/blog/2017/11/01/optimistic-locking-and-pessimistic-locking/ - ex. nodejs ```js= let conn = await pool.getConnection(); // Start Transaction await conn.beginTransaction(); try { // update record's paid to 1 const rId = req.body.reason.split(',')[1]; await conn.batch('update records set paid = 1 where `no` = ?', rId); // update turned = 1 var update_debt = await conn.batch('update debt set `turned` = 1 where `no` = ?;', req.body.no); // insert into financial var fId = await conn.batch('insert into financial(`aId`, `reason`, `money`) values(?, ?, ?);', [req.body.aId, req.body.reason, req.body.money]); // 新增到總帳務 // insert into financial_today await conn.batch('insert into financial_today(`aId`, `reason`, `money`, `fId`) values(?, ?, ?, ?);', [req.body.aId, req.body.reason, req.body.money, fId.insertId]); // 新增到今日帳務 // commit await conn.commit(); } catch(e) { console.log(e); // 還原 await conn.rollback(); } conn.end(); ``` ## 加快網頁載入速度 - 前端 - 盡量不要發太多次 request,因為建連線很花時間,可以一次拿多點資料 - 若要 call 多支無上下關係的 api,可以用 promise.all,而不是 await 一個完再 call 下個 - `<script>` 用 `defer` or `async` - 若 html parse 到 `<script>`,就會暫停 parse 和 redner 並去載入後再執行裡面的內容(所以傳統會把 `<script>` 寫在最下面,避免 html node 還沒 parse 出來,但在 code 中可能會需要 call DOM API 使用的狀況) - defer - 可以在背景載入(不是用 main thread - 載入完成會等全部 html parse 完後才會執行裡面的 code(可確保 html node 都已經建立) - async - 可以在背景載入(不是用 main thread - 載入完成後暫停 html parse 馬上去執行裡面的 code - 總結 - ![](https://hackmd.io/_uploads/SkdiTjkea.png) - 可以把 `<script defer>` 在 html 中初始引入,加快載入速度 - ref - https://ithelp.ithome.com.tw/articles/10216858 - 利用診斷工具(ex. lighthouse),檢查網頁的載入是被哪邊 block - lighthouse 還會給建議,也有 .js 的 code usage - https://pagespeed.web.dev/ - ex. - ![](https://hackmd.io/_uploads/H1Dh-91Mp.png) - 若要使用 js library 用 cdn 版本 - client 通過 dns server 會先去找該 cdn 的 load balancer,它會回傳比較適合的 server,再去找他拿真正資源 - jquery - `https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js` - 而不是 - `https://code.jquery.com/jquery-3.6.0.js` - 資源做 lazy load - ex. image lazy load - 只有用到的圖片才去動態載入,而不是一開始就全部載入 - ex. https://imgur.com/ - Code Minimize - 把變數或函式名稱改成很簡短,且去掉空白 - 減少傳輸及瀏覽器解析時間 - 後端 - 若 table 的 data 每天都會新增很多,可以區分最新的資料的 table 和以前的資料的 table,加快查詢速度 - 部分小量的數值可以用 redis - ex. 當日總支出 - 可以用 sql 完成的事情就不要拉出來再做 - ex. - 不用拉出來再算平均 - `SELECT avg(price) FROM Products;` - procedure - 可以寫一些判斷邏輯 - 會被編譯,效能較好 - 但缺點是不易閱讀及修改 - sql 盡量不要 `select *`,挑要用的欄位就好 - db table 常用的查詢欄位使用 index - index 會幫此欄位建一張 index table,可以藉由此 index table 對應到原本 table 的 record - 優 : 速度快 - 且 InnoDB 的 index 使用 b+ tree 儲存,所以只要用 index column 當查詢條件,時間複雜度是 O(logn),否則就會變 full table scan 的 O(n)。而且 index(non leaf node)是存在 mem 裡面,data 才是存在 disk - 此狀況若要用 ssn 去 query 需要 full table scan - ![](https://hackmd.io/_uploads/rkXqP3GGT.png) - ![](https://hackmd.io/_uploads/H1LOd2fz6.png) - 若發現 ssn 很常被用來 query,可以幫它建 index - ![](https://hackmd.io/_uploads/rknzunfzT.png) - 實際就會變這樣查詢 - ![](https://hackmd.io/_uploads/rJ2V_hMG6.png) - 缺 : 佔空間 - 需額外存 index table,每次新增資料 index table 也都需要新增 - note - 如果 `where` 用了 `!=`, `<>`, `or` 或找的資料是不同 type(會被做型別轉換)或用 `sum` 等 function 就會變回 full table scan - mysql 會直接把 pk 建 index - 選擇 index 是要看是否常用此 column 查詢 - ref - https://www.jyt0532.com/2021/01/30/index/ - https://wenwender.wordpress.com/2022/08/26/%E8%B3%87%E6%96%99%E5%BA%AB%E7%9A%84%E7%B4%A2%E5%BC%95index%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/ - 建立方法 - ![](https://hackmd.io/_uploads/rkGxb3MG6.png) - `unique` 是只有當 index 的 column 是唯一辨識才能用 - ![](https://hackmd.io/_uploads/r17bchzMT.png) - ![](https://hackmd.io/_uploads/HyTZ5hMGa.png) - ex. - student 的 pk 是學號,但因為很常需用 email 去查詢特定學生,所以可以把 email 的 column 設為 unique index - 需大量存取的資料可以用 redis ## webpack - 可以 uglify 與 minify - 可以打包後端模組或套件(ex. npm)讓前端 js 用 - ![](https://hackmd.io/_uploads/rySKxiLfa.png) - 雖然在 ES6 後,可以在瀏覽器上用 import 的方式使用後端套件 - ![](https://hackmd.io/_uploads/SJJVWj8zp.png) - 需 type = module - 缺點是若是要用 npm 套件,路徑就要寫成 `'./node_modules/pad-left/index.js'`,不易維護 - 也可以打包 圖片, css 讓前端 js 用 - ![](https://hackmd.io/_uploads/HyRfliUMp.png) - note - 為什麼很多專案(例如說 React)在部署前都要先 build?這個步驟在幹嘛你知道嗎? - 因為原始碼沒辦法直接放上去瀏覽器(會沒有辦法執行),所以一定要經過 webpack 打包處理,打包完的檔案才能讓瀏覽器執行。 - 你知道 require/module.exports 與 import/export 的差別嗎? - require/module.exports 是一套叫做 CommonJS 的規範,Node.js 有支援,瀏覽器沒有。 - 可以用 `require` 引入內建 module - 用 `module.exports` 製作 module - import/export 是 ES6 的規範,Node.js 部分支援(副檔名需 = `.mjs`),瀏覽器也是部分支援(type=module)。 - ref - https://blog.huli.tw/2020/01/21/webpack-newbie-tutorial/ ## react - virtual DOM - 透過比對 virtual DOM,避免直接改動到 DOM,會需要全部 node 重新 render,performance 較差,而是針對部分改動的 node 修改 - 優 - 模組化:react 中很容易就可以把各功能模組化 - 減少 code reuse - 提升可讀性 - 效能提升 - 利用 virtual dom 優化效能 - 傳統會用 browser api 或透過 jquery 對整個 DOM 重新 render,virtual dom 是 React 自身的 diff 演算法去計算出最小更新,進而去最小化更新真實的 DOM。 - 可以用 state 處理動態 value 改變比較方便,不用自己寫 innerHTML 那些 - ![](https://hackmd.io/_uploads/rJII54HMa.png) - 所以 react 被稱為 State Machines:component 依據 state 改變重新 render component - state and props - state - 把資料(通常是 user input, request api data, 會變化的)放自己 component 中 - ![](https://hackmd.io/_uploads/H140NBjG6.png) - 可以在 constructor 中用 `this.state` 宣告 state 物件 - 如果是不會變化的通常就不會放在 state,因若 state 改變還須再定義一次 - 當 state 內的資料更新, 會重新 render 該 component 內容(重新執行每個 component 都有的 `render` function),針對 component 做重新渲染 - 要用指定的 `setState` function 去改變 state, react 才會執行 render, 否則不會重新 render - ![](https://hackmd.io/_uploads/H1udIBjMa.png) - ![](https://hackmd.io/_uploads/Hy7c8BjMa.png) - ![](https://hackmd.io/_uploads/HJCvIrjz6.png) - props - 繼承父 component 的 atrribure or method - 父傳給子 - `<Test2_Child father="Test2" name={this.insert_text} />` - 子接收 ```js= class Test2_Child extends React.Component { constructor(props) { super(props); this.state = { }; } UpdateData = () => { //console.log(this.props.name) this.props.name("child") } render() { console.log(this.props.father) return ( <div> <button onClick={this.UpdateData}>test2 child {this.props.father}</button> </div> ); } } ``` - result - ![](https://hackmd.io/_uploads/rJRqmVSzp.png) - 子 component 是 read-only, 若要更改就需用父的 function 去更改 - component 的 2 種寫法 - ![](https://hackmd.io/_uploads/Byr6EWSGa.png) - `public/index.html` - 唯一一個 html file,裡面有一個 `<div id = "root"></div>` 的 element - 其他的 element 都是用 js 去動態生成於 root element 之下 - ![](https://hackmd.io/_uploads/Bk-rPgrf6.png) - JSX - 可以直接嵌入 DOM element, 也可以用大括號代入變數的語法 - ![](https://hackmd.io/_uploads/HyIBJrHGT.png) - 用 react 的困難 - 觀念和以前一個網頁就是三支 html, js, css 的 template 的方式不太一樣 - 需要把不同小功能切開來(才不會每次都需 render 沒有更動到的)然後互相串接,還會需要繼承各個 component 的 value - life cycle 1. Mount: 元件被渲染到畫面上 - component 從 constuctor 的初始 到 render 執行完 3. Update: 元件的內容因為資料的更動而重新渲染 - component 因為 state 的變動而重新 render 5. Unmount: 元件從畫面上消失,移除這個元件 - component 被移除 - hook - 也可以用來宣告 state,且語法更嚴謹 - ref - https://sweeteason.pixnet.net/blog/post/42910964-react-%E5%88%9D%E5%AD%B8%E8%80%85%E7%AD%86%E8%A8%98%E8%88%87%E6%95%99%E5%AD%B8-%28%E4%B8%89%29---state-%E7%9A%84%E4%BB%8B%E7%B4%B9%E8%88%87%E4%BD%BF - https://medium.com/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/react-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-0-%E5%89%8D%E8%A8%80%E8%88%87%E6%96%87%E7%AB%A0%E7%B5%B1%E6%95%B4-44603bc6bdc5 ## oop vs fp - object oriented programming - 將不同 component 抽象化,封裝在不同類別,降低 component 間的耦合,且不需要知道元件的實作細節(所以可用 private) - 特色 - 抽象化 - 封裝 - 封裝成 public, protected, private - 盡量設 private,降低耦合 - 繼承 - code reuse - 問題 - name confilction - 重複繼承 - 多型 - 依不同子 class 做不同行為 - 優點 - 低耦合 - 若其中一個 component 改動一些實作細節也不須改動到別的 component - 易測試單個 component 功能 - 高內聚 - 易讀、維護,因元件所需用到的屬性、function 會在相同元件 - SOLID - S = Single-responsibility principle (SRP) = 單一職責原則 - 定義:一個模組應只對唯一的一個角色負責(就一個類別而言,應該只有一個引起它變化的原因) - 例子 - 錯誤用法 1:一個模組的改動會改動多個角色的功能 - ![](https://hackmd.io/_uploads/Hkgol0xbT.png) - 錯誤用法 2:把不同功能寫在同個 class,不易更新新功能及維護,且會有大量重複 code - ![](https://hackmd.io/_uploads/rkAuKGbW6.png) - sol 1. 直接依不同角色拆成不同 class 封裝好 - ![](https://hackmd.io/_uploads/S1sRbCgWa.png) 2. 拆成不同 class 封裝好並實作 interface,抽象化程度較高,比較好 - ![](https://hackmd.io/_uploads/Hk9lMRgbp.png) - 第二個例子的 sol:提高內聚、降低耦合 - ![](https://hackmd.io/_uploads/S1dunfWb6.png) - O = Open–closed principle (OCP) = 開放封閉原則 - 定義:面對擴展時是開放的,且擴充新功能時不應修改到原有的 code。 - 原因 - 未區分核心邏輯與附加的商業邏輯 - ![](https://hackmd.io/_uploads/S1qMSzb-T.png) - https://wadehuanglearning.blogspot.com/2019/12/blog-post_11.html - sol - 開放擴充點(像是透過繼承) - ![](https://hackmd.io/_uploads/HyTqHMW-T.png) - 用介面減少針對單一物件的依賴,就可以適應傳進來的不同物件,而不用改動核心邏輯(做好 DIP) - ![](https://hackmd.io/_uploads/r11rIfZ-a.png) - https://igouist.github.io/post/2020/10/oo-11-open-closed-principle/ - 區分好每個模組的角色和任務,就不會需要互相更改(做好 SRP) - 像是一開始就先設定好擴充點 - 例子 - chrome 不會因為新增 plugin 而導致修改到原有的程式 - L = Liskov substitution principle (LSP) = 里氏替換原則 - 定義:子類別要能夠替代父類別,且替換後可以正常做父類別的內容(輸出要相同) - 就是在處理繼承問題 - 原因 - 子類別無法做出和父類別相同的內容 - ![](https://hackmd.io/_uploads/ByTB_1W-6.png) - 不要只因為 code reuse 就繼承,能不要繼承就不要,而是使用介面 - 因為介面可以要求子 class 要完成 interface 中的 method - ref - https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle/ - 例子 1. 簡易 - ![](https://hackmd.io/_uploads/r1PEKkbWa.png) - https://wadehuanglearning.blogspot.com/2017/08/php-oo-lsp.html 2. 正方形問題 - ![](https://hackmd.io/_uploads/rkCvvHvzp.png) - ![](https://hackmd.io/_uploads/B1quwrPfp.png) - ![](https://hackmd.io/_uploads/B16V_SDMa.png) - 驗證長方形面積是否正確的 function - ![](https://hackmd.io/_uploads/Hk_w_Bvfp.png) - 驗證結果不合預期,因為正方形沒辦法回傳和長方形一樣的結果 - I = Interface segregation principle (ISP) = 介面隔離原則 - 定義:不應該強迫用戶依賴它們未使用的方法 - 原因 - 如果介面設定太多太廣的方法,造成實作的 class 無法全部都實作(或只能實作空方法) - sol - 最小化介面的職責(單一介面不要做太多事),如果有很多不同職責的功能就拆成很多介面 - 一個合理的介面設計是能夠符合單一職責原則的,反過來說,我們可以用單一職責原則來檢視我們的介面設計是否良好。 - 例子 - 交通工具的介面要求能開關車門,結果電動機車無法實作。 - ref - https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle/ - D = Dependency inversion principle (DIP) = 依賴反向原則 - 定義:high level module 不應該直接依賴於 low level module,兩者要依賴於 interface - 原因 - 因為 high level module 會用到很多種 low level module,所以不希望有 low level module 更新連帶 high level module 也要改 - 例子 - ![](https://hackmd.io/_uploads/BkxIAyW-T.png) - sol - ![](https://hackmd.io/_uploads/rJiER1Zba.png) - 可以用建構子的方式把 interface 的 object 帶入 hight level module - ref - https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle/ - ref - https://igouist.github.io/series/%E8%8F%9C%E9%9B%9E%E8%88%87%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91/ - 屌 - https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E7%9B%AE%E9%8C%84-b33fdfc983ca - 高內聚、低耦合 - 良好的內聚應該只關注在一件事情上,並適時地將不屬於自身職責的工作交給別人,達到所謂「該內聚而內聚,該耦合而耦合」。 - function 寫法 - ![](https://hackmd.io/_uploads/r1bWjGZZ6.png) - 不好的寫法 - ![](https://hackmd.io/_uploads/rJFDiMWb6.png) - ![](https://hackmd.io/_uploads/SJUrozW-6.png) - 好的寫法,封裝邏輯 - ![](https://hackmd.io/_uploads/Sy43jf-WT.png) - https://ithelp.ithome.com.tw/articles/10206839 - functional programming - 盡量將邏輯都包成 pure function,主要邏輯用 declarative 方式寫,且 1 function 盡量做一件事情就好 - imperative : 命令式,一行一行做指令 - ex. 用 `for` 一行一行寫邏輯 - declarative : 宣告式,將步驟封裝好,只需宣告想要的結果 - ex. 用 `map` 宣告 - 優 - 增加重用性 - 增加可讀性 - debug 容易,易找哪邊改到 state - pure function - 會回傳 value、不會更改到 function 外部的變數 (no side effect),所以相同參數有相同輸出 - ex. - `array.slice(start_index, end_index-1)` - 為 pure funtion : 不會更改 array 值 - 回傳切割的範圍 - ![](https://hackmd.io/_uploads/B1wwS8lla.png) - `array.splice(start_index, end_index-1)` - 不是 pure function : 直接更改 array 值 - 回傳切割的範圍,原 array 剩切割完的 - ![](https://hackmd.io/_uploads/rkV_SLexp.png) - array 可以這樣帶入參數,或用 deepcopy - ![](https://hackmd.io/_uploads/B1_uqvxlp.png) - 目的 - 並不是要讓全部 function 都變 pure function,而是要降低 function 間的耦合性。才不會專案一大,容易搞不清楚哪一行導致 state 改變 - 解決賦值問題 - ![](https://hackmd.io/_uploads/H1k0Xvega.png) - 原則 1. Task // function只做一件事,所以會有大量的 function,但可以增加可讀性 2. return Statement // 會回傳結果狀態 3. Pure //純函式 4. No shared state //兩個函式不共用同個狀態 5. Immutable State //不可變的 6. Composable //可被組合 7. Predictable // 可預期的 - 兩者不衝突,可同時使用 - 兩者都是將實作概念抽象化,但 oop 在不同種類的物件間可以比較快速的看出互相的關係(因抽象化的範圍較廣),而 fp 可讓物件中實作的細節被再被更封裝起來。所以如果沒有 oop 而只用 fp 會讓不同種類物件之間的關係很複雜。 ## concurreny programming - 定義:process 可以一次執行多件事情(multi threading),最大化 cpu usage - Concurrent Programming 為什麼比 Object Oriented Programming 好 - Concurrent object-oriented programming is a programming paradigm which combines object-oriented programming (OOP) together with concurrency. `- wiki` - 應該可說 concurrent programming 可以在 oop 的基礎上再加上 concurrency,讓 process 一次執行多件事情來最大化 cpu usage - Multi-processing (多處理程序/多進程): - parallelism - 可以利用多顆 cpu 的效能 - multiple threads at the same time run across multiple cores - 資料在彼此間傳遞變得更加複雜及花時間,因為一個 process 在作業系統的管理下是無法去存取別的 process 的 memory - 適合需要 CPU 密集,像是迴圈計算 - cpu loading 會持續很高的狀況 - ![](https://hackmd.io/_uploads/H1_aFFCZa.png) - Multi-threading (多執行緒/多線程): - concurreny - 可以最大化使用單顆 cpu 週期 - 當 thread 在 wait io 時可以執行別的 thread - 資料彼此傳遞簡單,因為多執行緒的 memory 之間是共用的(因同個 process),但也因此要避免會有 Race Condition 問題 - multiple threads at the same time are generated by a single process - 適合需要 I/O 密集,像是處理 user request 再寫入 db 需大量讀寫 memory, disk、爬蟲需要時間等待 request 回覆 - 等待時可以做別的 thread - ex. 聊天室:收發訊息用不同 thread - ![](https://hackmd.io/_uploads/r1katK0WT.png) - 總結 - multi-processing - 可以在相同的時間內完成較多的工作 - 比較耗資源, 不同 process 間資源不共享 - 適合 cpu bound - 如果 cpu bound 用 multi-threading 效能可能會下降,因為 cpu loading 很滿還需要輪不同 thread - multi-threading - 可以讓相同工作可能可以在更短時間完成 - 比較不耗資源, 共用 memory space - 適合 io bound - 最大化 cpu 週期 - ref - https://www.maxlist.xyz/2020/04/09/concurrency-programming/ - https://www.geeksforgeeks.org/difference-between-multithreading-vs-multiprocessing-in-python/ - https://www.maxlist.xyz/2020/03/15/python-threading/ ## virtualization - vm - 硬體層級的虛擬化 - 可將單台 host 的硬體資源切割並最佳化利用 - 不一定想讓整台都在同一個環境作業 - 同時管理多台 vm 較管理多台有自己硬體的 host 容易 - 安全性高:不同 vm 環境以 os 隔離開來 - 缺點:抽象化層級太高,不易切割應用程式(ex. 只是要切割不同 web application 環境卻要切割 os,太浪費資源) - hypervisor(vm 管理軟體) type - bare metal - 直接在 host 上架 hypervisor - 較快:少了透過 host 的 os 轉送到硬體 - hosted - 透過 host 的 os 下載 hypervisor application - 較慢 - container - os 層級的虛擬化 - 把不同應用程式以容器隔離 - 優點 - 啟動快速:共用 host 的 os kernel - 可移植性:將 application 會用到的檔案、環境都包在 container,不用管不同 os 的設定 - 安全性高:用 namespace 將容器間的環境隔離開來(ex. network, storage)、用 cgroup 可以限制單個 container 的資源使用量(記憶體、檔案快取、CPU 使用、磁碟 I/O),ex. 所以有一個 container 被駭掉不會完全影響到整個 vm 的流量 - 可隔離開發和測試環境(且部屬快速 - 但相較於 vm 安全性低:因共用 os kernel - 同時多個 container:容器啟動或關閉,在硬體資源充足的情況下不互相影響 - 擴展性高:可以很輕易的開多個 container 分散流量 - 缺點 - 管理複雜:因將一個 application 切成多個微服務的容器需要管理、互相溝通、如何讓外部存取、存取其他服務的權限、可以使用的資源,尤其是在部屬了多種不同的 application 在多台 VM 上(導致學習成本高) - ex. 要部屬資管系、學校要用的一堆系統 - 安全性疑慮:共用 os kernel,若 kernel 有問題 所有 container 甚至 host 都會受影響 - image - 可以在 image 上疊加 image(ex. 在 php image 上加 CI image),使用 overlay2 讓整個 image 看起來只有一個 file system - overlay2 - 一種 UnionFS (聯合檔案系統) - Dockerfile 指令 - `from` - 要從哪一個 image 作為 base image 再修改 - `env` - 環境變數 - `run` vs `cmd` vs `entrypoint` - 兩種 type - Shell form command param1 param2 - 是以 `/bin/sh -c` 的方式執行 - Exec form ["executable","param1","param2"] - 以執行檔方式執行,所以會無法取得環境變數 - 但可以用 `["/bin/sh", "-c"]` 執行就有了 - 所以也可以用不是此預設的 shell 來執行 - run - 建立 container 時會執行的指令 - 通常用來 install package - cmd - container 建立完成時執行 - 若有多個,只有最後一個 `cmd` 有效 - 若 `docker run` 有帶參數就不會執行 - entrypoint - container 建立完成時執行 - 若有多個,只有最後一個 `entrypoint` 有效 - 一定會執行 - 總結 - run 是用來安裝一些套件(ex. `apt install nodejs`) - `cmd`, `entrypoint` 是 container 建完要執行的(ex. `node index.js`)事情 - 如果是可能會被取代的事情,可以用 `cmd`,否則就用 `entrypoint` - ref - https://hackmd.io/@tienyulin/docker3 - `expose` - 開放 port - ex. - ![](https://hackmd.io/_uploads/r1OvPFDf6.png) - docker network - ![](https://hackmd.io/_uploads/r1l7TDfeT.png) - default 用 bridge - bridge 讓同台 host 上不同 network 的 container 可以用 ip 溝通 - docker swarm - ingress - 讓 cluster 中就算是沒有部屬該 task container 的 node 也可以用同個 port 連到 - ![](https://hackmd.io/_uploads/SyQx1FDzT.png) - 可以用 worker2 的 ip:80 連到 service - ingress routing mash - ![](https://hackmd.io/_uploads/HyWGJKvGp.png) - 可以發現 node 的流量會轉送到 172.18.0.2 (ingress-sbox, 它是一個 network namespace) - ![](https://hackmd.io/_uploads/SkCsyYwGa.png) - `sudo iptables -nvL -t nat` - 它會把流量轉送到有 task container 的 node 上 - overlay - deploy stack 時如果沒有自己設定要用哪個 network,會幫 stack 自動建一個 overlay network - ![](https://hackmd.io/_uploads/rkdazKvMp.png) - 這樣同個 network 下,不同 node 的 container 就可以溝通了 - 總結 - VM 簡化了硬體資源配置與作業系統安裝的工作 - Container 簡化了介於作業系統與應用程式 (App) 之間的環境建置工作 - ref - https://hackmd.io/@ncnu-opensource/book/https%3A%2F%2Fhackmd.io%2F%40qtNgFtaqR4Or_CLAuotXjw%2FB1EpbbGk3 ## ORM (Object Relational Mapping) - 避免直接寫 raw sql,而是可以用 object 方式操作 sql - 因為每個人的 sql 寫法不同,用此較好維護 - 切換不同資料庫不須更新 - mysql, mssql - 避免 sql injection - django - 基本可以用 filter, all 去 select - php ORM vs query builder - 都是避免直接寫 raw sql,而是可以用 object 方式操作 sql - 因為每個人的 sql 寫法不同,用此較好維護 - 切換不同資料庫不須更新 - 但某些複雜情況還是得用 raw sql ## unit test 單元測試 - 藉由檢查每一個 function 回傳的物件,可以確定程式是不是有跑在預期的結果裡面 - 可以避免之後改版不小心改動到原本正常的 function - 主要是測試 function 的邏輯 - 固定 data 是否可以得到相同輸出 - 3A - Arrange - 準備測試會用到的 object - Account - 用 object 做一些邏輯得出結果 - Assert - 確認結果是否和預期一樣 - SUT(System Under Test) - 待測的系統,通常是 class - 每個 function 個別測試 - 若有 function 中用了另一個 function (ex. call library),就兩個 function 分開測試 - 降低耦合 : 避免測試 A function 時呼叫 B function,但 B function 是有問題的 - mock vs stub vs spy - stub - 當要驗證的 function 需要第三方的回傳資料(沒有帶參數) - ex. call db data - 就製作假資料取代第三方的回傳資料 - 實際方法 - 可以用物件導向的方式,把會使用到的第三方物件用建構子的方式帶入 class - 測試時再將帶入的第三方物件改為自己設定的 - ![](https://hackmd.io/_uploads/Hyr93P8gp.png) - ![](https://hackmd.io/_uploads/Sk9unP8gp.png) - mock - 當要驗證的 function 需要引入第三方 function 並依照驗證 function <b>帶入的參數</b>做事情 - 偽造第三方 function 並檢查帶入第三方物件的參數是否正確(檢查互動的方式是否正確) - 最後<b>是檢查第三方 function</b> 的資料是否正確 - ex. call function 寫入 log 檔 - ![](https://hackmd.io/_uploads/H19cEoDfp.png) - 原本的第三方物件 - ![](https://hackmd.io/_uploads/HJtUfFLeT.png) - 偽造假的第三方物件 - 偽造的要可以回傳需驗證 function 的 value - stub vs mock - 差別在於驗證原本的物件 or 假的第三方物件 - ![](https://hackmd.io/_uploads/ryRifFIxT.png) - ref - https://hackmd.io/@AlienHackMd/HycK5pEpo - https://ithelp.ithome.com.tw/articles/10263479 - spy - 驗證原本物件對其他物件的改變是否正確 - ![](https://hackmd.io/_uploads/BJ-PIKIgp.png) - ref - https://medium.com/starbugs/unit-test-%E4%B8%AD%E7%9A%84%E6%9B%BF%E8%BA%AB-%E6%90%9E%E4%B8%8D%E6%B8%85%E6%A5%9A%E7%9A%84dummy-stub-spy-mock-fake-94be192d5c46 - 總結 - stub - 偽造 SUT 所需的第三方資料 - 驗證整個 SUT 的回傳結果 - mock - 偽造 SUT 傳入參數的第三方模組 - 驗證偽造的第三方模組收到的資料 - spy - 驗證被 SUT 改動到的模組被改動後的資料 - NodeJS - repo : 可用 `mocha` + `supertest` + `chai` - `index.js` ```js= process.env.NTBA_FIX_319 = 1; const express = require('express') const app = express() const http = require('http'); const server = http.createServer(app); // 要測試的 api app.get('/test', async function (req, res) { res.json({suc:'test suc'}); res.end; return; }); app.get('/test/good', async function (req, res) { res.json({suc:'test good'}); res.end; return; }); server.listen(3001, () => { console.log('listening on *:3001'); }); module.exports = app // 這邊要 export 給 test 的 script ``` - `test.js` ```js= // supertest 可以讓專案不用另外跑起來也可以測試 api,它會幫你跑 const request = require('supertest') const app = require('./index') const { expect } = require("chai") // dedscribe 為一個測試區塊,通常裡面會有多個 it,代表實際要測試的更小的測試區塊 describe('GET /test', () => { it('should respond "test good"', (done) => { request(app) .get('/test/good') .end((err, res) => { const text = res.text; // chai 的語法,讓我們比較好描述要測試的東西 expect(text).to.be.equal('{"suc":"test good"}') done(); }) }), it('should respond "test suc"', (done) => { request(app) .get('/test') .expect(200, '{"suc":"test suc"}', done) }) }) ``` - chai 用法 - ![](https://hackmd.io/_uploads/HJ5LY1-f6.png) - `package.json` 要加上啟動 test ```json= "scripts": { "start": "node index.js", "test": "mocha test --exit " } ``` - `--exit` : 測試完就結束 script,最好都要加 - 開始測試 : `npm test` - ![](https://hackmd.io/_uploads/ByRuX1WfT.png) - gitlab ci - `.gitlab-ci.yaml` ```yml= image: node:latest stages: - build - test - deploy default: tags: - linux before_script: - now_time=$(date) - echo "${now_time}" - echo "started by ${GITLAB_USER_NAME}" - chmod 0777 ./node_modules/.bin/mocha build-job: stage: build script: - echo "running scripts in the build job" - npm install test-job1: stage: test script: - echo "running scripts in the test job 1" - npm test dependencies: - build-job test-job2: stage: test script: - echo "running scripts in the test job 2" deploy-job: stage: deploy script: - echo "running scripts in the deploy job" ``` - pipeline - ![](https://hackmd.io/_uploads/rkkV81bzp.png) - test-job1 - ![](https://hackmd.io/_uploads/HyYLUkbfT.png) - ref - https://medium.com/@stupidcoding/%E5%9C%A8node-js%E5%AF%AB%E6%B8%AC%E8%A9%A6-mocha-chai%E6%96%B7%E8%A8%80%E5%BA%AB-supertest%E6%A8%A1%E6%93%AC%E9%80%A3%E7%B7%9A-sinon%E6%9B%BF%E8%BA%AB-nyc%E7%B5%B1%E8%A8%88%E8%A6%86%E8%93%8B%E7%8E%87-f736c423b893 - https://medium.com/cubemail88/node-js-%E7%94%A8-mocha-%E5%81%9A%E5%96%AE%E5%85%83%E6%B8%AC%E8%A9%A6-16dd9125e632 ### 整合測試 - 整合和其他模組間的測試 - ex. db 和 web application ### end-to-end 測試 - 以客戶角度對整個系統測試 - 可以是人工也可以自動 ### CI/CD - Continuous Integration (持續整合) 與 Continuous Deployment (持續部署) - CI - 系統 環境安裝 + 單元測試 - CD - 系統 環境安裝 + 單元測試 + 自動部屬 - 作用 - push code 之後將剩下作業自動化 - 通常包含:建置(安裝環境)、測試、部屬 - 優點 - 自動化單元測試、部屬 - 可以快速更新新版本(有小問題可以馬上更新),減少環境安裝、部屬時間 - 降低操作錯誤性 - 流程通常會分 3 個 `stage` (也可以有多個) 1. `build` - 環境安裝 - 也可以省略此直接用該環境的 image - ex. `image: lorisleiva/laravel-docker:latest` 2. `test` - 單元測試 - 利用 `dependencies` 取得 `build` 安裝的環境 - 所以可以測試不同環境(ex. php5, php6) 3. `deploy` - 部屬到正式或測試環境 - 只要有其中一個有問題,就不會往下執行 - 建置成功才跑測試,測試成功才做佈署,這樣的先後關係在 GitLab CI 稱之為 `pipeline` - ![](https://hackmd.io/_uploads/r1N5PNIlT.png) - ![](https://hackmd.io/_uploads/B1lpJplMT.png) - 如果不分 `stage` 就會由上而下執行且不管上面有沒有成功下面的還是會執行 - `.yml` - 實際在哪個 stage 要執行那些指令是以 `job` 為單位 - 每個 `job` 都是獨立的 container - 所以如果要 `job` 間要共用一些環境,就要用 `dependencies` - `runner` : `job` 的執行器 - 預設每個 job 的 runner 各自獨立 - 可以用 gitlab 上提供的,也可以用自己架的 - 自己架 - 要提供 ssh 金鑰登入 - gitlab 上提供的 shared runners - settings -> CI/CD - ![](https://hackmd.io/_uploads/r1lYRhgfa.png) - runners -> expand - gitlab 提供的 runner, 每個 runner 都有 tag, 去區分不同環境的 container - ![](https://hackmd.io/_uploads/ryDMJaDMa.png) - ![](https://hackmd.io/_uploads/rk7rkaDzp.png) - `tag` : 指定哪一種類的 runner - `only` : 指定 branch - 通常 `deploy` stage 會指定 master branch(只有 master branch 的 code 會被 deploy 到正式環境) - `before_script` : `job` 執行 `script` 前會執行的指令 - `script` : `job` 真正執行的指令 - ex. - ![](https://hackmd.io/_uploads/HyrVtrIl6.png) - https://ithelp.ithome.com.tw/articles/10187654 - ![](https://hackmd.io/_uploads/HkfHKH8xT.png) - https://nick-chen.medium.com/%E6%95%99%E5%AD%B8-gitlab-ci-%E5%85%A5%E9%96%80%E5%AF%A6%E4%BD%9C-%E8%87%AA%E5%8B%95%E5%8C%96%E9%83%A8%E7%BD%B2%E7%AF%87-ci-cd-%E7%B3%BB%E5%88%97%E5%88%86%E4%BA%AB%E6%96%87-cbb5100a73d4 ```yml= stages: - build - test - deploy default: tags: - windows before_script: - Set-Variable -Name "time" -Value (date -Format "%H:%m") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" build-job: stage: build script: - echo "running scripts in the build job" test-job1: stage: test script: - echo "running scripts in the test job 1" test-job2: stage: test script: - echo "running scripts in the test job 2" deploy-job: stage: deploy script: - echo "running scripts in the deploy job" ``` - https://editor.leonh.space/2022/gitlab-ci/ - 查看此次 commit 的 pipeline 1. 點進 repo 的 branch 2. Build -> Pipelines (此 repo 的所有 branch 的 pipeline 都會在這) - ![](https://hackmd.io/_uploads/r1D7xU8g6.png) - 查看單一 pipeline 中各別 job 執行狀況 1. 點進 repo 的 branch 2. Build -> Jobs - ![](https://hackmd.io/_uploads/BydFRSUga.png) - 點進 job 看 log - ![](https://hackmd.io/_uploads/SJXuRBLeT.png) - ex. nodejs ```yml= image: node:latest stages: - build - test - deploy default: tags: - linux before_script: - now_time=$(date) - echo now_time - echo "started by ${GITLAB_USER_NAME}" build-job: stage: build script: - echo "running scripts in the build job" - npm install - node index.js test-job1: stage: test script: - echo "running scripts in the test job 1" - curl 127.0.0.1:3001 dependencies: - build_job test-job2: stage: test script: - echo "running scripts in the test job 2" deploy-job: stage: deploy script: - echo "running scripts in the deploy job" ``` - ref - https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml ## python - oo ```py= # 物件導向的最基礎,我們定義class,然後用它宣告我們的物件/實體(instance) # class 可以定義attribute(成員)、method(方法),還有一些既定方法像是初始化__init__ # class裡的method,跟function基本上是一樣的,但第一個參數是self,會指向此實體本身 # 定義一個class叫做Person # 我們習慣把class的名稱定義成UpperCaseCamelCase,字的開頭大寫 class Person: # 定義class attribute,所有實體會共用它 kind = 'Person' # 初始化,當宣告實體的時候這個方法會被呼叫 # 用self.xxx來定義實體的 attribute,每個被宣告的實體會有自己的attribute def __init__(self, name, age): self.name = name; self.age = age; # 定義實體的方法 def say_hi(self, test): print(f'Hi, my name is {self.name}, I am {self.age} years old {test}') # 使用定義好的Class宣導實體(instance) mike = Person('Mike', 5) # 帶進去的參數Mike與5分辨對應__init__裡的name與age john = Person('John', 10) # 呼叫實體的方法 mike.say_hi('god') # output: Hi, my name is Mike, I am 5 years old john.say_hi('good') # output: Hi, my name is John, I am 10 years old # 印出實體attribute print(mike.name) # output: Mike print(john.name) # output: John # class attribute是共用的,不管是class本身或是instance都能使用 print(mike.kind) # Person print(john.kind) # Person Person.kind = "pig" print(mike.kind) # pig print(john.kind) # pig ``` - set - 過濾重複資料 ```py= a=[1,1] list(set(a)) # [1] ``` - 帶入 variable - `print(f"{a}")` - 相反字串, list - `a[::-1]` - `for i in range(5)` - 一開始會先建 [0~4] 所以每次 loop i 會照順序被改 - 所以在 loop 改 i 的值(ex. `i=500`), 下次 loop 還是會變成 0~4 中的順序的值 - `a[:3]` = `a[0:3]` - a index = 0~2 - link list 資結 ```py= # Definition for singly-linked list. class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next ``` - 參數可設 default - class 是共用 address ```py= a = test(5) a.printN() # 5 c=a c.n = 8 a.printN() # 8 class test : def __init__(self, n) : self.n = n def printN(self) : print(self.n) ``` - linked list - 變數是 object 的話,變數內容是指向 object 的 address ```py= a = [1,2,3] b = a a[1] = 8 # a, b 指向相同 object 的 address, 所以 a 改 b 會改 print(b) # [1,2,8] a = [1,2,3] b = a a = [1,2,8] # a 指向其他 address, 而 b 還是指向原本 object 的 address print(b) # [1,2,3] ``` - 錯誤想法 ```py= # head = head of a linked list # new head, it's value = head.val new_head = head temp_new_head = new_head head = head.next # 一定要加這行 # 找全部不是 val 的 node while head != None : if head.val != val : new_head.next = head # 不然這邊一開始 new_head = head, 所以這樣的意思等於 head.next = head, 就會無限迴圈 new_head = head head = head.next return temp_new_head ``` - heap ```py= import heapq # 需引入 a = [2, 1, 4, 0, 8] # 轉為 min heap(若要轉為 max heap 可以先將 data 都 * -1) heapq.heapify(a) print(a) # [0, 1, 4, 2, 8] # 以下 pop, push 都須先轉為 heap # pop min heapq.heappop(a) # 0 print(a) # [1,2,4,8] # push heapq.heappush(a, 3) print(a) # [1, 2, 4, 8, 3] ``` - 題目 - https://leetcode.com/problems/kth-largest-element-in-a-stream/ - https://leetcode.com/problems/last-stone-weight/ - https://leetcode.com/problems/top-k-frequent-words/ - dfs bfs 1. ![](https://hackmd.io/_uploads/BJZ4_vMGa.png) - https://leetcode.com/problems/path-sum/description/ - dfs ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool: # dfs # leaf node's sum leaf_sum = [] # dfs traversal self.dfs(root, leaf_sum) # 最後檢查是否 target in leaf node's sum if targetSum in leaf_sum : return True else : return False def dfs(self, root, leaf_sum) : if root != None : # left and right child if root.left != None : # add parent value root.left.val += root.val # 繼續往左做到底 self.dfs(root.left, leaf_sum) if root.right != None : # add parent value root.right.val += root.val # 繼續往右做到底 self.dfs(root.right, leaf_sum) # 是 leaf node, 加到 leaf node's sum if root.left == None and root.right == None : leaf_sum.append(root.val) ``` - bfs ```python= def bfsSol(self, root: Optional[TreeNode], targetSum: int) -> bool: # bfs # leaf node's sum leaf_sum = [] # init queue, put the root queue = [root] # bfs traversal while len(queue) > 0 : # dequeue node = queue[0] queue.remove(node) # 因可能 root is None if node != None : # left and right child if node.left != None : # add parent value and enqueue left = node.left left.val += node.val queue.append(left) if node.right != None : # add parent value and enqueue right = node.right right.val += node.val queue.append(right) # check is leaf if node.left == None and node.right == None : leaf_sum.append(node.val) # 最後檢查是否 target in leaf node's sum if targetSum in leaf_sum : return True else : return False ``` 2. https://leetcode.com/problems/average-of-levels-in-binary-tree/description/ - dictionary - 撈 key, value - `for key, value in dict.items() :` - 撈 key - `for key in dict.keys()` - 撈 values - `for val in dict.values()` - 確認 key 是否存在 - `key in dict` - True or False - list - 可以相加不能減: `a = [1,2] + [3] # [1,2,3]` - 放到最前面 : `a.insert(0, 4) # [4,1,2,3]` - 相乘 : `[1,2] * 2 # [1,2,1,2]` - permutation 排列(n! 種) ```python= class Solution: def permute(self, nums: List[int]) -> List[List[int]]: output = [] # pass by reference self.perm(nums, [], len(nums), output) return output # deep copy of list def copy(self, obj) : n_obj = [] for i in obj : n_obj.append(i) return n_obj # 排列 def perm(self, nums, cur, total_len, output) : # 長度到了就結束 if len(cur) == total_len : # 是結果,所以需 deepcopy output.append(self.copy(cur)) else : # 每個點都做所有 value 的排列 for i in range(len(nums)) : # 這個點這一次要用的 value temp = nums[i] # 加到目前 list cur.append(temp) # 從原本 nums 中移除讓下個點不能用 nums.remove(temp) # 下個點以剩下的 nums 繼續做 self.perm(nums, cur, total_len, output) # 這個點做完此 value,換一個 value 所以要移除 cur.remove(temp) # 把這次用的 values 插回去原本 nums 的 index nums.insert(i, temp) ``` - combination 組合(C n 取 k) ```python= class Solution: def combine(self, n: int, k: int) -> List[List[int]]: output = [] self.dfs(n, k, [], 1, output) return output # 遞迴 k 次 def dfs(self, n, k, cur, now, output) : # 長度 = k, 結束 if len(cur) == k : output.append(cur.copy()) else : # 每個點只能用(起點是)前面點用過以後的 value(i+1) for i in range(now, n+1) : cur.append(i) self.dfs(n, k, cur, i+1, output) cur.remove(i) ``` - note - `//` : 沒小數 - `/` : 有小數 ## dns - 一個 dns server 可以有多個 dns zone - 每個 dns zone 會有自己的 SOA - SOA (Start of Authority) : zone file 的第一筆record,主要記載這個 zone 相關的資訊,例如管理員信箱、用來辨別是否有更新的序號、refresh、TTL(超過時間就要向 dns server 重要一次 dns record) - serial 序號 - 讓 secondary name server 知道有更新過 - refresh - secondary name server 多久要來和 primary 確認是否有更新資料 - 可以一個 dns zone 裡面放很多筆 dns record,但可能有些 domain 是想要有特殊設定(可能是網路內部才能存取的 domain) - DNS load balance - 可以設定多個 NS 的 dns server,就可以 load balance - zone transfer - 為了讓資訊同步,將 primary nameserver 的 zone file 複製一份到 secondary nameserver 的這個溝通過程,就稱為 zone transfer。 - DNS cache poisoning (DNS spoofing) - dns resovler 要 dns query 時,偽造錯的 dns 紀錄給它 - 只要偽造的 server 可以比正確的 server 還要早傳紀錄給 resolver 及包含一些正確資訊可能就可以 - 知道要回傳給 resolver 的 port (可以大量送) - 應用於中國的防火牆 - DNSSEC (DNS security) - 將 dns record 用非對稱式加密 - 可以確保 dns record 完整性,不會被竄改 - dns recon - 取得 dns server 的所有 dns record - 一般來說不能亂給別人看到所有紀錄,因為可能有些是內部使用的 domain - type - passive recon - 透過 browser search engine 找 subdomain - 但只能找到網頁的服務(ftp 就找不到) - active recon - 利用 dns server 漏洞直接向 dns server 拿完整資料 - ref - https://tech-blog.cymetrics.io/posts/crystal/dns-hacking-3/ - rndc (remote name daemon control) - 可用 rndc command 遠端或本地控制管理Bind,並以加密方式來傳送資料 - 當網管時管理 dns 哪邊沒做好 - 之後可以加上 dnssec - 不安全的 DNS 服務會遭什麼攻擊,有什麼辦法可以解決? - 被竄改 dns record - DNSSEC - 不存在的 dns record 是否真的不存在 - NSEC - 將 dns record 依首個字的字母大小排序,若找不到 record 會回傳下一筆(依字母排序的)存在的 dns record - 這樣就無法偽造下一筆的 dns record 不存在 - ex. - ![](https://hackmd.io/_uploads/HkII9Zxfp.png) - deploy bind9 on ubuntu - https://magiclen.org/ubuntu-server-dns/ ## 前後端分離 - 不分離 - server side render - 前端改,後端可能需要重跑 - 分離 - client side render - 讓 SEO 評分降低,因只能爬到一點資料 - 降低前後端耦合 - 增加開發效率、分配工作 - 更易維護、找問題 - path traversal - hacker 利用 `../` 存取其他 path 的檔案 or api - ![](https://hackmd.io/_uploads/B1RyuiMM6.png) - ![](https://hackmd.io/_uploads/r1Y7OofMT.png) - ![](https://hackmd.io/_uploads/BJB-djMMa.png) - sol - api 都要驗證 token ## 基因演算法GA (Gene Algorithm) - 因為要找出最佳解會花太久時間,此可以快速找出近似最佳解 - 如果是要找出一個外送員要送到 n 個客戶的最佳路線問題就是 O(n!) - 步驟 1. 將 data 編碼成基因 - 我們基因是由 city + car 組成 - `c3 c2 v1 c1 c8 v2 ...` - `vx` : 第 x 個 car - `cx` : 第 x 個 city 3. 產生 init population (隨機的初代群集) 4. 幫 population 中的個別染色體做 fitness 評分 - 一般會有多個情況影響到 fitness - 單一 car 超過最大最小限制公里 fitness 要加倍懲罰 - 單一 car 跑的城市數量超過最大最小也要懲罰 - 跑太少懲罰比要跑太多高 - 所有 car 跑的公里數的標準差太大也要懲罰 - 而且這個懲罰倍數要很高,因為跑的公里數要平均最重要 - 要算哪個條件要懲罰幾倍較麻煩 - 所以 fitness 算出來異常也比較麻煩, 要過濾掉多個條件 5. 對 population 做天擇,保留 fitness 較好的染色體 - selection tournament - 天擇方法 1. 從 poplation 隨機選幾個染色體 2. 從幾個染色體中挑 fitness 比較好的染色體做為新的 population 6. 將 population 中部分的染色體做兩兩的交配,利用他們的基因產生新的下一代的染色體的基因,這些新染色體會組成下一代的 population - 採雙點交配法 - 會隨機切一半是 parent1 的基因的順序,剩下另一半會是 parent2 基因的順序 - ref - https://hackmd.io/@labNote/HyPHPwsRj - https://hackmd.io/9Os2l76XTem9u9eUx_ZmBA?view#Travelling-salesman-problem%EF%BC%88TSP-%E6%97%85%E8%A1%8C%E6%8E%A8%E9%8A%B7%E5%93%A1%E5%95%8F%E9%A1%8C%EF%BC%89 ## terraform - 和 ansible 一樣都可以用來實現 IaC - 若想要新開一個測試環境,需要手動 - 去 aws 租一台 server, 並設定環境(ex. 網路) - install docker, db ... 並設定 - 把 repo 放上去並跑起來 - 但 terraform 可以把這些流程用腳本自動化 - ![](https://hackmd.io/_uploads/S1yYT1HfT.png) - provider : 雲端服務的提供者, ex. aws, azure - ref - https://www.huanlintalk.com/2023/07/terraform-overview.html ## galera - synchronous replication - 採用兩階段的 commit 可以確保所有 replicas 的 commit 都是同步,只可能是全部都有或是都沒有 - 優 - 不管啥時 failover 所有 replicas 資料都會是同步的 - 可以用 haproxy 做 load balance 增加 performance - 可以用 haproxy 做 failover - 速度慢 - 但隨著 cluster 中的 replicas 增多,交易 delay 會大幅增加,因須同步更多台 replicas - 且因每一台 replicas 都可以被寫入,所以須處理多個 replicas 的 confict ## master-slave - asynchronous replication - master 會直接完成交易並將 event 寫入到自己的 log,slave 再定期去和 master 拿新的紀錄 - 所以 master 不會知道 slave 有哪些紀錄,也不能保證任何 slave 和 master 的資料是同步的 - 所以當 master 有問題要 failover/failback(故障自動轉移) 時可能會有已經 commit 的紀錄卻沒有同步到 slave 的問題 - 速度快 - 不用處理 commit 同步問題 - 只有 master 能被寫入,不須處理多個 replicas 的 confict - ref - https://galeracluster.com/library/documentation/tech-desc-introduction.html - master-slave + keepalived - ![](https://hackmd.io/_uploads/Bk1H-XjGT.png) - 先將 A B 做戶為主從的設定 - 再 A B 都安裝 keepalived 並設定一個虛擬 ip - 客戶就連這個虛擬 ip 就可以 ## vue.js - 概念滿像是把 html 裡面會用到的內容(屬性, value)都分出來元件化 - html 放在 `<template>`, code 放在 `<script>` - 用 MVVM declarative render 概念帶入變數 : `{{變數}}` - MVVM - model - 處理資料 - view - 畫面呈現 - viewModel - 將 model 資料即時轉換給 view 做呈現 - html 用 v-for(for loop) 取出元素 - ![](https://hackmd.io/_uploads/S1FGhXjf6.png) - ![](https://hackmd.io/_uploads/ByhKpQjzT.png) - computed : 可以做依些運算再回傳值 - ![](https://hackmd.io/_uploads/SyPk6XofT.png) ## ref - https://hackmd.io/@splitline/BkALfYY5r