<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答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
        主站蜘蛛池模板: 亚洲综合激情另类专区| a毛看片免费观看视频| www视频在线观看免费| 国产亚洲精AA在线观看SEE| 国产日韩精品无码区免费专区国产| 国产男女猛烈无遮挡免费网站| 亚洲av成人片在线观看| 国产成人免费网站在线观看 | 精品国产免费观看一区| 亚洲色成人四虎在线观看| 精品久久久久久久免费加勒比| 亚洲狠狠婷婷综合久久| 四虎免费影院4hu永久免费| 国产精品免费久久| 亚洲Av无码专区国产乱码DVD| 免费A级毛片av无码| 亚洲视频中文字幕在线| 大学生一级毛片免费看| 亚洲精华国产精华精华液| 啊v在线免费观看| 99视频在线观看免费| 91亚洲精品第一综合不卡播放| 在线观看日本免费a∨视频| 久久精品国产亚洲av天美18| 亚洲国产成人影院播放| 免费在线看污视频| 亚洲人成影院77777| 亚洲?v无码国产在丝袜线观看| 精品国产污污免费网站| 亚洲情A成黄在线观看动漫软件| 国产精品免费综合一区视频| 黄桃AV无码免费一区二区三区| 精品亚洲国产成AV人片传媒| 女性无套免费网站在线看| 中文字幕不卡免费视频| 亚洲精品免费在线| 亚洲AV无码一区二区三区国产| 国产免费一区二区视频| 亚洲AV无码XXX麻豆艾秋| 亚洲AV无码精品色午夜在线观看| 中文字幕无码成人免费视频|