--- tags: backend description: 6/02後端會議 --- # 一、06/02 Node.JS & JS原形鍊 ## What is Node.js 『ㄏ勝』 :::success Node.js 是 Ryan Dahl 基於 Google 的 V8 引擎於 2009 年釋出的 JavaScript 開發平台,是一個高效能、易擴充的==網站應用程式開發框架(Web Application FrameWork)==,主要聚焦於 Web 程式的開發,通常被用來寫網站 它誕生的原因:為了讓開發者能夠更容易開發高延展性的網路服務,不需要經過太多複雜的效能調整及程式修改,就能滿足==網路服務在不同發展階段對效能的要求== ::: 優點: 1. 性能好,加載速度快,性能相當與php的86倍。 2. 依賴==chrome V8引擎==進行代碼解釋 * 開發前後端,用==node和golab==最好,其他的有可能會因為google引擎修改,而使效能降低 3. nodejs的非阻塞IO帶來了低資源耗用下的高性能和出眾的負載能力 * 非阻塞 -> 非同步的意思 * 非阻塞同時也是最主要的新手障礙 缺點: 1. 單線程,只支持單核CPU,一旦一個進程垮掉,整個服務器就會崩潰(可解) 2. 不適用於計算密集型應用(可解) 3. 同場加映Node.js 開發之父:「十個Node.js 的設計錯誤」(小問題) ## 同步?非同步? * ㄏ勝以「阻塞」和「非阻塞」來代稱,而要理解兩者的差異,必須先理解process和thread的關係 |Process|Thread -|- 資源不共享的程式|資源共享的子執行緒 ### Process (程序、進程) * Process 是電腦中==已執行 Program 的實體== * 每一個 Process 是==互相獨立的== * Process 本身不是基本執行單位,而是==Thread (執行緒)的容器== * Process 需要一些資源才能完成工作,如 CPU、記憶體、檔案以及I/O裝置 #### 程式(Program)?程序(Process)? 名稱|意義 -|- 程式|放在電腦內、尚未執行的檔案。 程序|已經在執行中的process Process其實就是活化的程式,開一個程式,那個程式就會變成一個process。 ==電腦有幾核心就有更多的process== * 系統管理員-CPU * PID就是process的位置 * thread就是他開了幾個thread * (EX:時鐘裡,會有秒針的thread,分針的thread) ### Thread (執行緒、線程) * 同一個 Process 會同時存在多個 Thread。 * 同一個 Process 底下的 Thread 共享資源,如 記憶體、變數等,不同的Process 則否。 Node ~ Node 是==Single-Process & Multi-Thread==的運作模式 而Node.js 是 用 JS 寫的,所以JS其實也是,他在瀏覽器上感覺不到是因為,網頁就是一個直譯器,在上面會一行行跑,在後端時,回到node的環境,才回到process-thread的運作。 * 所謂的multi-thread,就是別條thread會繼續跑,然後主程序也跑,thread會在主程序跑完才接回來。 ## 一般的程式語言都不是Multi-Thread :::info ==正常的程序式語言是一行一行地走,就像人按部就班的生活一樣== 但就是有人可以在按部就班之外,還可以邊吃飯邊看電視。 ==而Node就是能邊吃飯邊看電視的程式(節省時間)== ->靠的是「分心」,並非真的同時吃飯+看電視,而是利用中間那些很微小的時間點來吃飯或看電視。 ::: 『分心』=也就是分時==Time-sharing== ==Time-sharing== ~ 利用CPU運作的單位時間一個clock做一件事,下個clock做另外一件事,高速交替之下,看起來就像是同時做兩件事情。 * Promise:保證不分心 * IPC:使process之間能夠溝通 流程: ![](https://i.imgur.com/72GdSr6.png) 而吃完飯之後... 1. 電視沒看完就算了,去唸書:原先thread不跑完,直接就往下一步了 2. 等電視看完,再去唸書:有做callback跟promise能先跑完現行程序,再往下一步 ### 大腦vsCPU  ![](https://i.imgur.com/ahx7D7e.png) ### (Node在thread的)運算模式 密集運算 ~ 只能在main thread執行,一行接一行跑for,if... 非密集運算 ~ 會去另外開一個thread來開很多thread執行(DB、Fetch...),最後再用promise把thread接回main thread ## 結論 Node就像一個正常人一樣 * 一個process,去開很多thread,去完成IO、DB等非密集運算 * 但缺點也是只使用一個process(不過大部分RD也不寫multi process) * Node比一般程式來得像正常人 * RD:Research and Development * 缺點:只能使用一個process,掛掉就真的掛掉了。(大部分RD不會寫multi-process) * (其實這不一定是缺點,因為大部分人都沒在寫) * 但node只要一找到機會就會分心,所以要用callback和promise把分心出去的thread接回原本的process。 * Node是唯一一個預設multi-thread的程式,其他的如python和java都是要自己去寫的,因此他的資源分配最好。 * Process沒有其他thread在跑的時候,就是在main-thread上跑,遇到可以分心的事,就再開一個thread,開出來的thread就需要callback-promise去接回來。 * 備註:修OS會理解Process ## 參考資料 [Node.js 開發之父:「十個Node.js 的設計錯誤」- 以及其終極解決辦法](https://m.oursky.com/node-js-%E9%96%8B%E7%99%BC%E4%B9%8B%E7%88%B6-%E5%8D%81%E5%80%8Bnode-js-%E7%9A%84%E8%A8%AD%E8%A8%88%E9%8C%AF%E8%AA%A4-%E4%BB%A5%E5%8F%8A%E5%85%B6%E7%B5%82%E6%A5%B5%E8%A7%A3%E6%B1%BA%E8%BE%A6%E6%B3%95-f0db0afb496e) [Program/Process/Thread 差異](https://medium.com/@totoroLiu/program-process-thread-%E5%B7%AE%E7%95%B0-4a360c7345e5) [為什麼 Node.js 不適合大型和商業專案?](https://yami.io/you-might-not-need-nodejs/) [Node.JS基礎教學&簡介](https://www.slideshare.net/xdxie/nodejs-15251110) --- ## JavaScript原形鍊『周杰』 ### 什麼是Class? * 定義好物件的整體結構藍圖(blue print),然後再用這個類別定義,以此來產生相同結構的多個的物件實例 * 一個類別、藍圖,在其中可以定義很多型態,如果裡面有很多相同屬性的東西,可以藉由相同的結構產生,只要有對應參數就可以產生相同的實體。 ```javascript= //藍圖 class Person { constructor(name) { this.name = name; } } //實體 const john = new Person('John'); ``` ### 為什麼React需要class? * 因為React component本身就是一個class,可以用extend去繼承component們的屬性。(class App extends React.component) * 進而去進行 setState 或 Life Cycle 的操作 ```javascript= class Articles extends PureComponent { constructor(props){ super(props) this.state = { articles: [], } } componentDidMount(){ fetch(...) } render() { const { id } = this.props.match.params; const { articles } = this.state; ... return ( <> <div ref={node => this.node = node} /> <div style={styles.postsWrapper}> {display} </div> </> ) } } ``` ### javascript跟其他語言的差異 Class vs Prototype-based * Java 中要做 Class 間的繼承,得在定義 Class 時指定要繼承的父類別。 * JavaScript 中則是以改變 Constructor 的 Prototype 來使用其他 Constructor 的 Method 。 * ES6 包裝了原本 JS 物件導向的方法 * => 讓 Consturctor 更簡潔、更易讀 * => 但本質上還是 Prototype-Based * 預設的變數宣告,都是一個物件Object(物件導向)。 * JavaScript在宣告任何事情之前,不用先宣告型態,再產生實體,因為他所有東西都是一個物件(Java則反之)。 * 如果javascript有需要的型態,再去定義。 * javascript還可以用function的方式來宣告有類別功能的物件。 ### Constructor * Constructor 是建構函式,可以用它產生擁有相同 Properties 的 Object (Instance) * 本身其實是一個四不像的函式,讓Java的人能用class去理解。 * 在上面定義屬性,方法,在下面產生實體時,就跟class 87%像,後來變class只是為了讓他與Java更像。 * 但產生不同實體的時候,雖然做一模一樣的事情也產生了佔用的空間,這點就不像class了。 * 而ProtoType就解決了上述問題 ```javascript=1 // constructor function Person(name, age) { // properties this.name = name; this.age = age; // methods Person.prototype.log = function(){ console.log(this.name+', age: ' +this.age); } } // Instance var nick = new Person('nick',18); nick.log();// nick, age: 18 var peter = new Person('peter', 20); peter.log(); // peter, age:20 ``` :::info 但 console.log(nick.log === peter.log) // false 雖然 nick 的 log 這個 function 跟 peter 的 log 這個 function 是在做同一件事,但其實還是佔用了兩份空間,意思就是他們其實是兩個不同的 function。 ::: ### Prototype 為了解決上述空間浪費問題 * class其實有一個類別是prototype * 最大的差別是產生不同的實體的時候,會產生出同一個功能=>指向同一個method和空間 * prototype裡面的東西也都可以直接改,就用Person.prototype.log = function( ) { } * (JS產生新類別時,會使用new) ```javascript= function Person(name, age) { this.name = name; this.age = age; } Person.prototype.log = function () { console.log(this.name + ', age:' + this.age); } var nick = new Person('nick', 18); var peter = new Person('peter', 20); console.log(nick.log === peter.log) // true // 功能依舊跟之前一樣 nick.log(); // nick, age:18 peter.log(); // peter, age:20 ``` #### 實體(Instance)怎麼連接prototype? * 依靠每個物件裡都會有的 __proto__ * Instance.method->Instance->Class type->Class.prototype #### _proto_ * _proto_裡會寫此實體可以使用的函式 * 函式的內容即是類別的prototype所提供 * Javascript的任何class的prototype中都會有__proto__這個物件,會指向上層class的__proto__。每一層的__proto__,往上指上去,最後上層是「Object」,但一般來說不會動到Object。 Point() -> Constructor redPoint -> Instance ![](https://i.imgur.com/CfbDnWb.png) ![](https://i.imgur.com/fqt1nP4.png) [JS原型繼承](https://codingwife.com/2018/08/01/javascript/prototype/) #### 原型鍊 * __proto__ 不斷串起來的鍊,就叫做原型鍊。透過這一條原型鍊,就可以達成類似繼承的功能 * 『繼承(inherit)』 * prototype要用的method可以自己去定義 * (子物件.__proto__) -> ... -> (父物件.__proto__) -> (Object.__proto__)-> null * .prototype -> _proto__ -> 每個物件都有的實體,會指向上一層的prototype * 上一層的prototype跟這一層的__proto__是等價的 * JS裡全部都是object,所以他最上層一定是object ==物件導向:這不一定是我的終點,看你要不要再往下定義== ![](https://i.imgur.com/9UeNsJ3.png) ### Example ```javascript=1 class Person { constructor(name,age){ this.name = name; this.age = age; } log() { console.log(this.name + ', age: '+ this.age); } } var nick = new Person('nick',18); nick.log(); //nick, age: 18 console.log(nick.__proto__); //================================================== class GoodPerson extends Person { constructor(name,age) { super(name,age);// 必須回傳資料給上一層的建構子 } log(){ console.log(this.name + ' is a good person, age: ' + this.age); } } var parker = new GoodPerson('Parker',21); parker.log(); //Parker is a good person, age 21 ``` --- ## Promise『周杰』 :::info Promise就是一個Class,是一個表示非同步運算的最終完成或失敗的物件 基本上,一個 Promise 是一個根據附加給他的 Callback 回傳的物件,以取代傳遞 Callback 到這個函數,因而讓它更易讀。 ==大原則:今天確定做完某件事情,才去下一件事情== 一般的JS會不理你直接跑完,加入promise之後,就會等中間的thread都跑完。 ::: console.dir(Promise); 可以看到他的__proto__有then跟catch,就是為什麼我們可以用。 ### Callback 當你要做的事情是要等待一段時間的時候,會跟你的執行順序一樣。 但是你要callback一堆的時候就會很靠北,會造成波動拳 ![](https://i.imgur.com/fdvhKAy.jpg) 因此要使用Promise來解決這個狀況: ### Promise * 承諾,說我“會”做完一件事 * 吃一個function( resolve, reject ){ 定義成功與失敗時要做的事 } * new Promise(function(resolve, reject){ ... }); * 基本上,每個 Promise 代表著鏈中另外一個非同步函數的完成。 Promise 完整範例 ```javascript=1 //Promise語法 const promise = new Promise(function(resolve,reject){ if(true) { //成功時,呼叫reslove,並帶入回傳值 resolve(value); } //失敗時,呼叫reject,並帶入失敗原因 reject(reason); }); promise.then((result) => { console.log('result' + reuslt); //成功:這件事做完後要繼續做的事 //已知可預期的錯誤也能當結果的一種傳入 }).catch((error) => { console.log(error); //不可預期錯誤要回傳的值 }); ``` #### Promise有三個型態 * 未解 pending * 尚未執行Promise時 * 在這執行建構式給定的函式式後,才會執行promise的結果 * 已解,完成(settled, fullfilled) * 已解,失敗(settled, rejected) #### Promise.then(結果處理) Promise物件呼叫".then"函式後,並以result接resolve的值,作為最終處理結果 ```javascript= promise.then( (result) => { console.log('Promise success, result = '+ result); //成功後,結果處理 -> }, (reason) => { console.log('Promise failed, reason = ' + reason) }); //失敗後,告知原因 ``` 以Ajax呼叫後端API為例,通常會在then裡處理後端資料 ---- :::info ==一直.then???== 複數 Callback 可以透過重複呼叫 .then 達成。 .then(d => d.json()) .then(res => console.log(res)) 下一個then得到的參數,是上一個then回傳的東西。 ::: ```javascript= doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback); //有了新方法,我們附加 Callback 到回傳的 Promise 上,來製造 Promise 鏈: doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('Got the final result: ' + finalResult); }) .catch(failureCallback); ``` #### Promise.catch(錯誤處理) ```javascript= //.catch其實是.then的除錯語法糖 promise.then( undefined, (error) => { console.log(error); }); //用.catch更易讀 promise.catch((error) => { console.log(error); }); ``` 失敗後的串接也是可行的,也就是說 catch 會非常好用,即使鏈中出錯。看看這個範例: ```javascript= new Promise((resolve, reject) => { console.log('Initial'); resolve(); }) .then(() => { throw new Error('Something failed'); console.log('Do this'); }) .catch(() => { console.log('Do that'); }) .then(() => { console.log('Do this whatever happened before'); }); //Initial //Do that //注意「Do this」沒有被輸出,因為「Something failed」錯誤導致拒絕。 //Do this whatever happened before ``` #### 同時處理結果和錯誤 .then和.catch都會回傳new Promise物件 而.then沒給的第二個參數(處理reason),由.catch處理 ```javascript= promise .then((result) => {console.log('result', + result);}); .catch((error) => {console.log(error);}); ``` #### async function??? Promise??? async function是用Promise來實作的,他其實只是更易讀Promise --- #### Promise.all(&&的概念) ->可以把想要一起等的東西都打成一包,等那包東西都做完,再往後跑。 接API的時候,就不可能還沒接完就跑下面的東西,這時就會使用它。 `Promsie.all` 把一堆promise的包在同一綑,等==所有人都==做完 ```javascript=1 Promise.all( [showMsg('Hello',1000), showMsg('Bye',3000),/*rejectFunc*/]) .then( console.log(); ); ``` #### Promise.race -> 比誰先出來,一樣同時抓很多資料,但適用於你抓到一比就不用往後面抓時使用 `Promise.race` 可以一堆promise的包在同一綑,==只等最快的人==做完 #### QUIZ ```javascript= let done; const async = new Promise((resolve, reject)=> { this.done = resolve; return; }); async.then((result)=> { console.log('Good'); }).catch((error)=>{ console.log('Bad'); }); this.done(); ``` :::success 這邊混和了箭頭函式this綁定的特性,我們在Promise建構式中傳入了一個箭頭函式,並且在之中將Promise傳入的resolve函式指定給this的done變數,利用箭頭函式this綁定的特性,就可以在Promise外的地方,也就是程式碼最後一行呼叫done來呼叫Promise所傳入的resolve函式,完成異步運算 ::: ```javascript= async1() .then(() => async2()) .then(() => async3()) .catch((err) => errorHandler1()) .then(() => async4(), (err) => errorHandler2()) .catch((err) => console.log('Don\'t worry about it')) .then(() => console.log('All done!')) // 先執行async1(),若成功則接著執行async2(),失敗則不處理。 // async2()執行成功則接著執行async3(),若失敗則傳入err執行errorHandler1()。 // async3()執行成功會接著執行async4(),若執行async4()失敗則傳入err執行errorHandler2(),若在執行async3()階段就失敗則傳入err並執行console.log('Don\'t worry about it')。 // 最後執行console.log('All done!'))。 ``` ![](https://i.imgur.com/jZ89rHc.png) 參考資料: [MDN-使用Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises)