nodejs基礎知識查缺補漏

1. 單線程、異步I/O、對比phpphp

  nodejs是單線程的,可是是異步I/O,對於高併發時,它也可以快速的處理請求,100萬個請求也能夠承擔,可是缺點是很是的耗內存,可是咱們能夠加大內存, 因此能用錢解決的事就不是事。 而PHP語言是使用Apache服務器的,它的方法和node是徹底不同的,雖然php也是單線程的,可是apache是支持多線程的,它的方法是派出150個線程放在線程池中,而後對於須要的程序來從線程池中取得線程,用完了以後再放回去。 顯然,php作的後臺在速度上是不如node的(通常狀況下是這樣),由於node是支持高併發的。html

  不只如此, nodejs的相較於php開發效率很是高。 這並非說它代碼寫的少,而是他在優化方面作得少一些就能夠達到很好的效率,而php就要作大量的優化。node

  nodejs應用場景普遍, 可是目前在xp系統的某些應用還不能支持。mysql

  缺點是nodejs是很是新的, 因此可能在人才方面不夠充足,不少公司不會輕易使用node, 而php有不少成熟的中間件,大量的從業人員,因此就目前來講,php更加穩定一些。且node的IDE並吧完善。react

 

2. 框架es6

  目前用的比較多的就是express,更穩定一些。sql

  而koa也是一個node框架,可是這個框架比較新,還不夠穩定,而且它是徹底基於es6的。express

  Hapi框架比較適合大型網站,入門較難。apache

  sails框架也不錯,是基於express的。npm

 

3. 咱們使用下面的代碼建立服務器時,會發現每次訪問了一次,卻console.log兩次,以下所示:

var http = require("http");
http.createServer(function (req, res) {
            res.writeHead(200, {"Content-Type": "text/html"});
            console.log("訪問");
            res.write("Hello world!");
            res.end();
}).listen(8081);

console.log("Server running at http:127.0.0.1:8081");

注意1: 其中的end()表示response結束,若是不添加,就會發現瀏覽器頁面上會一直轉圈,這其實是由於沒有end因此服務器一直在等待接收資源,正如咱們打開一個資源較多的網站,若是資源沒有加載完成,那麼就一直會轉圈。。。另外,在res.end()中能夠輸出一些內容到頁面上,也能夠不輸出。

注意2: 咱們能夠console.log("訪問")應該在dos只有一次,可是每當咱們刷新的時候就會發現同時出現了兩次,這實際上能夠認爲是nodejs的bug,咱們須要經過添加一句話來解決這個問題(在express框架中的底層中已經解決了這個問題,可是咱們使用原生nodejs的時候仍是須要注意的)。

var http = require("http");
http.createServer(function (req, res) {
            res.writeHead(200, {"Content-Type": "text/html"});
            if (req.url !== "/favicon.ico") { // 清除第二次訪問
                console.log("訪問");
                res.write("Hello world!");
          res.end(); } }).listen(
8081); console.log("Server running at http:127.0.0.1:8081");

如此,就能夠解決問題了。(注意:必定是req.url !== "/favicon.ico")

 

 

4. 模塊調用(類的繼承)

  下面咱們解決這樣一個問題,在當前目錄創建一個server.js做爲node後臺,而後在當前目錄下再建立一個modules目錄,在modules目錄下面建立三個模塊: User、Teacher、Student。其中後二者繼承前者的。前者包含最基本的id、name和age,以及一個enter方法,表示進入,在後臺中打印出來。 Teacher在繼承了User的基礎上,又有本身的方法teach。 一樣,Student在繼承了User的基礎上,也有本身的方法teach。

  因爲ji是弱類型的,因此繼承相對寬鬆一些,不像C++同樣,有公有的、私有的、受保護的等等。 總體的架構以下:

 

 其中User.js內容以下:

function User(id, name, age) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.enter = function () {
        console.log(this.name + "進入系統");
    }
}
module.exports = User;

即這是一個構造函數, 而後由於就一個,因此直接使用module.exports = User;便可。

 

其中Teacher.js代碼以下所示:

var User = require("./User.js");

function Teacher(id, name, age) {
    User.apply(this, [id, name, age]);
    this.teach = function (res) {
        res.write(this.name + "正在上課");
    }
}
module.exports = Teacher;

能夠看到,這裏咱們使用借用構造函數的方法,這與原型繼承是不一樣的,另外,我給Teacher類又添加了一個本身的方法teach,並須要調用參數res。 

 

相似的,Student.js代碼以下所示:

var User = require("./User");

function Student(id, name, age) {
    User.apply(this, [id, name, age]);
    this.learn = function (res) {
        res.write(this.name + "正在學習");
    }
}

module.exports = Student;

 

server.js以下所示:

var http = require("http");
var User = require("./modules/User");
var Teacher = require("./modules/Teacher");
var Student = require("./modules/Student");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content-Type": "text/html; charset=utf8"});
    if (req.url !== "/favicon.ico") {
        var student = new Student(1, "John Zhu", 21);
        console.log(student.enter());
        student.learn(res);
        res.end();
    }
}).listen(8000);

console.log("Server is running at http://127.0.0.1:8000");

即將這幾個模塊都引入了進來,而後經過初始化構造函數建立實例, 最後調用了一些方法。 

這個例子就展現了類與繼承、模塊引入等方法。

 

5. nodejs路由

這是一個重要的概念,以前一直都沒有很好的理解,因此這裏着重整理。

其實很簡單,就是輸入 http://127.0.0.1:8000/login, 那麼服務端就返回與login相關的respond。  方法就是經過在server.js中拿到url,在拿到login,而後找到對應的js文件,拿到對應的方法,再將相應的文件顯示到頁面上便可。

話很少說,舉例以下:

router.js以下:

module.exports = {
    login: function (req, res) {
        res.write("進入login頁面");
    },
    register: function (req, res) {
        res.write("進入register頁面");
    }
};

server.js以下:

var http = require("http");
var url = require("url");
var router = require("./router");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content-Type": "text/html; charset=utf8"});
    if (req.url !== "/favicon.ico") {
        var path = url.parse(req.url).pathname;
        path = path.replace(/\//, "");
        router[path](req, res);
        res.end();
    }
}).listen(8888);

console.log("Server is running at http://127.0.0.1:8888");

 

即引入路由模塊,而後使用url模塊中的parser方法解析url獲得用戶輸入的path,並使用正則替換沒必要要的/,最後再利用js中的[]來調用方法,達到路由的目的。

 

 

 

6. nodejs文件讀取

nodejs中文件讀取有兩種,一種是同步,一種是異步,同步是指讀完以後再作下一件事情,異步是不等讀完就作下一件事情,相似於又開了一個線程。

同步執行很是簡單,就是執行完一個執行另外一個,下面主要看同步會出現的問題,以下:

server.js以下:

var http = require("http");
var url = require("url");
var optfile = require("./modules/optfile");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content": "utf8; charset=utf8"});
    if (req.url !== "/favicon.ico") {
        function recall(data) {
            res.write(data);
            res.end();
        }
        optfile.readfile("./view/login.html", recall);
        console.log("主程序執行完畢");
    }
}).listen(8888);

console.log("server is running at 127.0.0.1:8888");

 

而後optfile.js以下:

var fs = require("fs");

module.exports = {
    readfile: function (path, recall) {
        fs.readFile(path, function (err, data) {
            if (err) {
                console.log(err);
            } else {
                recall(data);
            }
        });
        console.log("異步方法執行完畢!");
    },
    readfileSync: function (path) {
        var data = fs.readFileSync(path,"utf-8");
        console.log(data.toString());
        console.log("同步方法執行完畢");
    }
};

 

login.html以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <p>login.html</p>
</body>
</html>

 

最終運行輸出以下:

爲何"server is running at 127.0.0.1:8888"老是最早打出來?這是由於在node中全部的api都是異步的,因此http.createServer也是異步的,因而不等執行完, 就跳過去執行console.log("server is running at 127.0.0.1:8888");了.

 

另外,爲何異步方法執行完畢在主程序執行完畢以前呢? 

由於雖然readFile是異步的,可是咱們本身建立的readfileSync確是同步的,只有在console.log("異步。。")執行完畢,這個同步的才執行完畢,因此console.log('同步。。')纔會以後輸出。

 

問題: 爲何必定要使用閉包呢? 若是我不傳入recall,而只是將res.write()和res.end()下載optfile.js中else下面可行嗎?

不可行!!!

由於進入readfile(這是同步的)以後,readFile是一個異步的,因此當即執行了console.log("異步。。")。而後棧退回到了http.createServer,這是這個函數也當即執行完畢,因此res做爲局部變量就當即被釋放了,而這是處在另外一個線程上讀完文件後,可能再用res,就已經被銷燬了,而不能找到。

可是使用了閉包以後就不同了,使用了閉包,就會對res有一個引用,即便是在函數執行完以後,也不會釋放res。 

可我直接將res.write()和res.end()放在optfile.js中else下面時,也是在一個函數裏啊,而後保存了對外層函數中的引用,這難道就不是閉包嗎???  

這裏咱們能夠先粗暴的理解,閉包必須是同步的,而不能是異步的

 

 

7. 讀取並顯示圖片

var http = require("http");
var fs = require("fs");

http.createServer(function (req, res) {
    res.writeHead(200, {"Content-Type": "image/jpeg"});
    if (req.url !== "/favicon.ico") {
        fs.readFile("./pig.jpg", "binary", function (err, data) {
            if (err) {
                console.log(err);
            } else {
                res.write(data, "binary");
                res.end();
            }
        });
    }
}).listen(8888);

console.log("Server is running at http://127.0.0.1:8888");

 

這裏區別並不大,以前使用的是text/plain,而這裏使用的是image/jpeg,雖然,最終咱們讀取的是jpg圖片,可是即便咱們讀取png圖片,最後也是能夠的。

注意,在readFile中的第二個參數中,是接受編碼的類型,普通的咱們也能夠寫成"utf-8", 當是讀取圖片時 ,就要寫成"binary"。

通過嘗試,咱們能夠發現Content-Type的值也能夠是image/png或者是image/jpg都是能夠的。可是若是換成其餘的就會出問題了。

另外,若是除了圖片,還輸出了文字,就會報錯,下面就會講述若是圖文混排。

 

 

8. nodejs異常處理

即若是咱們的路由出現問題時,後臺就會崩潰,這就是異常。 

處理異常的方式也很簡單。

對於同步的方法就是使用try-catch語句,把有可能出錯的代碼放在try中,而後catch到錯誤後處理錯誤。也能夠根據狀況,在try失敗時,throw出來,而後再catch。

而對於異步方法能夠直接在自身的err下處理異常就能夠了。 即對於可能使得後臺崩潰的地方使用異常處理。

 

9. 異步流程控制

第一步:讀文件, 第二步: 寫入磁盤, 第三步: 入庫。

在其餘的後臺語言中,這三步是一個執行完成另一個再執行,可是在node中,因爲默認全部的api都是異步的,因此這三個步驟是一塊兒併發運行的。可是咱們須要的是第一步完成後才能實用該結果運行第二步,第二步完成以後才能實用結果完成第三步。。。。

那麼這個問題怎麼解決呢?  在node中,每每最後一個參數是一個回調函數,因此能夠在作完第一步以後再將第二步做爲一個回調函數去執行,而後第二步執行完成以後,去完成做爲第二步的回調函數的第三步。。。。這就是所謂了回調地獄了。

這只是三個步驟,若是去寫嵌套,就會發現十分的不友好, 因此咱們須要的是有一種處理機制來解決這個問題。

 

首先,咱們分析一個多個函數運行可能會出現的狀況:

  • 串行無關聯 --- 即一個執行完成以後再執行另外一個,可是這幾個之間的執行結果都不會被下一個利用。 
  • 並行無關聯 --- 即全部的函數並行執行,誰先執行完是不知道的,關鍵是誰先執行完都對結果沒有影響。
  • 串行有關聯 --- 即咱們再上面的問題中所須要的一種執行模式。 一個執行完,才能執行下一個,而且這個執行完的結果是做爲下一個函數執行所須要的。
  • 並行限制 --- 即不少函數並行執行,可是咱們能夠限制每次並行執行的數量, 如限制每次只有2個函數能夠並行執行。

前面三個是比較重要的,因此咱們先學習前面三個,就要用到nodejs中的async。 可是這個不是node核心所包含的。因此咱們須要在一個文件中引入async, 即:

npm install async --save-dev

 

 可是不知是什麼問題。。。 不能安裝成功,因此仍是使用淘寶的鏡像吧。。。

cnpm install async --save-dev

 

安裝完成以後,就能夠發現package.json中已經包含了這個依賴項:

"devDependencies": {
    "async": "^2.3.0"
  }

 

 

咱們首先看一下兩個函數並行執行的狀況:

function oneFun() {
    ii = 0;
    setInterval(function () {
        ii++;
        if (ii == 3) {
            clearInterval(this);
        }
        console.log("aaa" + new Date());
    });
    console.log("oneFun");
}
function twoFun() {
    jj = 0;
    setInterval(function () {
        jj++;
        if (jj == 3) {
            clearInterval(this);
        }
        console.log("bbb" + new Date());
    });
    console.log("twoFun");
}
oneFun();
twoFun();
console.log("執行完畢");

 

輸出結果以下:

oneFun
twoFun
執行完畢
aaaSat Apr 15 2017 11:21:40 GMT+0800 (中國標準時間)
bbbSat Apr 15 2017 11:21:40 GMT+0800 (中國標準時間)
aaaSat Apr 15 2017 11:21:40 GMT+0800 (中國標準時間)
bbbSat Apr 15 2017 11:21:40 GMT+0800 (中國標準時間)
aaaSat Apr 15 2017 11:21:40 GMT+0800 (中國標準時間)
bbbSat Apr 15 2017 11:21:40 GMT+0800 (中國標準時間)

 

因爲setInterval()是異步的,因此oneFun和twoFun很快就執行結束,而後因爲兩個setInterval異步,因此他們併發執行,能夠看到他們是接替執行的,aaa、bbb、aaa、bbb、aaa、bbb。。。

注意:這裏使用clearInterval(this);就能夠清楚當前的計時器。

 

可是,若是咱們但願在oneFun執行完了以後再執行twoFun呢? 因爲setTimeout()是異步的,通常方法咱們就只能用下面的方法來實現,以下:

function oneFun() {
    ii = 0;
    setInterval(function () {
        ii++;
        if (ii == 3) {
            clearInterval(this);
            twoFun();
        }
        console.log("aaa" + new Date());
    });
    console.log("oneFun");
}
function twoFun() {
    jj = 0;
    setInterval(function () {
        jj++;
        if (jj == 3) {
            clearInterval(this);
        }
        console.log("bbb" + new Date());
    });
    console.log("twoFun");
}
oneFun();
console.log("執行完畢");

 

 可是不難理解,這樣的作法會很難管理,而且這還只是兩個異步的控制,若是有多個,簡直就會瘋掉~

 

因而async就能夠用獲得了。。。

首先咱們使用async完成下面的串行無關聯,利用async.series。 

var async = require("async");

function exec() {
    async.series(
        {
            one: function (done) {
                ii = 0;
                setInterval(function () {
                    ii++;
                    console.log("aaa" + new Date());
                    if (ii == 3) {
                        clearInterval(this);
                        done(null, "onefun執行完畢");
                    }
                });
            },
            two: function (done) {
                jj = 0;
                setInterval(function () {
                    jj++;
                    console.log("bbb" + new Date());
                    if (jj == 3) {
                        clearInterval(this);
                        done(null, "onefun執行完畢");
                    }
                });
            },
            three: function (done) {
                done(null, "全部程序執行完畢");
            }
        },function (err, rs) {
            console.log(err);
            console.log(rs);
        }
    );
}
exec();

 

即實現將async模塊引入,而後利用async.series()方法,即串行無關聯,這個方法接受兩個參數,第一個參數是一個對象,對象中能夠定義咱們但願串行執行的方法,而後第二個參數是一個回調函數,這個回調函數要在第一個參數的方法中做爲回調。 指的注意的是: 這裏只有第一個參數的每個方法直到執行完了done();而且done()的第一個參數是null(即沒有出錯),纔會執行下一個方法, 這樣就能夠實現串行無關聯了。

結果以下所示:
 

能夠看到aaa所有打印出來以後,纔打印的bbb,而後最後輸出了一個對象說明了執行狀況。 

注意: 前面的one和two中的兩個done是用來告知下一個方法:我執行完了,你能夠執行了。 而最後一個done纔是用來提示的,若是最後一個done沒有回調,固然也能夠串行執行,由於他是最後一個了,可是就不會返回這個對象了。

 

下面再介紹一下並行無關聯,很簡單,只須要將上面的async修改成parallel便可。最後的輸出結果以下:

能夠看到,a和b顯然是並行執行的,從最後一句能夠看到three是最早完成的,由於並行狀況下執行時間最短的最早完成。  

注意: 這裏因爲是並行執行,因此done()的做用就不是控制後續進程的進行了,而只是說明是否執行了。

 

最後,咱們介紹一個 串行有關聯 的實現, 即幾個異步的函數,咱們讓他們串行執行,而且在一個執行完了以後將結果傳輸給下一個要執行的函數利用,以下所示:

var async = require("async");

function exec() {
    async.waterfall(
        [
            function (done) {
                ii = 0;
                setInterval(function () {
                    ii++;
                    console.log("aaa" + new Date());
                    if (ii == 3) {
                        clearInterval(this);
                        done(null, "第一個函數向下傳遞值");
                    }
                });
            },
            function (preValue ,done) {
                jj = 0;
                setInterval(function () {
                    jj++;
                    console.log("bbb"+ "後面是接受的第一個函數的值 " + preValue);
                    if (jj == 3) {
                        clearInterval(this);
                        done(null, "第二個函數向下傳遞值");
                    }
                });
            },
            function (preValue,done) {
                console.log("後面是第二個函數給我傳遞的值:" + preValue);
                done(null, "全部程序執行完畢");
            }
        ],function (err, rs) {
            console.log(err);
            console.log(rs);
        }
    );
}
exec();

 

注意點1:使用async.waterfall方法實現串行有關聯

注意點2: 該方法的第一個參數是一個數組, 第二個參數是一個回調函數,沒有變化。 因爲這是數組,因此就沒有鍵的概念了,故每個元素都是一個函數。

注意點3: 因爲這是串行有關聯的,因此咱們能夠經過function的第一個參數來接收上一個函數執行完以後返回的結果。 

 

 

 

10. 鏈接MySQL

鏈接MySQL的方法有兩種,一種是直接鏈接,另外一種是使用鏈接池鏈接。  直接鏈接更簡單一些,因此咱們先學習直接鏈接。

直接鏈接MySQL

首先使用node安裝在當前文件夾下,以下(使用npm安裝會出現問題,因此使用cnpm):

cnpm install mysql

 

 

11. Buffer

在網絡層對於不一樣的文件都是使用二進制交互的,而js自己不具備能力,因此在node中引入了Buffer用於引入。 對於Buffer,咱們能夠在命令行中查看。

直接輸入Buffer,回車,能夠看到Buffer是一個對象, 它也是一個構造函數: 以下:

 

咱們能夠new 一個Buffer,而且傳入一個字符串,存入Buffer時,默認的編碼格式是uft-8, 以下:

 

而後,咱們能夠指定其具體的編碼格式,如base64, 以下:

 

咱們還能夠指定其長度,而後寫入:

能夠看到咱們已經定義了buf的長度爲7, 即便寫的超過了7, 最後也只會留下7個,其餘的被忽略掉。

 

咱們可使用Buffer.isBuffer()來判斷是不是Buffer,以下:

 

 

Buffer寫入,若是咱們直接使用write()方法寫入,即只接受一個字符串,那麼會覆蓋以前的, 以下所示:

 

 

可是write()方法還能夠接受第二個參數,即偏移量,即從哪裏開始寫,以下所示:

 

從這裏能夠看出, 偏移量是指從0開始計算的。 而且一旦開始new的時候傳入了字符串,就肯定了buf的長度,後面即便是再寫入,也不能超過原來的長度。

 

copy()方法以下所示:

即第二個參數指定開始寫的位置,第三個參數指定複製的起始位置,第四個參數指定賦值的結束位置。

 

 

 

重要

  每次咱們再修改了js內容時,都須要從新啓動服務器,這是很是麻煩的,因此咱們可使用相似react中的熱加載,即安裝supervisor,以下:

npm install supervisor --save

  而後就能夠全局使用了, 接着建立一個node文件,原來咱們是經過 node <文件名> 方式來執行的,如今,咱們使用 supervisor --harmony <文件名> 的方式就能執行,而且只要咱們改了js,而且保存,這時再刷新頁面就會發現已經改變, 而不須要再次啓動node服務器。

 

 

 

 

 

 

 

 

 

 

 

 

視頻資源:http://study.163.com/course/courseLearn.htm?courseId=1003228034#/learn/video?lessonId=1003665724&courseId=1003228034

代碼資源: http://www.yuankuwang.com

相關文章
相關標籤/搜索