Day3: npm補(bǔ)充 和 Express

寫在前面


昨天說的npm看了之后, 覺得有兩個指的記錄, 再就是寫下Express

npm補(bǔ)充


npm2和npm3的區(qū)別


就本地安裝的包來說, npm2npm3有一個很大的區(qū)別, 就是組織包的結(jié)構(gòu). npm2組織依賴的包是按照樹形組織的. npm3將其改進(jìn)為扁平結(jié)構(gòu).

npm2會將所依賴的包存放到當(dāng)前目錄的./node_modules/目錄下. 而被安裝的包又會依賴其他的包的話, 則會存放到該包的./node_modules下. 所以, 當(dāng)依賴結(jié)構(gòu)很復(fù)雜的時候, 目錄結(jié)構(gòu)會非常深. 不管是性能還是操作上, 體驗都不怎么好.

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), 二級依賴會放到當(dāng)前目錄的node_modules的里, 與一級包在同一目錄.

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/

盡量都安裝到項目目錄下的./node_modules中. 但是, 這樣會遇到幾個問題.

由于安裝順序不同, ./node_modules的目錄結(jié)構(gòu)也可能不同, 這樣導(dǎo)致即使是相同的依賴關(guān)系, 目錄結(jié)構(gòu)也不一定一樣. 例如在npm3例子中, 如果先安裝module_d的話, module_b@2.0則會和module_d同級, 這樣module_amodule_c所依賴的module_b@1.0則會在各自的./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/

雖然一般情況下, 這個并不影響項目運(yùn)行, 但是如果要達(dá)成目錄結(jié)構(gòu)一致的話, 解決方法是, 刪除./node_modules目錄下的所有文件, 重新安裝. 因為npm會按照依賴包的字符表順序排序后, 按照這個順序進(jìn)行安裝.

npm安裝全局包遇到權(quán)限問題


還有一點(diǎn), 就是在Linux下安裝npm全局包的話, 會遇到權(quán)限問題. 我以前一般是用sudo添加權(quán)限. 而又兩個方法更好:

1, 用chown來改變目錄的所有者, 全局包安裝在npm config get prefix目錄下, 將這個目錄的所有者改成運(yùn)行項目的人就行.

sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

2, 利用npm config set prefix <new_dir>設(shè)置到當(dāng)前用戶擁有讀寫權(quán)限的目錄下. 再講這個路徑加到PATH中.

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH
source ~/.profile

如果不想更改PATH系統(tǒng)變量, 可以這樣. 由于npm最后會在/usr/local/bin目錄下創(chuàng)建鏈接文件, 指向包的入口文件, 所以這樣也是OK的.

NPM_CONFIG_PREFIX=~/.npm-global npm install -g jshint

Express


Express中, 采用的是一種中間件的方式對請求進(jìn)行處理.

       +--------+--------+--------+--------+
req -> |   MW1  |   MW2  |   MW3  |   MW4  |
       | -----> | -----> | -----> | ---    |
       |    \   |    \   |    \   |    \   |
res <- |  <-/   |  <-/   |  <-/   |  <-/   |
       +--------+--------+--------+--------+

HTTP請求會依次經(jīng)過每個中間件處理, 每個中間件可以選擇處理該請求, 返回, 或者交給下一個中間件繼續(xù)處理. 類似于Java Web的filter. 而末尾的中間件, 可以看做成action.

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對象. app.get就是設(shè)置一個中間件, 這個中間件只處理GET請求. app.get第一個參數(shù)是當(dāng)HTTP請求的URL匹配這個參數(shù)的時候, 運(yùn)行第二個參數(shù)傳入函數(shù).

每個中間件就是一個接受三個參數(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對象有很多方法, 其中, use表示所有HTTP請求都需要經(jīng)過這個中間件, 還有幾個針對HTTP請求類型的, 比如get, post, put, delete等等. 這些只處理相應(yīng)類型的HTTP請求, 不處理除此之外其他類型的請求.

更好的組織代碼的方式是利用Router. 在router對象上設(shè)置各種處理, 然后將這個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);

這樣, 訪問/routes/index會首先匹配到route對象, 然后在這個對象進(jìn)行下一步的匹配, 匹配到/index, 就執(zhí)行這里的方法, 渲染index模板并返回給瀏覽器.

總體看來, 可以認(rèn)為Express是對HTTP請求的URL和中間件進(jìn)行匹配的. 匹配過程中會找到一些符合的中間件, 執(zhí)行這些中間件. 中間件有很多種, 有針對某種HTTP請求類型的中間件, 也有不管HTTP請求類型, 都要執(zhí)行的中間件. 這都是通過在設(shè)置中間件時, 使用哪種方法設(shè)置來區(qū)別的.

app.use() // HTTP 所有類型的請求
app.get() // HTTP GET請求
app.post() // HTTP POST請求
...

Express支持很多請求類型.

最重要的還有中間件接受的參數(shù), req, res,和next.

Request


Request用來封裝HTTP請求的信息.

路由匹配過程中可以匹配參數(shù).

app.get('/user/:id', function(req, res){
  res.end('' + req.params.id);
});

get參數(shù)可以在req.query中訪問

// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse

req.query.order
// => "desc"

req.query.shoe.color
// => "blue"

req.query.shoe.type
// => "converse"

訪問HTTP請求頭

req.get('Content-Type');
// => "text/plain"

req.get('content-type');
// => "text/plain"

req.get('Something');
// => undefined

其他的方法可以查Express 4.x Request

Response


res.locals針對當(dāng)前請求, 保存一些信息, 可以被模板引擎直接訪問, 也可以在其他的中間件中訪問到. 不同請求, locals是新的對象, 請求之間是隔離的.

res.locals.title = 'Express';
res.render('index'); // 在模板引擎中可以直接訪問title變量, 值為上面指定的`Express

app.use(function(req, res, next){
  res.locals.user = req.user;
  res.locals.authenticated = ! req.user.anonymous;
  next(); // 下一個中間件可以訪問這兩個變量
});

通過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出來之前, Express很腫脹, 到了4.x的時候, Express將很多東西都作為中間件單獨(dú)提供, 清爽很多, 而且官網(wǎng)首頁的一句話介紹也改了. 有過一段時間, 想學(xué)學(xué)connect. 但是放棄了, 覺得Express更方便一些.

對于Koa, 使用generator function的確很好, 但是函數(shù)的上下文有點(diǎn)混亂, 等Express的文檔看完了, 有空好好學(xué)學(xué)ES6`

對于看ES6的內(nèi)容來說, 大概看了下, 覺得阮一峰的<ECMAScript 6 入門>寫的非常好. JavaScript語言有很多不符合直覺的地方, 很別扭, 比如:

// Chrome 版本 47.0.2526.111 m (64-bit)
[] + []
// => ""
{} + []
// => 0
[] + {}
// => "[object Object]"
{} + {}
// => NaN
NaN === NaN
// => false

// 還有很多...

JavaScript也有很多不好的地方. 比如不同瀏覽器支持的程度不一樣, 新的標(biāo)準(zhǔn)出來了, 瀏覽器要跟上也得過段時間; 不同瀏覽器又會實驗性地拓展內(nèi)容; 為了使用瀏覽器未實現(xiàn)的語言標(biāo)準(zhǔn), 出現(xiàn)了中間層來轉(zhuǎn)換(babel). 為了磨平瀏覽器對于語言支持的區(qū)別, 也出現(xiàn)了shim / polyfill; ES6還沒學(xué)會呢, ES7就快來了(已經(jīng)有不少草案了);

雖然JavaScript作為服務(wù)器端腳本, 相對其他的腳本語言有所欠缺, 但是我等相信, 未來更好.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容