[Nodejs]關於、介紹和筆記

我在2015年7月份的時候,開始接觸了nodejs這一個語言,嚴格來說nodejs也不算是一種語言,因為他所使用的語言是大家更為熟悉的javascript,一個以往用於前台設計卻一直都無法崛起的語言。

小弟我對C語言及C#語言也略有研究,以下如果無法簡單解釋可能會套用一些觀念進來解釋。

本來在這半年多以來,寫過的東西不算多,但也是終於看出了些端倪,以下介紹一些所發現的重點,如果有錯誤還請包含,另外也請各位來指正小弟,互相交流。


環境:Linux/Windows
版本:v4.X LTE

用途

爬過一些文章寫說nodejs是寫web用的...的確,nodejs在web上絕對是一個利器,但也不能以偏概全的說他就是寫web的,對於網頁設計的工程師來說,轉到後台用nodejs可以說是比起其他語言簡單很多,還可以提供比.Net、IIS、apache更多的後台支援。

但其實他可以實際上運用的範圍絕對多過於此,就連單晶片的韌體,也可以用nodejs來寫,效能也會比用C語言寫來的強(下面會解釋),除非你對C語言的架構熟之又熟,不然基本上nodejs怎麼寫都比C強。

單執行緒(Single Thread)
首先關於nodejs,他是單執行緒的程式語言,這是大家聽到耳朵爛掉,但一直不知道到底是在講什麼鬼東西的重點。

參考:http://fred-zone.blogspot.tw/2015/08/nodejs-single-thread.html
此篇文章以一個更人性化的方法告訴大家他的效能優點,有興趣也可以從此作者的部落個去做延伸閱讀。

首先要先跟大家談什麼是執行緒,在其他語言裡面你會查到的關鍵字就是thread、pthread。也就是會產生一個新的process去執行你所指定的程式碼。最直接的想法就是,你請了另外一個人去執行那項任務,而原本的人就可以做自己的事情,而不被這項任務絆住。

而在nodejs中,顧名思義單執行緒就是從到尾只會有一個人在做這些任務,只是把他分成function(函式)執行,同一個時間中只會執行一個function,並非任務。

當你呼叫了function時,會先將其丟入task queue裡頭,然後由nodejs中執行task的機制去把queue裡的任務執行完。

function A(){
console.log("A");
B();
}

function B(){
console.log("B");
}

function C(){
console.log("C");
}

function main(){
setTimeout(A,0);
setTimeout(C,0);
}

main();

以上程式碼,從main開始看,A先執行 // queue = [A]
main還沒執行結束,繼續執行了C // queue = [A,C]
main執行結束,繼續尋找下一個task,執行A // queue = [C]
A呼叫了B // queue = [C,B]
A等待B結束,繼續尋找下一個task // queue = [B]
C執行結束,繼續尋找下一個task // queue = []
B執行結束,回到A,A執行結束 // queue = []

這樣看起來就像是thread去做了A跟C,同時做了A跟C兩個function,但實際上卻是同一個人在做這兩件事情。根據上面的參考連結解釋,會發現如果這樣的執行順序,這個方法不會產生閒置狀態,而是繼續執行下一個task,相較於以往的thread,如果中間delay了,就會造成整個thread佔住資源只為了睡給你看。

淺談之後,重點就來了,那麼這兩個分別應該應用於哪裡呢?就像Fred Chien最後說的,如果你是開酒店或按摩店,一百個座位就需要請一百個員工了。

此話用意在於這些員工的工作內容是什麼,如果只是單純的餐廳那樣點餐送餐收盤子,那麼一個人絕對有辦法負責更多的坐位,但若是要一直服務同一樣事情而抽不開身做其他事情,就會造成程式流程卡卡的,因為他總是只能找到一點點鎖碎的時間去幫你做其他的task。

變數型態(variable type)

如果你是從其他語言過來學習的,一定會遇到一個可愛又可恨的問題是,我TMD怎麼知道這個變數的型態是什麼...能不能用?有沒有問題?
我都跟我的同事解釋說,你怎麼知道你兒子未來要做什麼?但你會知道他的成長歷程,你可以知道他現在是什麼,最重要的是起碼你知道他是你兒子。你可以阻止他成為你不想要他成為的人,就算管不住了,你也可以去判定他適不適合做你要他做的事情。
可愛的點在於,你並不需要去特別宣告他的變數型態,因為變數本身並不會存在固有的型態,而是根據你給予的值去決定現在的型態。
當然你也可以透過typeof (variable)去找出他現在的型態為何,除了string,number,boolean外你可以簡單的判定出來,但若今天要判定的是class名稱,你所得到的卻永遠只會是object。
更好的判定方法使用:
Object.prototype.toString.call(variable)
此種方法會回傳一個字串,藉由字串去判定此類別(class)為何

就C語言觀念來講他就像是一個 (void *)的變數,他從來就指紀錄位置,你也只是透過他從位置上取得值,他也不會管你的拿到的值是什麼,只負責將位置放在變數中給你存取。

特別注意的是,array並不是array,而是由object所定義出來的物件,所以當你使用typeof時,他所給予的會是"object"。

等於與全等於( == vs ===)

程式語言中首先你必須要知道,一個等於和兩個等於的差別,而javascript還存在了三個等於。

一個等於:=
是賦予的意思,例如很簡單的var x = 1,宣告了一個x變數並賦予1值給x。
兩個等於:==
是等於的意思,回傳的是boolean(true, false),特別注意的是,在nodejs中,兩個等於在比較的時候,會將比較者強制轉型和被比較者同型態。
三個等於:===
是全等於的意思,會將兩個變數最原始的型態去做比對,以C語言角度來說,他們是去比對變數中所儲存的記憶體位置是否相等。

var str = "1"; // 賦予
var num = 1; // 賦予
str == num // true, num被轉型為字串
str === num // false, str指向字串"1",num指向數字1

未定義變數 (undefined, null, define)

undefined代表的是沒有被定義,即為沒有被宣告過的意思,反之就是define,值得注意的是,若是物件中的變數成員沒有被定義,程式碼即會回傳undefined,若是直接取用變數則會丟出一個例外錯誤告訴你此變數並未被定義過。
如果沒有跳出來,你又沒有定義過此變數,那麼客官你麻煩大了,下面的變數作用域會提到,這個變數已經在某個script中被指定為全域變數。

null代表的是沒有資料,如果使用兩個等於去判定時,會回傳true,都是指沒有值的意思。

另外,在判斷式的時候,如果遇到undefined,null,""(empty string),0(number zero)時,會直接判定成false。若是定義過的變數則為true。

var a = {};
console.log(a ? "exists" : "not exists"); // "exists"
var a = "";
console.log(a ? a : "empty string"); // "empty string"
var a = 0;
console.log(a ? "number is larger then 0" : "number is zero"); // "number is zero"

基於此機制,建議如果在判定是否有值又是在比較尷尬的變數型態時,還是把判斷式寫的完整些。

var a = 0;
console.log(typeof a === "number"  ? "exists" : "not exists"); // "exists"

變數作用域(scope)

首先要提到最會出問題也是我看了很多新手程式碼很想罵髒話的全域變數問題,全域變數的確很好用,就像明星一樣每個都可以看的到他,今天大家都用到了同一個變數名稱時,所代表的值到底是誰的,會造成混淆,嚴重點如果是其他script所宣告的變數,你根本無從trace,也改不得,這隻小蟲(bug)就會成為你致命的噩夢。

所以,最重要的就是不要濫用全域變數,如果程式流程是對的,基本上我還不相信會出現一個會需要到讓大家都看的到變數,除非是常數例如:pi = 3.14

宣告成全域變數的方法很簡單也很危險,一般我們宣告變數時使用的是var x;,若是要將他變成全域變數只需要將var拿掉即可。所以如果你發現一個完全沒有宣告的變數卻發現他有值,可能要注意一下是不是哪裡賦予值的時候忘記宣告他,導致nodejs以為你要將他變成全域變數了。

作用域的判斷通常我都是直接看離這個變數最近的外層括弧{},如果你有對程式碼排版的習慣可以很明確的看出來,在宣告變數的地方同level又離他最近的括弧,就是他的作用域,以那個level之下的作用域都可以存取到此變數,這個方法同樣可以用在判斷this這個變數所指的物件是什麼。

指標&值

不管在什麼語言裡面我們都會面臨到一個問題,就是我們現在使用的是變數,還是指標變數。這關係到傳入參數時,所代表的意義和可以動用的範圍。
如果是純值(boolean,string,number),就會被當成值做複製(copy)賦予,如果是object型態就會是將變數位置當成值作為賦予,而被賦予值的變數則會到相同位置去尋找變數值做使用。

範例:

var people1 = {
    "name":"Shach",
    "ages":18
};

var ages = people1.ages;
ages = ages + 1;
console.log(people1); // {"name":"Shach", "ages":18 }
console.log(ages); // 19
var people2 = people1;
people2.name = "Shach Kuo";
console.log(people1); // {"name":"Shach Kuo", "ages":18 }

延伸:
如果變數儲存的是位置而不是純值,則如果重新賦予,會重新指向位置,而不是取代原本的值

console.log(people2); // {"name":"Shach Kuo", "ages":18 }
people2 = {
    "str":"Hello World"
};

console.log(people2); // {"str":"Hello World"}
console.log(people1); // {"name":"Shach Kuo", "ages":18 }

參考:
JavaScript教學
2016/03/14:先寫到這邊,之後有想到什麼再繼續補充

沒有留言:

張貼留言