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")
下面咱們解決這樣一個問題,在當前目錄創建一個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");
即將這幾個模塊都引入了進來,而後經過初始化構造函數建立實例, 最後調用了一些方法。
這個例子就展現了類與繼承、模塊引入等方法。
這是一個重要的概念,以前一直都沒有很好的理解,因此這裏着重整理。
其實很簡單,就是輸入 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中,每每最後一個參數是一個回調函數,因此能夠在作完第一步以後再將第二步做爲一個回調函數去執行,而後第二步執行完成以後,去完成做爲第二步的回調函數的第三步。。。。這就是所謂了回調地獄了。
這只是三個步驟,若是去寫嵌套,就會發現十分的不友好, 因此咱們須要的是有一種處理機制來解決這個問題。
首先,咱們分析一個多個函數運行可能會出現的狀況:
前面三個是比較重要的,因此咱們先學習前面三個,就要用到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