• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當前位置: 首頁 - 科技 - 知識百科 - 正文

    使用Node.js實現簡易MVC框架的方法

    來源:懂視網 責編:小采 時間:2020-11-27 22:33:24
    文檔

    使用Node.js實現簡易MVC框架的方法

    使用Node.js實現簡易MVC框架的方法:在使用Node.js搭建靜態資源服務器一文中我們完成了服務器對靜態資源請求的處理,但并未涉及動態請求,目前還無法根據客戶端發出的不同請求而返回個性化的內容。單靠靜態資源豈能撐得起這些復雜的網站應用,本文將介紹如何使用Node處理動態請求,以及如何搭建
    推薦度:
    導讀使用Node.js實現簡易MVC框架的方法:在使用Node.js搭建靜態資源服務器一文中我們完成了服務器對靜態資源請求的處理,但并未涉及動態請求,目前還無法根據客戶端發出的不同請求而返回個性化的內容。單靠靜態資源豈能撐得起這些復雜的網站應用,本文將介紹如何使用Node處理動態請求,以及如何搭建

    在使用Node.js搭建靜態資源服務器一文中我們完成了服務器對靜態資源請求的處理,但并未涉及動態請求,目前還無法根據客戶端發出的不同請求而返回個性化的內容。單靠靜態資源豈能撐得起這些復雜的網站應用,本文將介紹如何使用Node處理動態請求,以及如何搭建一個簡易的 MVC 框架。因為前文已經詳細介紹過靜態資源請求如何響應,本文將略過所有靜態部分。

    一個簡單的示例

    先從一個簡單示例入手,明白在 Node 中如何向客戶端返回動態內容。

    假設我們有這樣的需求:

    當用戶訪問/actors時返回男演員列表頁

    當用戶訪問/actresses時返回女演員列表

    可以用以下的代碼完成功能:

    const http = require('http');
    const url = require('url');
    
    http.createServer((req, res) => {
     const pathName = url.parse(req.url).pathname;
     if (['/actors', '/actresses'].includes(pathName)) {
     res.writeHead(200, {
     'Content-Type': 'text/html'
     });
     const actors = ['Leonardo DiCaprio', 'Brad Pitt', 'Johnny Depp'];
     const actresses = ['Jennifer Aniston', 'Scarlett Johansson', 'Kate Winslet'];
     let lists = [];
     if (pathName === '/actors') {
     lists = actors;
     } else {
     lists = actresses;
     }
    
     const content = lists.reduce((template, item, index) => {
     return template + `<p>No.${index+1} ${item}</p>`;
     }, `<h1>${pathName.slice(1)}</h1>`);
     res.end(content);
     } else {
     res.writeHead(404);
     res.end('<h1>Requested page not found.</h1>')
     }
    }).listen(9527);

    上面代碼的核心是路由匹配,當請求抵達時,檢查是否有對應其路徑的邏輯處理,當請求匹配不上任何路由時,返回 404。匹配成功時處理相應的邏輯。

    simple request

    上面的代碼顯然并不通用,而且在僅有兩種路由匹配候選項(且還未區分請求方法),以及尚未使用數據庫以及模板文件的前提下,代碼都已經有些糾結了。因此接下來我們將搭建一個簡易的MVC框架,使數據、模型、表現分離開來,各司其職。

    搭建簡易MVC框架

    MVC 分別指的是:

    M: Model (數據)

    V: View (表現)

    C: Controller (邏輯)

    在 Node 中,MVC 架構下處理請求的過程如下:

    請求抵達服務端

    服務端將請求交由路由處理

    路由通過路徑匹配,將請求導向對應的 controller

    controller 收到請求,向 model 索要數據

    model 給 controller 返回其所需數據

    controller 可能需要對收到的數據做一些再加工

    controller 將處理好的數據交給 view

    view 根據數據和模板生成響應內容

    服務端將此內容返回客戶端

    以此為依據,我們需要準備以下模塊:

    server: 監聽和響應請求

    router: 將請求交由正確的controller處理

    controllers: 執行業務邏輯,從 model 中取出數據,傳遞給 view

    model: 提供數據

    view: 提供 html

    創建如下目錄:

    -- server.js
    -- lib
     -- router.js
    -- views
    -- controllers
    -- models

    server

    創建 server.js 文件:

    const http = require('http');
    const router = require('./lib/router')();
    
    router.get('/actors', (req, res) => {
     res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
    });
    
    http.createServer(router).listen(9527, err => {
     if (err) {
     console.error(err);
     console.info('Failed to start server');
     } else {
     console.info(`Server started`);
     }
    });

    先不管這個文件里的細節,router是下面將要完成的模塊,這里先引入,請求抵達后即交由它處理。

    router 模塊

    router模塊其實只需完成一件事,將請求導向正確的controller處理,理想中它可以這樣使用:

    const router = require('./lib/router')();
    const actorsController = require('./controllers/actors');
    
    router.use((req, res, next) => {
     console.info('New request arrived');
     next()
    });
    
    router.get('/actors', (req, res) => {
     actorsController.fetchList();
    });
    
    router.post('/actors/:name', (req, res) => {
     actorsController.createNewActor();
    });

    總的來說,我們希望它同時支持路由中間件和非中間件,請求抵達后會由 router 交給匹配上的中間件們處理。中間件是一個可訪問請求對象和響應對象的函數,在中間件內可以做的事情包括:

    執行任何代碼,比如添加日志和處理錯誤等

    修改請求 (req) 和響應對象 (res),比如從 req.url 獲取查詢參數并賦值到 req.query

    結束響應

    調用下一個中間件 (next)

    Note:

    需要注意的是,如果在某個中間件內既沒有終結響應,也沒有調用 next 方法將控制權交給下一個中間件, 則請求就會掛起

    __非路由中間件__通過以下方式添加,匹配所有請求:

    router.use(fn);

    比如上面的例子:

    router.use((req, res, next) => {
     console.info('New request arrived');
     next()
    });

    __路由中間件__通過以下方式添加,以 請求方法和路徑精確匹配:

    router.HTTP_METHOD(path, fn)

    梳理好了之后先寫出框架:

    /lib/router.js

    const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'];
    
    module.exports = () => {
     const routes = [];
    
     const router = (req, res) => {
     
     };
    
     router.use = (fn) => {
     routes.push({
     method: null,
     path: null,
     handler: fn
     });
     };
    
     METHODS.forEach(item => {
     const method = item.toLowerCase();
     router[method] = (path, fn) => {
     routes.push({
     method,
     path,
     handler: fn
     });
     };
     });
    };

    以上主要是給 router 添加了 use、get、post 等方法,每當調用這些方法時,給 routes 添加一條 route 規則。

    Note:

    Javascript 中函數是一種特殊的對象,能被調用的同時,還可以擁有屬性、方法。

    接下來的重點在 router 函數,它需要做的是:

    從req對象中取得 method、pathname

    依據 method、pathname 將請求與routes數組內各個 route 按它們被添加的順序依次匹配

    如果與某個route匹配成功,執行 route.handler,執行完后與下一個 route 匹配或結束流程 (后面詳述)

    如果匹配不成功,繼續與下一個 route 匹配,重復3、4步驟

     const router = (req, res) => {
     const pathname = decodeURI(url.parse(req.url).pathname);
     const method = req.method.toLowerCase();
     let i = 0;
    
     const next = () => {
     route = routes[i++];
     if (!route) return;
     const routeForAllRequest = !route.method && !route.path;
     if (routeForAllRequest || (route.method === method && pathname === route.path)) {
     route.handler(req, res, next);
     } else {
     next();
     }
     }
    
     next();
     };

    對于非路由中間件,直接調用其 handler。對于路由中間件,只有請求方法和路徑都匹配成功時,才調用其 handler。當沒有匹配上的 route 時,直接與下一個route繼續匹配。

    需要注意的是,在某條 route 匹配成功的情況下,執行完其 handler 之后,還會不會再接著與下個 route 匹配,就要看開發者在其 handler 內有沒有主動調用 next() 交出控制權了。

    在__server.js__中添加一些route:

    router.use((req, res, next) => {
     console.info('New request arrived');
     next()
    });
    
    router.get('/actors', (req, res) => {
     res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
    });
    
    router.get('/actresses', (req, res) => {
     res.end('Jennifer Aniston, Scarlett Johansson, Kate Winslet');
    });
    
    router.use((req, res, next) => {
     res.statusCode = 404;
     res.end();
    });
    

    每個請求抵達時,首先打印出一條 log,接著匹配其他route。當匹配上 actors 或 actresses 的 get 請求時,直接發回演員名字,并不需要繼續匹配其他 route。如果都沒匹配上,返回 404。

    在瀏覽器中依次訪問 http://localhost:9527/erwe、http://localhost:9527/actors、http://localhost:9527/actresses 測試一下:

    404

    network 中觀察到的結果符合預期,同時后臺命令行中也打印出了三條 New request arrived語句。

    接下來繼續改進 router 模塊。

    首先添加一個 router.all 方法,調用它即意味著為所有請求方法都添加了一條 route:

    router.all = (path, fn) => {
     METHODS.forEach(item => {
     const method = item.toLowerCase();
     router[method](path, fn);
     })
     };

    接著,添加錯誤處理。

    /lib/router.js

    const defaultErrorHander = (err, req, res) => {
     res.statusCode = 500;
     res.end();
    };
    
    module.exports = (errorHander) => {
     const routes = [];
    
     const router = (req, res) => {
     ...
     errorHander = errorHander || defaultErrorHander;
    
     const next = (err) => {
     if (err) return errorHander(err, req, res);
     ...
     }
    
     next();
     };
    

    server.js

    ...
    const router = require('./lib/router')((err, req, res) => {
     console.error(err);
     res.statusCode = 500;
     res.end(err.stack);
    });
    ...

    默認情況下,遇到錯誤時會返回 500,但開發者使用 router 模塊時可以傳入自己的錯誤處理函數將其替代。

    修改一下代碼,測試是否能正確執行錯誤處理:

    router.use((req, res, next) => {
     console.info('New request arrived');
     next(new Error('an error'));
    });

    這樣任何請求都應該返回 500:

    error stack

    繼續,修改 route.path 與 pathname 的匹配規則。現在我們認為只有當兩字符串相等時才讓匹配通過,這沒有考慮到 url 中包含路徑參數的情況,比如:

    localhost:9527/actors/Leonardo

    router.get('/actors/:name', someRouteHandler);

    這條route應該匹配成功才是。

    新增一個函數用來將字符串類型的 route.path 轉換成正則對象,并存入 route.pattern:

    const getRoutePattern = pathname => {
     pathname = '^' + pathname.replace(/(\:\w+)/g, '\(\[a-zA-Z0-9-\]\+\\s\)') + '$';
     return new RegExp(pathname);
    };

    這樣就可以匹配上帶有路徑參數的url了,并將這些路徑參數存入 req.params 對象:

     const matchedResults = pathname.match(route.pattern);
     if (route.method === method && matchedResults) {
     addParamsToRequest(req, route.path, matchedResults);
     route.handler(req, res, next);
     } else {
     next();
     }
    const addParamsToRequest = (req, routePath, matchedResults) => {
     req.params = {};
     let urlParameterNames = routePath.match(/:(\w+)/g);
     if (urlParameterNames) {
     for (let i=0; i < urlParameterNames.length; i++) {
     req.params[urlParameterNames[i].slice(1)] = matchedResults[i + 1];
     }
     }
    }

    添加個 route 測試一下:

    router.get('/actors/:year/:country', (req, res) => {
     res.end(`year: ${req.params.year} country: ${req.params.country}`);
    });

    訪問http://localhost:9527/actors/1990/China試試:

    url parameters

    router 模塊就寫到此,至于查詢參數的格式化以及獲取請求主體,比較瑣碎就不試驗了,需要可以直接使用 bordy-parser 等模塊。

    現在我們已經創建好了router模塊,接下來將 route handler 內的業務邏輯都轉移到 controller 中去。

    修改__server.js__,引入 controller:

    ...
    const actorsController = require('./controllers/actors');
    ...
    router.get('/actors', (req, res) => {
     actorsController.getList(req, res);
    });
    
    router.get('/actors/:name', (req, res) => {
     actorsController.getActorByName(req, res);
    });
    
    router.get('/actors/:year/:country', (req, res) => {
     actorsController.getActorsByYearAndCountry(req, res);
    });
    ...

    新建__controllers/actors.js__:

    const actorsTemplate = require('../views/actors-list');
    const actorsModel = require('../models/actors');
    
    exports.getList = (req, res) => {
     const data = actorsModel.getList();
     const htmlStr = actorsTemplate.build(data);
     res.writeHead(200, {
     'Content-Type': 'text/html'
     });
     res.end(htmlStr);
    };
    
    exports.getActorByName = (req, res) => {
     const data = actorsModel.getActorByName(req.params.name);
     const htmlStr = actorsTemplate.build(data);
     res.writeHead(200, {
     'Content-Type': 'text/html'
     });
     res.end(htmlStr);
    };
    
    exports.getActorsByYearAndCountry = (req, res) => {
     const data = actorsModel.getActorsByYearAndCountry(req.params.year, req.params.country);
     const htmlStr = actorsTemplate.build(data);
     res.writeHead(200, {
     'Content-Type': 'text/html'
     });
     res.end(htmlStr);
    };
    

    在 controller 中同時引入了 view 和 model, 其充當了這二者間的粘合劑。回顧下 controller 的任務:

    controller 收到請求,向 model 索要數據
    model 給 controller 返回其所需數據
    controller 可能需要對收到的數據做一些再加工
    controller 將處理好的數據交給 view

    在此 controller 中,我們將調用 model 模塊的方法獲取演員列表,接著將數據交給 view,交由 view 生成呈現出演員列表頁的 html 字符串。最后將此字符串返回給客戶端,在瀏覽器中呈現列表。

    從 model 中獲取數據

    通常 model 是需要跟數據庫交互來獲取數據的,這里我們就簡化一下,將數據存放在一個 json 文件中。

    /models/test-data.json

    [
     {
     "name": "Leonardo DiCaprio",
     "birth year": 1974,
     "country": "US",
     "movies": ["Titanic", "The Revenant", "Inception"]
     },
     {
     "name": "Brad Pitt",
     "birth year": 1963,
     "country": "US",
     "movies": ["Fight Club", "Inglourious Basterd", "Mr. & Mrs. Smith"]
     },
     {
     "name": "Johnny Depp",
     "birth year": 1963,
     "country": "US",
     "movies": ["Edward Scissorhands", "Black Mass", "The Lone Ranger"]
     }
    ]

    接著就可以在 model 中定義一些方法來訪問這些數據。

    models/actors.js

    const actors = require('./test-data');
    
    exports.getList = () => actors;
    
    exports.getActorByName = (name) => actors.filter(actor => {
     return actor.name == name;
    });
    
    exports.getActorsByYearAndCountry = (year, country) => actors.filter(actor => {
     return actor["birth year"] == year && actor.country == country;
    });
    

    當 controller 從 model 中取得想要的數據后,下一步就輪到 view 發光發熱了。view 層通常都會用到模板引擎,如 dust 等。同樣為了簡化,這里采用簡單替換模板中占位符的方式獲取 html,渲染得非常有限,粗略理解過程即可。

    創建 /views/actors-list.js:

    const actorTemplate = `
    <h1>{name}</h1>
    <p><em>Born: </em>{contry}, {year}</p>
    <ul>{movies}</ul>
    `;
    
    exports.build = list => {
     let content = '';
     list.forEach(actor => {
     content += actorTemplate.replace('{name}', actor.name)
     .replace('{contry}', actor.country)
     .replace('{year}', actor["birth year"])
     .replace('{movies}', actor.movies.reduce((moviesHTML, movieName) => {
     return moviesHTML + `<li>${movieName}</li>`
     }, ''));
     });
     return content;
    };
    
    

    在瀏覽器中測試一下:

    test mvc

    至此,就大功告成啦!

    以上這篇使用Node.js實現簡易MVC框架的方法就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

    聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    使用Node.js實現簡易MVC框架的方法

    使用Node.js實現簡易MVC框架的方法:在使用Node.js搭建靜態資源服務器一文中我們完成了服務器對靜態資源請求的處理,但并未涉及動態請求,目前還無法根據客戶端發出的不同請求而返回個性化的內容。單靠靜態資源豈能撐得起這些復雜的網站應用,本文將介紹如何使用Node處理動態請求,以及如何搭建
    推薦度:
    標簽: js 簡單的 node.js
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 午夜精品福利视频| 久久精品一区二区影院| 精品国产午夜肉伦伦影院| 青青草原精品国产亚洲av| 久久精品国产亚洲5555| 午夜精品视频在线| 国产成人精品999在线观看| 人妻无码久久精品| 国产日韩高清三级精品人成| 久久成人国产精品二三区| 久久久国产精品亚洲一区| 亚洲精品456播放| 精品午夜福利1000在线观看| 最新国产の精品合集| 国产精品久久久久久福利69堂| 亚洲AV无码精品色午夜在线观看| 日韩精品电影一区亚洲| 国产午夜精品久久久久九九电影| 久久99热精品| 国产精品视频第一页| 精品爆乳一区二区三区无码av| 亚洲热线99精品视频| 日韩一区二区精品观看| 久久亚洲中文字幕精品一区| 国产乱人伦偷精品视频免观看| 伊人久久大香线蕉精品| 四虎4hu永久免费国产精品| 精品999在线| 国产在线拍揄自揄视精品不卡| 99久久er这里只有精品18| 国产精品视频一区二区三区四| 老司机67194精品线观看| 亚洲精品无码专区久久久| 亚洲精品NV久久久久久久久久| 人妻VA精品VA欧美VA| 日本午夜精品理论片A级APP发布| 无码欧精品亚洲日韩一区夜夜嗨| 精品午夜国产人人福利| 日韩精品欧美国产在线| 一本大道无码日韩精品影视| 中文字幕一区二区三区日韩精品|