Node.js第一天
1. 初識Node.js
1.1 Node.js是什么
- Node.js? is a JavaScript runtime built on Chrome's V8 JavaScript engine.
- Node.js不是一門語言
- Node.js不是庫(library)、不是框架(frame)
- ==Node.js是一個JavaScript運行環境==
- <u>簡單點來講就是Node.js可以解析和執行JavaScript代碼</u>
- ==以前只有瀏覽器可以解析和執行JavaScript代碼==
- 也就是說現在的JavaScript可以完全脫落瀏覽器來運行,一切都歸功于:Node.js
- 構建于chrome的v8引擎之上
- 代碼只是具有特定格式的字符串
- 引擎可以幫你去解析和執行
- chrome的V8引擎是目前工人的解析執行js代碼最快的
- node.js作者把Google Chrome中的V8引擎移植了出來,開發了一個獨立的js運行環境
- 瀏覽器中的JavaScript
- ECMAscript
- js基本語法
- BOM
- DOM
- ECMAscript
- Node.js中的JavaScript
- 沒有BOM、DOM
- 有ECMAScript
- 在Node這個JavaScript執行環境中為JavaScript提供了一些服務器級別的操作API
- 例如文件的讀寫
- 網絡服務的構建
- 網絡通信
- http服務器
- 等處理(相當于開發web服務器,學習對應的api即可)
- Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
- event-driven 事件驅動
- non-blocking I/O model 非阻塞IO模型(異步)
- lightweight and efficient. 輕量和高效
- 隨著課程慢慢學習會明白事件驅動、非阻塞IO模型
- ==NodeJS以事件驅動著名,通過異步的編程達到高吞吐量高性能。==
- Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
- npm是世界上最大的開源庫生態系統
- 絕大多數JavaScript相關的包都存放在了npm上,這樣做的目的是為了讓開發人員更方便的去下載使用
- npm是基于node.js開發的包管理工具
1.2 ==node的API參考文檔的使用:==
-
黑點后面代表參數,例:
- request <http.IncomingMessage>
如果是方法它會告訴你方法的參數是什么,是否可選的。
上述
request
代表綁定函數的參數,因為它是一個事件因此肯定需要綁定一個函數,后面的連接為這個參數的類型,功能上能夠調用什么變量和方法。 加()的代表方法,不加()的代表屬性
1.3 Node.js能做什么
- web服務器后臺
- 命令行工具
- npm
- git(C語言)
- hexo(node)
- 。。。
- 對于前端開發工程師來講,接觸node最多的是使用它的命令行工具
- 自己寫的很少,主要是使用別人第三方發布的
- webpack
- gulp
- npm
1.4 達到目標
- B/S編程模型
- browser-Server
- back-end
- 任何服務端技術這種BS編程模型都是一樣,和語言無關
- Node只是一個工具
- 模塊化編程
- RequireJS
- SeaJS
- css中可以使用
@import('文件路徑')
來實現引入外部文件 - 以前認知的js智能通過
script
標簽來加載 - 在Node中可以像
@import
一樣來引用加載JavaScript腳本文件
- Node常用API
- 異步編程
- 回調函數
- Promise
- async
- generator
- Express web開發框架
- ECMAScript 6
- 在課程中穿插講解
- 它只是一個新的語法
- 。。。
- 學習node不僅會幫助打開服務端黑盒子,同時有助于學習高級內容
- Vue.js
- React
- angular
2. 起步
2.1. 安裝Node
- 查看當前Node環境的版本號
- 官網下載,LTS(長期支持版)穩定版,Current體驗版
- 安裝,一直下一步
- 查看,打開命令行 cmd,輸入
node --version
或者node -v
查看版本 - 環境變量
2.2 node練習
- node只有js文件,代碼一般為 .js文件
- 創建編寫js文件
- 打開終端(命令行),定位到腳本文件所屬目錄
- 輸入
node 文件名
執行對應的文件,顯示結果
==注意:文件名不能使用node
,最好也不要使用中文==
解析執行JavaScript
-
讀寫文件
-
使用
fs
模塊var fs = require('fs');
-
讀取文件
fs.readFile('路徑', '可選編碼', function (error, data) {});//具體查看代碼
==readFile方法的第二個參數為可選參數,規定解析文件的編碼,可選擇
utf-8
編碼,讓他以網頁原文件的形式讀取,不是二進制數據==-
寫文件
fs.writeFile();
-
==Node.js中常常使用回調函數作為參數,因為是異步的==
- HTTP
- 詳見代碼。
- ==服務器和web頁面一般使用字符串進行通信==,因此json的傳遞需要轉化為字符串
-
Ctrl
+c
可以關閉服務器(bash和cmd一樣) - 在瀏覽器查看http請求中,有一個
/favicon.ico
路徑,該請求是瀏覽器的默認行為,目的是請求網頁的網站頭像 - ==響應內容只能是字符串或二進制數據(Buffer)。對象、數字、數組、布爾值都不行==,因此
response.end()
方法能夠響應字符串和二進制數據
3. Node中的JavaScript
什么是模塊化
-
現實中的模塊化
- 生產效率高
- 維護方便,成本低
-
程序中的模塊化
- 開發效率高 一次編寫多次使用
- 方便維護了(維護的成本更低)模塊之間有高耦合低內聚的特點
-
為什么要在 程序 中使用 模塊化的開發方式
- 命名沖突
- 文件依賴
3.1 Node中使用模塊
- ECMAScript
- 沒有DOM、BOM
- 核心模塊
- 第三方模塊
- 用戶自定義模塊
3.2 核心模塊
-
Node為JavaScript提供了很多服務器級別的API,這些API絕大多數都被包裝到了一個具名的核心模塊中了。
例如文件操作的
fs
核心模塊,http服務構建http
模塊,path
路勁操作模塊、os
操作系統信息模塊。-
只要提到一個核心模塊,就要想到引入該模塊。使用require[1]。
var fs = require('fs'); var http = require('http');
==模塊的好處:可以完全避免變量命名沖突污染的問題==
但是模塊之間的通信需要使用模塊中的
exports
對象==使用的所有文件操作的API都是異步的,即核心模塊fs的方法。執行順序可能要放到其他執行過程的后邊執行==
3.3 用戶自定義模塊
? Node.js不能同時執行多個js文件,需要使用模塊化編程
- 模塊化相當于同一個文件中的script標簽
- ==模塊的功能要單一==
-
require
- require是一個方法,作用是用來加載模塊的
- 在Node中,模塊有三種
- 具名的核心模塊,例如fs、http
- 用戶自己編寫的模塊(就是一個js文件),可以使用require方法加載。==相對路徑必須加
./
==,否則會報錯,因為會將模塊認定為一個核心模塊
在node中沒有全局作用域,只有==模塊作用域==,相當于兩個script標簽。require方法加載時可以省略后綴名的。
- 推薦使用省略后綴名的寫法
- 既然是模塊作用域,如何讓模塊與模塊之間通信
require方法有兩個作用:
- 加載文件模塊并執行里面的代碼
- 拿到被加載文件模塊導出的接口對象
? 每個文件模塊中都提供了一個對象:exports
? exports默認是一個空對象,是require方法的返回值
- exports
- exports用來實現不同模塊之間的通信
- ==每個模塊中都提供了一個exports對象,默認是一個空對象==
- 該對象類似于原型鏈中的prototype對象,能夠向對象中添加數據,另一個模塊可以通過
require
方法返回的exports對象使用這些添加的數據
4. web服務器開發
4.1端口的概念
計算機中只有一個物理網卡,而且同一個局域網中,網卡的地址必須是唯一的。
- 網卡是通過唯一的ip地址來進行定位的
- ip地址用來定位計算機
- 端口號用來定位具體的應用程序(所有需要聯網通信的軟件都必須具有端口號),通信的軟件占用
- 端口號的范圍從0~65536之間
- 在計算機中有一些默認的端口號,最好不要去占用
- 例如http服務的80
- 可以同時開啟多個服務,但一定要確保不同服務占用的端口號不一致。同一個端口號只能被一個程序占用
- ==目前發現,node的網絡編程主要是通過request和response兩個對象來實現的。一般就是監聽request事件來獲取客戶端和服務端的數據。==
- ==request代表著客戶端的信息,response代表著服務器的信息==
- 開啟服務器流程
var http = require('http')
//1. 創建server
var server = http.createServer()
//2. 監聽Server的request請求事件,設置請求處理函數
// 請求
// 處理
// 響應
// 一個請求對應一個響應,如果一個請求過程中,已經結束響應了,則不能重發發送響應。
// 沒有請求就沒有響應
server.on('request', function (req, res) {
console.log(req.url)
})
// 3.綁定端口號,啟動服務
server.listen(3000, function () {
console.log('server running');
})
4.2 Content-Type
- 在服務端默認發送的數據,其實都是utf-8編碼的內容
但在瀏覽器不知道你是utf-8編碼的內容
瀏覽器在不知道服務器響應內容的編碼情況下會按照==當前操作系統的默認編碼==去解析 - ==在http協議中,Content-Type就是用來告知對方我給你發送的數據內容是什么類型==,響應和請求都可以設置
- Content-Type的值:
-
text/plain
為普通文本,瀏覽器不會解析 -
text/html
為html文本,瀏覽器會當做html文件解析該文本 - 圖片不需要指定編碼,編碼一般指定的是:字符編碼
-
- Content-Type的值:
==網址http://tool.oschina.net能夠查詢文件對應的Content-Type==
==URL==:統一資源定位符,一個url對應一個資源
5. 注意事項
- node不管什么模塊,涉及到路徑的都是從app所在的目錄為起點的
Node.js第二天
1. 代碼規范問題
-
采用無分號代碼風格的時候,需要注意一下情況:
當一行的代碼是以:
()
、[]
、`` `[1] 為開頭,需要在這行代碼的前面加上分號防止語法錯誤-
在ES6中使用`` `(反引號)包圍的字符串時,字符串中拼接變量不用使用+變量+的形式,直接在字符串中使用${}來引用變量
str = `adadadadd${str1}ppppppp`;
?
say() ;(function(){//此處需要加分號 })()//這種形式絕對不會報錯
- 能在一些第三方開發的代碼中一上來就以一個
;
開頭。 - 不論使用什么風格的代碼,在符合上述情況的時候,最好都在開頭加上
;
- 有些人也使用
!
或者~
等
//連續調用兩個方法,規范如下 http .createServer() .listen()
?
==進程是資源的分配和調度的一個獨立單元,而線程是CPU調度的基本單元==
2. 目錄斜杠問題
- 字符串目錄一般采用斜杠
/
,也可以使用\\
,\
反斜杠的作用是用來轉義 - Windows的目錄采用了
\
來顯示目錄,這點與字符串和網址的url相反
3. Node 安裝第三方庫
- 在需要安裝的目錄下 輸入npm install +包名,安裝到執行目錄中新建的node_modules目錄下
- 一般安裝在與服務器代碼同級目錄下
- node_modules目錄不能夠修改
4. 服務器端渲染
- 在服務端使用模板引擎(見代碼)
- 瀏覽器端的渲染在script標簽中進行,服務端的渲染在服務器端進行,服務器渲染是可以減少請求,提升頁面性能的,在實際操作時視情況選用
- 客戶端渲染不利于SEO(爬蟲查找)搜索引擎優化,例如ajax等異步渲染(想想全部刷新的頁面一般都是服務器端渲染)
- 服務端渲染是可以被爬蟲抓取到的,客戶端異步渲染是很難被爬蟲抓取到的
- 真正的網站是異步和服務端渲染相結合的
- 例如京東的商品列表,服務器喧嚷是為了SEO搜索引擎優化;商品評論為了用戶體驗,而且也不需要SEO優化,所以采用客戶端渲染
- 服務器渲染一般會刷新整個頁面,頁面源碼會看到呈現的文字(方便SEO);客戶端渲染一般不會刷新頁面。
- ==服務端和瀏覽器端使用模板引擎有本質區別,瀏覽器端是將要解析的模板放在script標簽找中的,服務器是將整個web頁面當做要解析的字符串,因此服務器端使用模板引擎會在沒有解析時暴露源碼內容,瀏覽器端使用模板引擎則不會出現這種情況==
5. 瀏覽器解析靜態資源
- js的執行會阻塞DOM樹的解析和渲染
- css的加載不會阻塞DOM樹的解析,會阻塞DOM樹的渲染
-
瀏覽器收到HTML響應內容之后,會開始從上到下一次解析
當在解析的過程中,如果發現:
? link
? script
? img
? iframe
? video
? audio
等帶有src或者是href(a標簽除外)屬性的標簽(具有外鏈的資源標簽)的時候,瀏覽器會自動對這些資源發起新的請求
6. 開發文件規范問題(處理網站中的靜態資源)
- 為了讓目錄結構保持統一清晰,約定把所有的HTML文件都放在views目錄下
- 為了方便的統一處理靜態資源(css,img,js,第三方資源),我們約定把所有的靜態資源都存放在public目錄下
- css目錄中存放css文件
- img目錄下存放圖片文件
- js目錄中存放js文件
- lib目錄下存放第三方資源
- 使用代碼控制,可以控制用戶訪問哪些資源。即可以訪問index.html和public目錄中的資源
- ==客戶端的請求路徑不再使用文件路徑了(相對路徑),web頁面的請求url一般使用
/
(根目錄)的url地址來引用資源。服務端代碼的路徑還是相對路徑== - 瀏覽器在真正發送請求的時候會自動把 http://127.0.0.1:3000 拼上,所以在瀏覽器顯示的請求路徑一律寫成根路徑形式,例如
/public
,這種形式叫做url路徑
7. url模塊
- url模塊能夠用于 URL 處理與解析
-
url.parse()
方法能夠解析請求網址,返回一個包含各個請求數據的對象。==記住第二個參數設置為true== - 該方法返回的路勁也為端口號后面的路徑
-
==網頁中的路徑都是url路徑,不是文件路徑==
8. ==服務器讓客戶端重定向==
-
如何通過服務器讓客戶端重定向,重定向就是為了減少代碼的書寫
-
狀態碼設置為302臨時重定向(還有個301為永久重定向)
? 通過res.statusCode
-
在響應頭中通過Location告訴客戶端往哪重定向
? 通過res.setHeader來
設置完重定向之后一定要關閉請求,res.end()
res.statusCode = 302 res.setHeader('Location', '/') res.end()
?
如果客戶端發現收到服務器的響應的狀態碼是302,就會自動去響應頭中找Location,然后對該地址發起新的請求。所有就可以看到客戶端自動跳轉
-
-
301永久重定向 瀏覽器會記住
- 比如訪問a.com 會跳轉至b.com 下次瀏覽器不在向a.com發請求會直接跳轉至b.com
-
302 臨時重定向 瀏覽器不會記憶
- a.com臨時,下次瀏覽器還會請求a.com 然后告訴瀏覽器訪問b.com。每次請求都嘗試一下
9. Node中使用Console測試
- 在任何目錄下打開cmd,輸入node回車即可進入console控制臺,可以在console中測試方法
- 在node的console中所有核心模塊可以直接使用,不需要require引入
- 按Ctrl+c兩次即可退出console
- 這個專用名稱是REPL
- read
- eval
- loop
10.Node 常用指令
- 目前就一個node 進入node環境執行
11. 注意:
- ==原生的node 用 res.end() 來結束請求;express 框架用 res.send() 結束請求。==
Node.js 第三天
知識點
- 模塊系統
- 核心模塊
- 第三方模塊
- 自己寫的模塊
- 加載規則以及加載機制
- 循環加載的問題
- npm
- package.json
- Express
- 第三方web開發框架
- ==高度封裝了http模塊==
- 提高編碼效率,更加專注于業務,而非底層細節
- 學習過程知其所以然
- 增刪改查
- 使用文件來保存數據(鍛煉異步編碼),遇到業務能夠自己封裝
- MongoDB(數據庫)
- (所有方法都封裝好了)
軟件版本
- 涉及到軟件工程知識
- x.x.x
- 第一個數字代表大版本,新增功能比較多,甚至可能去除了某些功能
- 第二個數字:加入了新功能
- 第三個數字:修復bug,提升了性能
- 一般是客戶端軟件、技術框架開發者比較理解的多
- 做網站很少涉及到版本的概念,網站目的就是快
each和forEach解析
- jquery中each使用,一般用于遍歷jQuery選擇器選擇到的偽數組實例對象
- $.each(數組,function(index,element){}),該方法一個參數為要操作的數組,另一個為具體執行函數
- $(selector).each(function(index,element){}),該方法為選取元素執行函數
- jQuery的function中的參數與原生的forEach方法剛好相反
- each方法的作用
- 方便的遍歷jquery元素的
- 可以在不兼容forEach的低版本瀏覽器中使用jQuery的each方法
- forEach(function(item,index){})為原生js中的方法,可以遍歷任何可以被遍歷的成員
- ==forEach只能夠遍歷數組,each可以遍歷jquery獲取到的偽數組==
- jQuery的each方法支持低版本的IE瀏覽器,ECMAScript5中的forEach方法不支持IE8版本以及以下的瀏覽器
- ==$('div')的jQuery獲取元素的方法,返回的是一個偽數組,偽數組是對象不是數組,不能夠使用forEach方法,forEach()是Array.prototype原型鏈中的方法==
偽數組轉化為數組
- 偽數組使用
[].slice.call(eles)
可以使用數組中的方法 - 偽數組是一個key為數字的對象
node和PHP
- php是文件型的
- node定制性更強
- node比較底層,很多功能需要自己實現。
- 在Node中開啟的服務器,默認是黑盒子,所有資源都不允許用戶來訪問,用戶可以訪問哪些資源具體有開發人員編寫設計的代碼為準。可以處理url,php則會顯示index.html的形式
1.Node中的模塊系統
使用node編寫應用程序主要就是在使用:
- ECMAScript語言
- 核心模塊
- 文件操作的fs
- http服務的http
- url路徑操作系統
- path路徑處理模塊
- os操作系統信息
- 第三方模塊
- art-template
- 必須通過npm來下載可以使用
- 自己寫 的模塊
- 自己創建的模塊
1.1 什么是模塊化
- 具有文件作用域
- 通信規則
- 加載 require
- 導出
- ==模塊的好處:可以完全避免變量命名沖突污染的問題==
1.2 CommonJs模塊規范
在Node中的js還有一個重要的概念:模塊系統
- 模塊作用域
- 使用require方法用來加載模塊
- 使用exports接口對象用來導出模塊中的成員
如果一個模塊需要直接導出某個成員,而非掛載的方式,需要使用下面的方式
module.exports = 'hello'//這樣另一個模塊加載這個模塊就會返回'hello',而不是exports對象
1.2.1 加載 require
語法:
var 自定義變量名 = require('模塊')
兩個作用:
- 執行被加載模塊中的代碼
- 得到被加載模塊中的
exports
導出接口對象
1.2.2 導出exports
- Node中是模塊作用域,默認文件中所有的成員只在當前文件模塊中有效
- 對于希望可以被其他模塊訪問的成員,我們就需要把這些公開的成員都掛載到
exports
接口對象中
導出多個成員(必須在對象中):
exports.a = 123
exports.b = {
foo : 123
}
導出單個成員(拿到的就是:函數、字符串):
module.exports = 'hello'
module.exports = function(){//后者會覆蓋前者
}
因此,也可以如下方式導出多個成員:
module.exports = {
//加成員
}
1.2.3 原理解析
- 可以認為在node中,沒一個模塊都有一個自己的
module
對象 - module對象中,有一個成員叫:
exports
成員也是一個對象 - 也就是說需要對外導出成員,只需要把導出的成員掛載到
module.exports
對象中
var module = {
exports : {
}
}
//誰require這個模塊,就會得到 module.exports
//為了簡化操作,在模塊中還有一句代碼
var exports = module.exports //exports和module.exports掛載是一樣的。這行代碼應該是在第一行
console.log(exports === module.exports)//結果為true
//默認在代碼的最后又一句:
return module.exports
//一定要記住return的是module.exports,不能通過直接修改exports影響返回的對象,因此導出單個成員需要修改module.exports對象
- exports是
module.exports
的一個引用
1.2.4 require方法加載規則
優先從緩存加載
不會重復加載相同的模塊
可以拿到其中的接口對象,不會重復執行模塊中的代碼
這樣做是為了避免重復加載模塊,提高加載效率
require的參數為==模塊標識==
-
==加載時要判斷模塊標識==
- 核心模塊
- 第三方模塊
- 凡是第三方模塊都可以通過
npm
來下載 - 使用的時候可以同require('包名')的方式進行加載才可以使用(非路徑形式)
- 不可能有一個第三方名字與核心模塊名字相同
- 查找規則(以art-template模塊為例)
- 先找到當前文件所處目錄中的node_modules目錄(這個目錄就是用來存放第三方模塊的)
- 找node_modules/art-template
- 找node_modules/art-template/package.json 文件
- 找node_modules/art-template/package.json文件中的“main”屬性
- mian屬性中就記錄了art-template的入口模塊
- 然后加載使用這個第三方包,實際上最后加載的還是文件
- 如果package.json文件不存在或者main指定的入口模塊也沒有,則node會自動找該目錄下的index.js作為默認備選項
- 若果以上所有任何一個條件都不成立,則會進入上一級目錄中的node_modules目錄查找,規則如上
- 如果上一級還沒有,則繼續往上上一級查找,知道磁盤根目錄還找不到,最后報錯:
can not find module xxx
,==反正要找到node_modules
目錄==
- 凡是第三方模塊都可以通過
- 自己寫的模塊
==注意:==
- 路徑形式的模塊采用
./
,當前目錄不可省略,`../``上一節目錄不可省略 - 核心模塊的加載按名字來加載,核心模塊的本質也是文件。核心模塊的文件已經被編譯到二進制文件中了,我們只需要按照名字來加載就可以了
- 一個項目有且只有一個node_modules,不會出現多個node_modules。該目錄放在項目同目錄(項目根目錄)中,這樣的話項目中的代碼都可以加載到第三方包。
1.3 npm
- node package manager,是一種命令行工具
- npm install art-template jquery bootstrap(可以一次性裝幾個包)
- install 也可以用
i
代替
1.3.1 npm網站
npmjs.com npm官方網站,可以查詢包和發包
1.3.2 npm命令行工具
npm第二層含義就是一個命令行工具,只要你安裝node就已經安裝好了npm。
npm也有版本的概念(獨立的軟件)
- npm查看版本輸入:
npm --version
npm -v
- npm升級
npm install --global npm//自己升級自己
- npm初始化
npm init
npm init -y//可以跳過向導,快速生成
- npm install
- 一次性把dependencies選項中的依賴項全部安裝
- ==npm i install可以使用i簡寫代替==
- npm install 包名
- 只下載
- npm i 包名
- npm安裝指定的版本
- npm i bootstrap@版本號 (中間沒有空格)
- npm install --save 包名
- 下載并保存依賴項(package.json文件中的dependencies選項)
- npm i -S 包名
- npm uninstall 包名
- 只刪除,如果有依賴項會依然保存
- npm un 包名
- npm uninstall --save 包名
- 刪除的同時也會把依賴信息也刪除
- npm un -S 包名
- npm help
- 查看使用幫助
- npm 命令 --help
- 查看指定命令的幫助
- --global 為全局安裝,安裝到node的安裝目錄下,一般用于影響操作的軟件更新或替換
- npm install 包名 --save-dev 這樣安裝的包和版本號就會存在 package.json 的 devDependencies 這個里面,--save 會會存在 dependencies 里面。簡寫為 -D
1.3.3 解決npm被墻問題
- npm速度很慢,http://npm.taobao.org/ 淘寶的開發團隊吧npm在國內做了一個備份
- 安裝淘寶的 cnpm:
# 在任意目錄下執行都可以
# --global表示安裝到全局,而非當前目錄,因此在任意目錄都可以執行
# --global不可以省略
npm install --global cnpm
? 接下來你安裝包的時候吧之前的npm替換成 cnpm
? 舉個例子:
# 這里走國外的npm服務器,速度較慢
npm install jquery
#使用淘寶的服務器下載:jquery
cnpm install jquery
如果不想安裝cnpm
又想使用淘寶的服務器來下載:
npm install jquery --registry=https://registry.npm.taobao.org
但是每一次手動這樣加參數很麻煩,所以我們可以吧這個選項加入到配置文件中:
npm config set registry https://registry.npm.taobao.org
# 查看 npm 配置信息
npm config list
只要經過了上面命令的配置,則你以后所有的npm install
都會默認通過淘寶的服務器來下載。
1.4 package.json
- package.json,包描述文件,包的說明書,說明包引用了什么依賴
- npm install art-template --save(放到包后面)或者npm install --save art-template (放到包前面,前后都可以),package.json文件中多了一個屬性
"dependencies"
,該屬性的值的對象中保存著項目的依賴(依賴什么插件、插件的版本) - ==建議每一個項目都有一個package.json文件,就像說明書==
- package.json文件可以通過
npm init
的方式自動初始化出來,npm init
會以向導的方式(問一句答一句),提問項如下(不填的直接回車):- name:
- version
- description:(項目的描述)
- entry point:(index.js) (項目的入口一般使用
main.js
作為入口,與json文件同目錄) - test command:(測試命令)
- git repository:(github地址)
- keywords:
- author:作者
- license: (ISC)(許可證)
- --save 第三方包會出現"dependencies"屬性
- 目前最有用的是"dependencies"選項,包含第三方包的依賴信息
- 建議執行
npm install +第三方包名
都加上--save
這個選項,用來保存(項目依賴信息) - 如果node_modules刪除了,只要"dependencies"選項中保存有依賴包,下次直接npm install來加載包即可
==補充:==
npm 5 以前是不會有package-lock.json
這個文件的
npm 5 之后才加入這個文件
當你安裝包的時候,npm 都會生成或者更新package-lock.json
文件
2. Express
- experss : 表達,快速的
原生的==http==在某些方面表現不足以應對我們的開發需求,所以我們就需要使用框架來加快我們的開發效率,框架的目的就是提高效率,讓我們的代碼更高度統一。
- ==注意 express 也可以使用原生的方法==
- Express 是一個簡潔而靈活的 node.js Web應用框架, 提供了一系列強大特性幫助你創建各種 Web 應用,和豐富的 HTTP 工具。使用 Express 可以快速地搭建一個完整功能的網站。
- 在Node中,有很多的web開發框架,我們這里以學習
express
為主。 - http://expressjs.com/
//先建一個app.js
// 安裝依賴
// 初始化應用
// 引包開發
var express = require('express')
// 2.創建你的服務器應用程序
// 也就是原來的 http.createServer
var app = express() //相當于原來的server
//當服務器收到get請求/的時候,執行回調處理函數。必須為get請求
app.get('/', function (req, res) {
res.send('hello world')
})
//不需要一個一個再判斷了
app.get('/about', function (req, res) {
res.send('你好') //此處的編碼問題框架已經幫我們處理好了,不需要res.setHeader()
})
//相當于server.listen
app.listen(3000, function () {
console.log('app is running at port 3000.')
})
- express找不到的路徑,會返回Cannot GET /a (比如說‘a’) 頁面,同時返回狀態碼404
- express的get方法判斷的路徑為純路徑,不帶傳遞的參數和錨等內容
- ==app.get()方法會返回app對象,可以鏈式編程,繼續執行get方法==
express的request對象
//req對象中直接包含了 原生 url 模塊中返回的方法
req.query //返回參數對象
- request對象的其他屬性和方法可以查看文檔 http://www.runoob.com/nodejs/nodejs-express-framework.html
express公開指定的目錄(處理靜態資源)
// 公開指定目錄
//只要這樣做了,就可以直接通過 /public/xx 的方式訪問 public 目錄中的所有資源了
app.use('/public/', express.static('./public/')) //相當于拼接的'. + 路徑'
- 不公開目錄默認找不到路徑處理。
- 上述代碼公開的是public文件夾下的文件,因此次用
‘/public/’
的形式,也可以使用/public
,它只是在檢測url是否以你定義的開頭 - ==瀏覽器的訪問路徑只有在端口號后沒有'/'的時候補一個‘/’,瀏覽器在文件路徑后面補一個‘/’,其他的路徑都是在瀏覽器地址欄看到的路徑,沒有區別。==
總結:
- get方法是用來判斷訪問路徑,作出處理的
- use方法是用來開發靜態資源的。
- ==瀏覽器路徑確認是文件,會在后面自動拼接一個
/
== - 在Express中開放資源就是一個API的事兒;模板引擎在Express中也是一個API的事兒
偽數組
- 一般偽數組的形式:(如下的一種對象)
var fakeArr = {
0 : 'abc',
1 : 'efg',
2 : 'aaa',
length : 3
}
Node.js 第四天
知識點
- express
- 基于文件做一套 CRUD
- C - Creation 增加
R - Retrieve 查詢
U - Update 修改
D - DELETE 刪除
- C - Creation 增加
文件路徑和模塊標識
- 文件路徑
/
為磁盤根目錄 - 模塊標識的
./
中的.
不能省去
1. 修改完代碼自動重啟
? 我們這里可以使用一個第三方命令行工具:nodemon
來幫助我們解決頻繁修改代碼重啟服務器問題
? nodemon
是一個基于Node.js開發的一個第三方命令行工具,我們使用的時候需要獨立安裝:
npm install --global nodemon
安裝完畢之后,使用:
node aap.js
# 使用 nodemon啟動app
nodemon app.js
只要通過nodemon app.js
啟動服務,則他會自動監視你的文件變化。當文件發生變化的時候,自動幫你重啟服務器。
2. express 基本路由(router)
路由(routing)是指分組從源到目的地時,決定端到端路徑的網絡范圍的進程。
- 路由其實就是一張表。我們已經了解了 HTTP 請求的基本應用,而路由決定了由誰(指定腳本)去響應客戶端請求。
- 這個表里面有具體的映射關系
- 當請求'/',執行什么語句,分配什么
- ==app.get()方法會返回app對象,可以鏈式編程,繼續執行get方法==
- express 中的get方法就相當于一個路由,以什么路徑執行的時候,執行對應的處理函數,就是一個路由處理
- ==千萬不要糾結端口后面的請求url是否加
/
開頭,不加也可以==
路由器(映射關系):
- 請求方法
- 請求路徑
- 請求處理函數
get:
//當以 GET方法請求 / 的時候,執行對應的處理函數
app.get('/', function (req, res) {
res.send('hello')
})
post:
//當以 POST 方法請求 / 的時候,執行對應的處理函數
app.post('/', function (req, res) {
res.send('hello')
})
- 同一路徑使用不同的請求方式,就可以讓同一個路徑被多次使用
3. express靜態服務
- 當以
/public/
開頭的時候,添加.
去./public/
目錄中尋找對應的資源
app.use('/public/', express.static('./public/'))
- 當省略第一個參數的時候,則可以通過省略 /public 的方式來訪問。直接寫該目錄下的資源路徑即可,例如public目錄下的index.html,url為 127.0.0.1:3000/inde.html
app.use(express.static('./public/'))
- 當指定第一個參數的時候,必須是/a/為開頭取代 /public/ 訪問public中的資源,是為了方便指定定制訪問路徑。例:1207.0.0.1:3000/a/index.html 才能訪問如下的開放目錄。
app.use('/a/', express.static('./public/'))
4 第四種還沒學
app.use('/static', express.static(path.join(_dirname, './public/')))
web瀏覽器中的路徑為 URL,不是傳統的路徑,是一個定位符
4. 在express中配置使用art-template
模板引擎
-
art-template
- 注:上邊的語法為[]+()
安裝
npm install --save art-template
npm install --save express-art-template
配置:
app.engine('art', require('express-art-template'))
// 第一個參數標識,當前以 .art 結尾的文件的時候,使用art-template模板引擎
// 也可以把第一個參數改寫為html
app.engine('html', require('express-art-template'))
使用:
// Express 為 Response 響應對象提供了一個方法 : render
// render 方法默認是不可以使用的,但是如果配置了模板引擎就可以使用了
// res.render('html模板名', {模板數據})
// render方法中第一個參數不能寫路徑,默認會去項目中的 views 目錄查找該模板文件
// 也就是說 Express 有一個約定:開發人員把所有的視圖文件都放到 views 目錄中
// Express 默認查找目錄就是 views 目錄
// 呈現視圖并將呈現的HTML字符串發送到客戶端
app.get('/', function (req, res) {
res.render('404.art')
})
// 如果想要修改默認的views 為別的目錄
// app.set('views', 'render函數的默認路徑')
app.set('views', './views')
// 查找views 目錄下的文件
app.get('/admin', function (req, res) {
res.render('admin/index.html', {
title : '管理系統'
})
})
==當需要直接返回頁面的時候,也可以使用render方法==
//當需要返回路徑的時候
app.get('/', function (req, res) {
res.render('index.html')
})
5. express 中重定向的使用
//原生的重定向
res.statusCode = 302
res.setHeader('Location', '/')
//express 中封裝的重定向
res.redirect('/')
GET和POST請求獲取數據
-
res.query
是通過查詢字符串來獲取參數,只能獲取get請求參數
- get請求數據在請求行中,post需要設置請求行
- GET沒有請求體,post的請求數據在請求體中
在express 中獲取表單 POST 請求體數據
- 在Express 中沒有內置獲取表單 POST 請求體的API,這里我們需要使用一個第三方中間件:
body-parser
安裝:
$ npm install body-parser
配置:
var express = require('express')
//0.先引包
var bodyParser = require('body-parser')
var app = express()
//配置 body-parser
//只要加入這個配置,則在 req 請求對象上會多出一個屬性:body
//也就是說你可以直接通過 req.body 來獲取表單 POST 請求體數據了
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
//使用
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
//可以通過 req.body 來獲取表單 POST 請求數據
res.end(JSON.stringify(req.body, null, 2))
})
fs.readFile()
方法補充
//fs.readFile()方法讀取到的文件為二進制數據,如果想要解析該二進制數據,可以使用toString()方法,也可以使用readFile()方法第二個參數傳值為'utf-8'
==SyntaxError
表示語法錯誤==
6. express 中設置狀態碼
- express的響應對象
response
中有一個方法status()
可以通過傳遞參數來設置響應碼,該方法還可以鏈式編程,
res.status(500).send('hello')
==數據庫中的性別gender
一般使用0或1代替==
7. express注意
express沒有提供讀取文件的api,主要封裝了http操作的api
-
fs模塊是異步的,因此在寫讀取文件的時候,最好在當前的http操作函數中讀取文件。相當于setTimeout()方法
例如:[圖片上傳失敗...(image-ed9523-1536318692721)]
8. Express-crud 路由設計
- 路由設計就是請求不同的url,響應不同的結果
- 路由的設計:考慮請求方式、請求路徑、請求參數。
請求方式 | 請求路徑 | get請求 | post請求 | 備注 |
---|---|---|---|---|
GET | /students | 渲染首頁 | ||
GET | /students/new | 渲染新增頁面 | ||
POST | /students/new | name、age、gender、hobbies | 處理新增請求 | |
GET | /students/edit | id | 渲染編輯頁面 | |
POST | /students/edit | id、name、age、gender、hobbies | 處理編輯請求 | |
GET | /students/delete | id | 處理刪除請求 |
路由的文件采用模塊化開發,需要單獨寫一個模塊。
- 路由模塊放在app入口模塊的同級目錄
express 提供了一種==包裝路由==的方法:
//1. 創建一個路由容器
var router = express.Router()
//2. 把路由都掛載到 router 路由容器中
router.get('/', function(){})
......
//3. 導出router容器
module.exports = router
在app文件中使用router容器:
//1. 導入路由容器文件
var router = require('./router')
//2. 把路由容器掛載到app服務中
app.use(router)
==模塊的職責要單一,不要混寫。劃分模塊的目的就是為了增強項目代碼的可維護性,提升開發效率==
注意:
==配置模板引擎和body-parser 一定要在 app.use(router) 掛載路由之前,才能正常執行==
9. 獲取函數異步操作的結果(封裝異步API)
- 獲取一個函數中異步操作的結果,則必須通過==回調函數==來獲取
- 回調函數的關鍵作用
例如:
function fn (callback) {
setTimeout(function () {
var data = 'ddd'
callback(data)
}, 1000)
}
fn(function (data) {
console.log(data)
})
只要是獲取函數中的異步函數的數據(函數中還有執行的函數),都可以采用上述方法。通過引用類型可以獲取到異步操作的結果。(試一下對象行不行) 實際運行的過程中是上面的fn函數中代用了回調函數。
注意:
==callback的運行一定要寫在函數的最后一行,要不然會阻礙后邊代碼的執行==
10. ES6 初識
ECMAscript 6 中的一個數組方法:find
該方法需要接收一個函數作為參數
當某個遍歷項符合回調函數中的遍歷條件時,find會終止遍歷,同時返回遍歷的成員。只要對比項和數組中的某一項相同時,就會返回數組中的某一項。
var stu = students.find(function (item) {
return item.name === student.name
})
ECMAscript 6 中的一個數組方法:findIndex(),同上
11. 數字和字符串之間的轉換
-
數字轉換成字符串
使用:toString方法,只能字符串使用
-
字符串轉化為數字
使用:parseInt()方法
十進制轉化成其他進制,要使用toString()方法
任何進制(包括字符串)轉化成十進制都要使用parseInt()方法
==瀏覽器端渲染和服務器端渲染==
表單請求的post不需要設置請求頭。
瀏覽器腳本請求需要設置請求頭。
post只是一種請求!不是響應
請求方式
1. 表單
2. a 標簽,href 屬性
3. 腳本(ajax,http)
1和2屬于采用服務器端渲染的方式,3屬于瀏覽器端渲染的方式。
- 說白了就是 服務器端加載時服務器處理完成返回一個 html 頁面直接渲染就可以呈現。瀏覽器端加載時先返回一個 HTML 頁面,然后在加載的過程中渲染需要使用的功能。
服務器端使用 template 和瀏覽器端使用 template 的區別
- 瀏覽器端是先寫好 HTML ,引入一個 template 區域來加載。加載的過程中從 js 中提取數據。
- 服務器端是 直接寫好模板頁面的字符串,傳遞給處理程序,然后再返回數據,瀏覽器加載。
- 兩種加載都是渲染字符串的形式。處理完成返回字符串,渲染到頁面上。
Node.js 第五天
JavaScript的異步操作是因為其單線程、事件循環的底層機制。
1. 異步編程
注意:凡是需要得到一個函數內部異步操作的結果
- setTimeout
- readFile
- writeFile
- ajax
這種情況必須通過:回調函數來獲取數據
- JavaScript中的事件一般都是等代碼執行完成之后才會執行的,因此函數中有綁定事件的,獲取事件中的數據一般要使用callback 回調函數的形式
2. 瀏覽器中的模塊化
瀏覽器中可以使用第三方庫實現模塊化開發,js天生不支持模塊化
- require.js 第三方庫 AMD
- sea.js 第三方庫 CMD
PHP直接支持require
、include
,因為PHP當初設計時加入了這個功能
- 模塊作用域
- 可以使用API 來進行文件與文件之間的依賴加載
node.js中的模塊化 CommonJS
- CommonJS、AMD、CMD 都是民間搞出來的
- EXMAScript 是官方規范定義。官方解決在不同的環境使用不同的 JavaScript 模塊化解決方案,所有ECMAscript 在2015年發布了 ECMAscript 2016 官方標準,支持了模塊化。雖然標準已經發布,但是有的 js 運行環境還不支持
學vue 的時候會去學習,例如:
- less 編譯器 > css
- es6 編譯器 > es5
目前前端的情況就是使用很多新技術,然后利用編譯器工具打包可以在低版本瀏覽器中運行。
使用新技術的目的就是為了提高效率,增加可維護性
3. app.use()
- app.use() 涉及到中間件的問題
- 不僅可以用來處理靜態資源,還可以配置中間件等等,很多工作
- 中間件有一套規則
4. 新版本的npm特性(npm5 以上)
- 新版本的的npm不需要加 --save 命令
- uninstall 不加--save 會刪除依賴
- 當安裝包的時候,會自動創建或者是更新
package-lock.json
這個文件 -
package-lock.json
文件中保存著所有依賴樹(所有包)的信息,下載地址,刪除node_modules文件之后能夠根據該文件快速下載依賴。- 這樣重新
npm install
的速度就會提升
- 這樣重新
- 從文件來看有
lock
,是用來鎖定版本的- 如果項目依賴了某第三方庫的 1.1.1 版本,如果重新安裝有可能升級這個庫,而
package-lock.json
會鎖住依賴的版本,不會出現升級造成項目不可用的情況
- 如果項目依賴了某第三方庫的 1.1.1 版本,如果重新安裝有可能升級這個庫,而
5.條件函數的使用(封裝數組 find 方法)
- 不論條件函數還是回調函數,函數作為參數的情況,都接收的是整個函數,參數寫成 callback 或者 conditionFunc ,不能寫成callback(a,b)這樣的形式
例:
//條件函數的使用
//封裝數組的 find 和 findIndex 方法
Array.prototype.myFind = function (conditionFunc) {
for (var i = 0; i < this.length; i++) {
if (conditionFunc(this[i], i)) {
return this[i]
}
}
}
var ret = [1,2,3].myFind(function (item, index) {
return item === 2
})
數組中的遍歷方法,都是對函數作為參數的一種應用。數組中遍歷的方法都有:
- every
- some
- includes
- map
- reduce
6. sublime 中設置 js 代碼高亮
- ctrl + shift + p , 快捷鍵 set syntax javascript
7. Express 定制 404 頁面
express 定制404頁面需要通過中間件來配置
-
只需要在自己的路由之后增加一個
app.use(function (req, res) {
? //所有未處理的請求路徑都會跑到這里
})
MongoDB
1. 關系型數據庫和非關系型數據庫
表就是關系
或者說表與表之間存在關系
- 所有的關系型數據庫都需要通過
spq
語言來操作 - 所有的關系型數據庫在操作之前都需要設計表結構
- 而且數據表還支持約束(保證數據的完整性)
- 唯一的
- 主鍵
- 默認值
- 非空
- 非關系型數據庫非常靈活
- 有的非關系型數據庫就是 key-value 對兒,沒有表
- 在 MongoDB 是長的最像關系型數據庫的非關系型數據庫
- 它也是 創建數據庫 -> 數據庫(MongoDB中的叫法)
- 數據表 -> 集合(數組)
- 表記錄 -> 文檔對象
- MongoDB 不需要設計表結構
- 也就是說你可以任意的往里面存數據,沒有結構性這么一說
- 查看MongoDB版本 在命令行工具中輸入 mongod --version
- 需要配置==環境變量==,復制安裝包的 bin 文件目錄,加 ; 粘貼到Windows的環境變量中
2. 啟動和關閉數據庫
啟動:
# mongodb 默認使用執行 mongod 命令所處盤符根目錄下的 /data/db 作為自己的數據存儲目錄
# 所以在第一次執行命令之前先自己手動新建一個 /data/db (在需要執行的盤符根目錄下)
mongod
如果想要修改默認的數據存儲目錄,可以:
mongod --dbpath=數據存儲目錄路徑
停止:
#在開啟服務的控制臺,直接 Ctrl + c 即可停止
# 或者直接關閉開啟服務的控制臺也可以
3. 連接數據庫
連接:
# 默認連接本機的 MongoDB 服務
mongo
退出:
# 在連接狀態輸入 exit 退出連接
exit
4. 基本命令
-
show dbs
- 查看數據庫列表,查看顯示所有數據庫(查看有數據的數據庫)
-
db
- 查看當前操作的數據庫
-
use 數據庫名稱
- 切換到指定的數據庫(如果沒有回創建)
- 例如新建數據庫: use zbb
- 插入數據(不設計表結構,默認每個數據自帶ID)
-
db.students.insertOne({"name":"jack"})
相當于新建了一個students數據往里面加元素
-
- show collections
- 可以查看當前表里的集合
- db.集合名.find()
- 查看當前集合中的數據
5. 在Node中如何操作 MongoDB 數據庫
5.1 使用官方的mongodb
包來操作
- 可以查看官方文檔 github 搜索 mongodb node
5.2 使用第三方 mongoose 來操作 MongoDB 數據庫
第三方包:mongoose
基于MongoDB 官方的 mongodb
包再做的一次封裝.
安裝: npm i mongoose
開啟mongodb 數據庫,然后執行例程代碼
var mongoose = require('mongoose');
//連接MongoDB數據庫,引號中的第一個為本機,第二個為本機的test數據庫。
//指定連接的數據庫不需要存在,當你插入第一條數據之后就會自動被創建出來
mongoose.connect('mongodb://localhost/test', { useMongoClient: true });
mongoose.Promise = global.Promise;
// 創建一個模型
// 就是在設計數據庫
// MongoDB 是動態的,非常靈活,只需要在代碼中設計你的數據庫就可以了
// mongoose 這個包就可以讓你的設計編寫過程變得非常的簡單
var Cat = mongoose.model('Cat', { name: String });
// Cat 為表名(最終生成名稱為 cats),第二個參數為數據結構
// 實例化一個Cat
var kitty = new Cat({ name: '喵喵'});
// 持久化保存 kitty 實例
kitty.save(function (err) {
if (err) {
console.log(err);
} else {
console.log('meow');
}
});
5.3 MongoDB 數據庫的基本概念
按開發順序:
- 可以有多個數據庫
- 一個數據庫中可以有多個集合(表)
- 一個集合中可以有多個文檔(表記錄)
- 文檔結構很靈活,沒有任何限制
- MongoDB 非常靈活,不需要像 MySQL 一樣先創建數據庫、表、設計表結構
- 在這里只需要:當你需要插入數據的時候,只需要指定往那個數據庫的哪個集合操作就可以
- 一切都有 MongoDB 來幫助自動完成建庫建表這件事
- MongoDB 默認開啟端口 27017
理解:
{
qq : {//數據庫
users : [//集合
{name:"zzz"}//文檔對象
]
},
taobao : {
}
}
mongoose 官方指南
1. 設計Schema 發布 model
- 可以在文檔中的
model.js
中查看增刪改查的方法
// 此模塊是為了新建一個數據庫
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// 連接數據庫
mongoose.connect('mongodb://localhost/zbb')
// 相當于設計集合結構(表結構)
// 字段名稱就是表結構中的屬性名稱
// 每個屬性的值做了強制要求,都為js代碼
// 約束的目的是為了保證數據的完整性,不要有臟數據
// var blogSchema = new Schema({
// title: String,
// author: String,
// body: String,
// comments: [{ body: String, date: Date }],
// date: { type: Date, default: Date.now },
// hidden: Boolean,
// meta: {
// votes: Number,
// favs: Number
// }
// });
// 設計約束案例:
var userSchema = new Schema({
username: {
type: String,
required: true //必須要有不能為空
},
password: {
type: String,
required: true
},
// email: {
// type: String
// }
email: String
})
// 3. 將文檔結構發布為模型
// mongoose.model 方法就是用來將一個架構發布為 model
// 第一個參數:傳入一個大寫名詞單數字符串來表示你的數據庫名稱
// mongoose 會自動將大寫名詞的字符串生成小寫復數的集合名稱
// User在數據庫中會變成users 集合名稱
// 第二個參數:架構 Schema
// 返回值 :模型構造函數
// var Blog = mongoose.model('Blog', blogSchema)
var User = mongoose.model('User', userSchema)
// 4. 當我們游了模型構造函數之后就可以使用構造函數對users 集合中的數據操作了(增刪改查)
2. 增加數據
// 4. 當我們有了模型構造函數之后就可以使用構造函數對users 集合中的數據操作了(增刪改查)
// new 模型構造函數 參數為對象
var admin = new User({
username: 'admin',
password: 'aaa1'
})
// 5. 數據持久化
// 使用 .save() 方法
admin.save(function (err, ret) {
if (err) {
console.log('保存失敗');
} else {
console.log('保存成功');
console.log(ret); //就是剛剛保存的數據,id自動生成
}
})
3. 查詢
查詢所有:
User.find(function (err, ret) {
if(err) {
console.log('查詢失敗')
} else {
console.log('查詢成功');
console.log(ret);
}
})
按條件查詢所有:
User.find({
username: 'zs'
}, function (err, ret) {
if(err) {
console.log('查詢失敗')
} else {
console.log(ret);
}
})
按條件查詢單個:
User.findOne({
username: 'zs',
password: '123'
}, function (err, ret) {
if(err) {
console.log('查詢失敗')
} else {
console.log(ret);
}
})
4. 刪除數據
按條件刪除:
User.remove({
username: 'zs'
}, function (err) {
if (err) {
console.log('刪除失敗')
} else {
console.log('刪除成功')
console.log(ret);
}
})
根據條件刪除一個:
Model.findOneAndRemove(conditions, [options], [callback])
根據id刪除一個:
Model.findByIdAndRemove(id, [options], [callback])
5. 更新數據
根據id更新一個:
User.findByIdAndUpdate('數據的id', {
更新的數據
password: '1234'
}, function (err, ret) {
if(err) {
console.log('更新失敗');
} else {
console.log('更新成功');
}
})
剩余的更新方法查看文檔。
Node 操作 MySQL數據庫
安裝:
npm install --save mysql
未完待續。。。
異步編程
回調函數
Promise
callback hell:
[圖片上傳失敗...(image-3fee8d-1536318692721)]
無法保證順序的代碼:
var fs = require('fs')
fs.readFile('a.text', 'utf8', function(err, data) {
if (err) {
throw err
}
console.log(data)
fs.readFile('b.text', 'utf8', function(err, data) {
if (err) {
throw err
}
console.log(data)
fs.readFile('c.text', 'utf8', function(err, data) {
if (err) {
throw err
}
console.log(data)
})
})
})
注意:
之前的讀取文件 return 結束函數的方式,在開發中常常使用 throw err
的形式拋出異常
多個異步編程的情況,無法保證回調函數的執行順序(多線程調度機制),只能通過異步嵌套異步的形式保證順序。代碼太繁瑣,不好修改。
為了解決以上編碼方式帶來的問題(回調地獄嵌套),所以在ECMAscript 6 中新增了一個 API:Promise
- Promise : 保證承諾
Promise 容器:
容器中存放了一個==異步任務==
-
任務默認有三種狀態
- 默認狀態:pending 正在進行(之前網站響應有過這個狀態)
- pending 狀態發生變化,變為兩種狀態其中的一種
- Resolved 解決 成功
- Rejected 失敗
- 狀態改變之后不能再改變成另一種狀態
在ECMAscript 6 中新增了一個 API Promise
Promise 是一個構造函數
使用:
創建一個 Promise 容器
-
給別人一個承諾
Promise 容器一旦創建,就開始執行里面的代碼
new Promise(function () { //一個異步任務 })
注意:承諾本身不是異步的,只是它存放的異步任務是異步的。Promise 本身不是異步的,但是內部往往都是封裝一個異步任務
Promise 的具體使用:
[圖片上傳失敗...(image-d65b7a-1536318692721)]
var p1 = new Promise(function (resolve, reject) {
fs.readFile('a.txt', function (err, data) { //異步任務
if (err) {
//容器中的任務失敗
//把容器中的 Pending 狀態變為 Rejected
reject(err)
} else {
//承諾容器中的任務成功了
//把容器中的 Pending 狀態改為成功 Resolved
//也就是說這里調用的resolve 方法實際上就是then 方法傳遞的那個 function
resolve(data)
}
})
})
//p1 就是那個承諾
//當p1 成功了 然后(then) 做指定的操作
//then 方法接收的第一個 function 就是容器中的 resolve 函數中的數據
//調用了reject 就相當于調用了 then 方法的第二個參數函數
p1
.then(function (data) {
console.log(data)
//return 123
// 當前成功函數 return 的結果就可以在后面的 then 中function 接收到
//當你 return 123 后面就會接收到 123
//return hello 后面就會接收到 'hello'
// 沒有 return 后面收到的就是哦undefined
// 上面這些事原理
// 真正使用的是:我們可以 return 一個 Promise 對象
//當 return 一個 Promise 對象的時候,后續的 then 中的方法的第一個參數會作為 p2 的 resolve函數的結果
return p2
}, function (err) {
console.log('讀取文件失敗', err)
})
以上情況稱為異步調用鏈式編程。
-
Promise 應用場景
在開發的過程中,通過多個接口獲取的數據或者多個ajax請求獲取的數據,可以使用Promise 來寫
jquery 中的ajax 方法已經支持 Promise ,可以使用 ==then(Promise對象使用then 方法)== 方法。
在需要==不同的請求==獲取結果的時候,需要使用Promise解決方案,可以鏈式編程獲取異步處理結果。
-
==callback 注意事項(開發中的小事)==:
[圖片上傳失敗...(image-97a1fd-1536318692721)]
圖中的fn() 函數調用沒有傳遞 callback 參數(會出錯),因此需要使用圖中到的解決方案
注意:
==1. 要使用 .then() 方法,必須要支持 Promise==
==2. mongoose 的查詢操作都是異步操作,都可以使用 .then 方法==
==3. .then() 方法的執行,必須要等到 Promise 的狀態改變==
中間件概念
middlewares
-
例如開發一個模塊解決一個功能
一般采取如下的形式
module.exports = function (req, res) { req.body = {} } //在調用的模塊中使用下列函數 query(req) //把req 對象傳遞到執行函數中
調用返回的函數,傳遞req 對象就可以讓該對象擁有body 屬性。例如在express中使用 bodyParse 的配置和 template 的配置,都需要先配置之后才會給 req 對象添加一個方法
Express 中間件(app.use的用法)
中間件: 處理請求的,本質就是函數。
==中間件主要執行規則就是前面的中間件會對后面的中間件進行封堵。next() 方法也要求匹配,如果路徑不匹配也不行。next 是執行后面的,不是執行后一個==
在 Express 中,對中間件有幾種分類。
同一個請求所經歷的中間件都是同一個請求對象和響應對象。傳遞相同的req 對象和 res 對象。
1. 應用程序級別中間件
-
不關心請求路徑的請求方法的中間件
也就是說任何請求(不關心請求路徑和請求方法)都會進入這個中間件
使用 app.use() 方法來調用,參數一般為函數
app.use(function (req, res, next) { })
?
-
中間件本身就是一個方法,該方法接收三個參數
Request 請求對象
Response 響應對象
-
next 下一個中間件,next 本身為函數,默認中間件不會執行下一個中間件,調用 next() 方法之后,會執行下一個中間件。
app.use(function (req, res, next) { console.log(1) next() }) app.use(function (req, res, next) { console.log(2) })
-
-
當一個請求進入一個中間件之后,如果不調用 next 則會停留在當前中間件。所以next 是一個方法,用來調用下一個中間件的。
-
關心請求路徑的中間件(以 /xxx 開頭的路徑中間件)。
app.use('/a', function (req, res, next) { console.log(req.url) //此處打印的 url 是出去 /a 后面的url })
當請求路徑是以
a
開頭的情況,執行function 里面的代碼
當請求進來,會從第一個中間件開始進行匹配。(代碼順序)
? 如果匹配,則進來。
? 如果請求進入中間件之后,沒有調用next() 方法,則代碼會停留在這個中間件中。
? 如果調用了next 則向后查找匹配的中間件
? 如果不匹配,則繼續判斷匹配下一個中間件。
- 如果沒有能匹配的中間件,express 會默認輸出:Cannot GET 路徑(POST 請求則換成POST)
2. 路由級別的中間件
-
除了以上中間件之外,還有一種最常用的
嚴格匹配請求方法和請求路徑的中間件
app.get 和 app.post
app.get('/a', function (req, res, next) { console.log('aaa') next() }) app.get('/a', function (req, res, next) { console.log('bbb') })
上述例子中下面的不會覆蓋上面的中間件
總結
- express 框架下編程也可以使用原生的node編寫
- 原生的node 使用 res.end() 方法來結束請求,express 框架使用 res.send() 方法來結束請求
- 原生的node使用自己判斷的方式來開發靜態資源;express 框架使用 app.use() (中間件形式)方法來開放靜態資源