寫(xiě)在前面
昨天說(shuō)的npm看了之后, 覺(jué)得有兩個(gè)指的記錄, 再就是寫(xiě)下Express
npm補(bǔ)充
npm2和npm3的區(qū)別
就本地安裝的包來(lái)說(shuō), npm2
和npm3
有一個(gè)很大的區(qū)別, 就是組織包的結(jié)構(gòu). npm2
組織依賴的包是按照樹(shù)形組織的. npm3
將其改進(jìn)為扁平結(jié)構(gòu).
npm2
會(huì)將所依賴的包存放到當(dāng)前目錄的./node_modules/
目錄下. 而被安裝的包又會(huì)依賴其他的包的話, 則會(huì)存放到該包的./node_modules
下. 所以, 當(dāng)依賴結(jié)構(gòu)很復(fù)雜的時(shí)候, 目錄結(jié)構(gòu)會(huì)非常深. 不管是性能還是操作上, 體驗(yàn)都不怎么好.
your_project/
node_modules/
module_a@1.0/
node_modules/
module_b@1.0/
module_c@1.0/
node_modules/
module_b@1.0
module_d@1.0/
node_modules/
module_b@2.0/
而在npm3
中, 采用扁平的目錄結(jié)構(gòu), 二級(jí)依賴會(huì)放到當(dāng)前目錄的node_modules
的里, 與一級(jí)包在同一目錄.
your_project/
node_modules/
module_a@1.0/
module_b@1.0
module_c@1.0/
module_d@1.0/
node_modules/
module_b@2.0/
盡量都安裝到項(xiàng)目目錄下的./node_modules
中. 但是, 這樣會(huì)遇到幾個(gè)問(wèn)題.
由于安裝順序不同, ./node_modules
的目錄結(jié)構(gòu)也可能不同, 這樣導(dǎo)致即使是相同的依賴關(guān)系, 目錄結(jié)構(gòu)也不一定一樣. 例如在npm3
例子中, 如果先安裝module_d
的話, module_b@2.0
則會(huì)和module_d
同級(jí), 這樣module_a
和module_c
所依賴的module_b@1.0
則會(huì)在各自的./node_modules
目錄下, 即使是重復(fù)的. 像下面這樣:
your_project/
node_modules/
module_a@1.0/
node_modules/
module_b@1.0
module_c@1.0/
node_modules/
module_b@1.0
module_d@1.0/
module_b@2.0/
雖然一般情況下, 這個(gè)并不影響項(xiàng)目運(yùn)行, 但是如果要達(dá)成目錄結(jié)構(gòu)一致的話, 解決方法是, 刪除./node_modules
目錄下的所有文件, 重新安裝. 因?yàn)?code>npm會(huì)按照依賴包的字符表順序排序后, 按照這個(gè)順序進(jìn)行安裝.
npm安裝全局包遇到權(quán)限問(wèn)題
還有一點(diǎn), 就是在Linux下安裝npm
全局包的話, 會(huì)遇到權(quán)限問(wèn)題. 我以前一般是用sudo
添加權(quán)限. 而又兩個(gè)方法更好:
1, 用chown
來(lái)改變目錄的所有者, 全局包安裝在npm config get prefix
目錄下, 將這個(gè)目錄的所有者改成運(yùn)行項(xiàng)目的人就行.
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
2, 利用npm config set prefix <new_dir>
設(shè)置到當(dāng)前用戶擁有讀寫(xiě)權(quán)限的目錄下. 再講這個(gè)路徑加到PATH
中.
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH
source ~/.profile
如果不想更改PATH
系統(tǒng)變量, 可以這樣. 由于npm
最后會(huì)在/usr/local/bin
目錄下創(chuàng)建鏈接文件, 指向包的入口文件, 所以這樣也是OK的.
NPM_CONFIG_PREFIX=~/.npm-global npm install -g jshint
Express
在Express
中, 采用的是一種中間件的方式對(duì)請(qǐng)求進(jìn)行處理.
+--------+--------+--------+--------+
req -> | MW1 | MW2 | MW3 | MW4 |
| -----> | -----> | -----> | --- |
| \ | \ | \ | \ |
res <- | <-/ | <-/ | <-/ | <-/ |
+--------+--------+--------+--------+
HTTP
請(qǐng)求會(huì)依次經(jīng)過(guò)每個(gè)中間件處理, 每個(gè)中間件可以選擇處理該請(qǐng)求, 返回, 或者交給下一個(gè)中間件繼續(xù)處理. 類似于Java Web的filter. 而末尾的中間件, 可以看做成action
.
寫(xiě)Express
應(yīng)用一般是這樣.
var express = require('express');
var app = express();
// respond with "hello world" when a GET request is made to the homepage
app.get('/', function(req, res) {
res.send('hello world');
});
第一行加載Express
模塊, 第二行創(chuàng)建Express
對(duì)象. app.get
就是設(shè)置一個(gè)中間件, 這個(gè)中間件只處理GET
請(qǐng)求. app.get
第一個(gè)參數(shù)是當(dāng)HTTP
請(qǐng)求的URL
匹配這個(gè)參數(shù)的時(shí)候, 運(yùn)行第二個(gè)參數(shù)傳入函數(shù).
每個(gè)中間件就是一個(gè)接受三個(gè)參數(shù)的函數(shù).
function middleware(req, res, next){
// your code
}
而Express
加載中間件的方式也有很多
app.use(function(req, res, next){
// your code.
});
app.use('url_pattern_string', function(req, res, next){
// your code.
});
使用app
對(duì)象有很多方法, 其中, use
表示所有HTTP
請(qǐng)求都需要經(jīng)過(guò)這個(gè)中間件, 還有幾個(gè)針對(duì)HTTP
請(qǐng)求類型的, 比如get
, post
, put
, delete
等等. 這些只處理相應(yīng)類型的HTTP
請(qǐng)求, 不處理除此之外其他類型的請(qǐng)求.
更好的組織代碼的方式是利用Router
. 在router
對(duì)象上設(shè)置各種處理, 然后將這個(gè)router
作為中間件使用.
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/index', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
然后再添加到app
中
var express = require('express');
var routes = require('./routes/index');
app.use('/routes', routes);
這樣, 訪問(wèn)/routes/index
會(huì)首先匹配到route
對(duì)象, 然后在這個(gè)對(duì)象進(jìn)行下一步的匹配, 匹配到/index
, 就執(zhí)行這里的方法, 渲染index
模板并返回給瀏覽器.
總體看來(lái), 可以認(rèn)為Express
是對(duì)HTTP
請(qǐng)求的URL
和中間件進(jìn)行匹配的. 匹配過(guò)程中會(huì)找到一些符合的中間件, 執(zhí)行這些中間件. 中間件有很多種, 有針對(duì)某種HTTP
請(qǐng)求類型的中間件, 也有不管HTTP
請(qǐng)求類型, 都要執(zhí)行的中間件. 這都是通過(guò)在設(shè)置中間件時(shí), 使用哪種方法設(shè)置來(lái)區(qū)別的.
app.use() // HTTP 所有類型的請(qǐng)求
app.get() // HTTP GET請(qǐng)求
app.post() // HTTP POST請(qǐng)求
...
Express
支持很多請(qǐng)求類型.
最重要的還有中間件接受的參數(shù), req
, res
,和next
.
Request
Request
用來(lái)封裝HTTP
請(qǐng)求的信息.
路由匹配過(guò)程中可以匹配參數(shù).
app.get('/user/:id', function(req, res){
res.end('' + req.params.id);
});
get參數(shù)可以在req.query
中訪問(wèn)
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse"
訪問(wèn)HTTP請(qǐng)求頭
req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined
其他的方法可以查Express 4.x Request
Response
res.locals針對(duì)當(dāng)前請(qǐng)求, 保存一些信息, 可以被模板引擎直接訪問(wèn), 也可以在其他的中間件中訪問(wèn)到. 不同請(qǐng)求, locals是新的對(duì)象, 請(qǐng)求之間是隔離的.
res.locals.title = 'Express';
res.render('index'); // 在模板引擎中可以直接訪問(wèn)title變量, 值為上面指定的`Express
app.use(function(req, res, next){
res.locals.user = req.user;
res.locals.authenticated = ! req.user.anonymous;
next(); // 下一個(gè)中間件可以訪問(wèn)這兩個(gè)變量
});
通過(guò)res可以設(shè)置cookie
// res.cookie(name, value [, options])
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
響應(yīng)有很多種
-
res.json([body])
返回JSON
-
res.jsonp([body])
返回JSONP
-
res.download(path [, filename] [, fn])
, 返回文件, 使瀏覽器下載 -
res.redirect([status,] path)
返回重定向 -
res.render(view [, locals] [, callback])
進(jìn)行模板渲染, 然后傳輸給瀏覽器
進(jìn)一步的參考Express 4.x Response
最后
在Koa
出來(lái)之前, Express
很腫脹, 到了4.x
的時(shí)候, Express將很多東西都作為中間件單獨(dú)提供, 清爽很多, 而且官網(wǎng)首頁(yè)的一句話介紹也改了. 有過(guò)一段時(shí)間, 想學(xué)學(xué)connect
. 但是放棄了, 覺(jué)得Express
更方便一些.
對(duì)于Koa
, 使用generator function
的確很好, 但是函數(shù)的上下文有點(diǎn)混亂, 等Express的文檔看完了, 有空好好學(xué)學(xué)
ES6`
對(duì)于看ES6
的內(nèi)容來(lái)說(shuō), 大概看了下, 覺(jué)得阮一峰的<ECMAScript 6 入門>寫(xiě)的非常好. JavaScript
語(yǔ)言有很多不符合直覺(jué)的地方, 很別扭, 比如:
// Chrome 版本 47.0.2526.111 m (64-bit)
[] + []
// => ""
{} + []
// => 0
[] + {}
// => "[object Object]"
{} + {}
// => NaN
NaN === NaN
// => false
// 還有很多...
JavaScript
也有很多不好的地方. 比如不同瀏覽器支持的程度不一樣, 新的標(biāo)準(zhǔn)出來(lái)了, 瀏覽器要跟上也得過(guò)段時(shí)間; 不同瀏覽器又會(huì)實(shí)驗(yàn)性地拓展內(nèi)容; 為了使用瀏覽器未實(shí)現(xiàn)的語(yǔ)言標(biāo)準(zhǔn), 出現(xiàn)了中間層來(lái)轉(zhuǎn)換(babel). 為了磨平瀏覽器對(duì)于語(yǔ)言支持的區(qū)別, 也出現(xiàn)了shim / polyfill; ES6還沒(méi)學(xué)會(huì)呢, ES7就快來(lái)了(已經(jīng)有不少草案了);
雖然JavaScript
作為服務(wù)器端腳本, 相對(duì)其他的腳本語(yǔ)言有所欠缺, 但是我等相信, 未來(lái)更好.