Express 框架是什么?
Express 框架是一个基于 Node 平台的 web 应用开发框架,它提供了一系列强大特性,帮助我们创建各种 Web 应用。使用 npm install express 进行下载。在此只以学习前端的目的去了解这个框架,想了解更多可以查阅 Express 官网 介绍
Express 框架具有哪些特性?
原生 Node.js 与 Express 比较(服务器创建、路由定义、参数处理)。
1、创建服务原生 Node.js 调用 http 模块下的 createServer() 方法,使用 Express 框架只需要调用 express 模块返回的函数即可。
// 原生Node.js
const http = require('http');
const app = http.createServer();
app.listen(80);
// 使用Express框架
const express = require('express');
const app = express();
app.listen(80);
2、路由定义原生 Node.js 必须使用 url 模块对地址进行解析并根据地址和请求方式使用分支语句定义路由(确定不同请求和响应的映射关系)。Express 框架则使用中间件机制对路由定义进行了封装,定义路由非常方便并且提高了代码可维护性。
const url = require('url');
// 原生Node.js路由定义
app.on('request', (req, res) => {
// 利用url模块的parse方法解析请求路径并解构获得路径内容
let { pathname } = url.parse(req.url);
// 获取请求方式
let method = req.method.toLowerCase();
// 定义路由
if (pathname == '/' || pathname == '/index') {
res.end('这是首页响应程序');
} else if (pathname == '/list') {
if (method == 'get') {
res.end('请求路径为/list的get请求');
} else {
res.end('请求路径为/list的post请求');
}
} else if (pathname == '/about') {
if (method == 'get') {
res.end('请求路径为/about的get请求');
} else {
res.end('请求路径为/about的post请求');
}
} else {
res.end('您访问的网页不存在');
}
})
// Express框架
app.get('/', (req, res) => {
res.send('首页');
});
app.post('/login', (req, res) => {
res.send('使用post请求方式请求/login路由');
})
题外知识:第三方模块 router 实现路由功能
// 引入router第三方模块
const getRouter = require('router');
// 获取路由对象
const router = getRouter();
// 调用路由对象下的方法创建路由
router.get('/add', (req, res) => {
res.end('添加页面');
});
router.post('/login', (req, res) => {
res.end('登录页面');
});
app.on('request', () => {
// 启用路由
router(req, res);
})
3、原生 Node.js 获取 get 参数时使用 url 模块将请求地址解析并解构后可获得,post 参数则可以借助系统模块 querystring 对获取过来的 post 参数进行解析,但是获取的过程依旧相当繁琐。Express 框架中 get 请求参数可以直接使用 req.query 获取,post 参数通过第三方模块 body-parser 获取。
// 原生Node.js获取参数
const url = require('url');
const querystring = require('querystring');
app.on('require', (req, res) => {
// 获取get参数,添加true参数该字段值才是对象否则还是字符串
let { query } = url.parse(req.url, true);
// 获取post参数
let postData = '';
app.on('data', (chunk) => {
postData += chunk;
});
app.on('end', () => {
console.log(querystring.parse(postData));
});
});
// Express 框架获取参数
const bodyParser = require('body-parser');
// 配置post参数的解析格式
app.use(bodyParser.urlencoded({ extended: false }));
// 直接可以获得json格式的参数
app.post('/', (req, res) => {
// 获取get参数
console.log(req.query);
// 获取post参数
console.log(req.body);
})
bodyParser.urlencoded({extended: false}) 实际上返回一个函数,在函数内部封装了相应的参数处理程序,处理好参数后将 post 参数定义为 req 对象的 body 属性并调用 next() 方法将请求控制权交给下一个路由。对象参数的 extended: false 属性表示底层依旧使用 querystring 模块处理参数,如果值为 true 则使用另一个功能更加强大的模块 qs 。
4、原生 Node.js 访问静态资源时需要使用内置模块 file 读取文件,使用 writeHeader() 方法设置响应内容类型、http 状态码和编码方式等再响应给客户端,过程非常繁琐。而 Express 框架处理这些问题非常简单。并且响应时使用的是 send() 方法而不是原生的 end() 方法,它会自动检测响应内容类型、编码和设置 http 状态码。
1、什么是中间件?什么又是第三方中间件?
中间件本质上是 Express 框架为便利开发者而封装好的一系列方法。其可以接收客户端发来的请求并做出响应,也可以将请求继续交给下一个中间件处理。Express 是一款轻量级的框架,本身不会提供大量的中间件。但是支持大量第三方中间件(类似于插件,别人封装好的中间件方法,使用前需要下载),常用的有 body-parser、cookie-parse 和 express-session 等。
2、中间件具有哪些相似的语法?
中间件方法和请求处理函数是中间件的主要组成。它们用于拦截请求,参数均有一个请求回调函数负责处理请求。
app.get('请求路径', '处理函数'); // 拦截处理get请求
app.post('请求路径', '处理函数'); // 拦截处理post请求
3、中间件的匹配规则是怎样的?
首先明确:可以针对一个请求设置多个中间件(一个请求可以匹配多个中间件),对一个请求进行多次处理。默认情况下,请求自上而下依次匹配中间件,一旦匹配成功,终止匹并执行请求处理函数。可以在请求处理函数内调用 next() 方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件(如果不调用 next 函数则结束)。
app.get('/login', (req, res, net) => {
req.name = 'TKOP_';
// 将控制权交给下一个中间件
next();
});
app.get('/login', (req.res) => {
res.send(req.name);
})
1、中间件 use() 的用法
app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求(同时忽略请求方式和请求地址的匹配)。
当然第一个参数也可以传入请求地址,这时就需要进行匹配相应的请求地址。
// 只传入请求处理函数
app.use((req, res, next) => {
console.log(req.url);
// 将控制权交给下一个中间件
next();
})
// 传入请求地址和请求处理函数
app.use('/admin', (req, res, next) => {
console.log(req.url);
next();
})
2、use() 中间件常见应用场景
3、错误处理中间件
在程序执行过程中,可能会发生不可预知的错误,例如文件读取失败,数据库无法连接等。错误中间件是一个集中处理错误的方法。它的处理函数一共有 4 个参数,这也是它与其他中间件的主要区别。
app.use((err, req, res, next) => {
// 可以通过err对象下的message属性获取错误信息
console.log(err.message);
// 发送500状态码并响应
res.status(500).send('服务器发生未知错误');
});
当程序出错时,调用 next() 方法,并将错误对象通过参数的形式传递给 next() 方法,即可触发错误处理中间件。
// 直接抛出错误
app.post('/', () => {
throw new Error('我错了');
})
// 异步API的的回调函数中获取错误
app.get('/', (req, res, next) => {
fs.readRile('/file-does-not-exist', (err, data) => {
err ? next(err) : void(0);
})
});
上面示例代码中,异步 API 的错误信息通过回调函数获取,当然支持 Promise 对象的异步 API 发生错误也可以调用 catch 方法获取。异步函数或者同步代码执行过程中的错误则通过异常捕获语句 try catch 进行捕获。
// 异步函数或者同步代码
app.get('/', async (req, res, next) => {
try {
await User.create({name: 'TKOP_'});
} catch(e) {
next(e);
}
})
//
// 也可以将异步API转换为异步函数使用try catch 语句捕获错误
const fs = require('fs');
const promisify = require('util').promisify;
const readFile = promisify('fs.readFile');
app.get('/', async (req, res, next) => {
try {
await readFile('./xxx.js');
} catch(e) {
next(e);
}
})
4、什么是创建模块化路由?如何创建模块化路由?
创建模块化路由是指根据应用实际需要将路由进行分类并从主入口文件分离出来放入不同的文件夹中,简化主入口文件的代码结构和逻辑。再将路由控制(映射)文件和请求处理函数进行分离。将不同的处理函数作为独立的模块进行开发,并将其引入应用到路由控制文件中相应的路由中。当然路由二级路由对象也必须导入主入口文件中使用。其中他们是基于下面的程序逻辑进行的。
const express = require('express');
// 创建路由对象
const admin = express.Router();
// 路径为'/admin/xxx[/...]'时将请求的控制权交给二级路由admin
app.use('/admin', admin); // 将路由和请求进行匹配
// 在admin路由下继续创建二级路由
home.get('user', () => {
// '/admin/user'
res.send('这里响应的是/admin/user路径下的get请求')
});
home.get('user-edit', () => {
// '/admin/user-edit'
res.send('这里响应的是/admin/user-edit路径下的get请求')
})
上示例代码是支持创建模块化路由的逻辑基础,我们可以将上述中的 admin 二级路由分离到一个单独的文件中,再将该二级路由下不同路径请求的处理函数分离成单独的模块放入统一文件夹 admin 中。在二级路文件下就只有路由的控制逻辑了。具体实现如下:
首先看一下文件结构
// home.js文件,二级路由home控制文件
// 创建二级路由
const admin = express.Router();
// 进入用户编辑页面请求的处理路由
admin.get('/user-edit', require('./admin/user-edit'))
// 添加用户提交表单请求处理路由
admin.post('/user-edit', require('./admin/user-edit-fn'))
// 编辑用户信息的请求处理路由
admin.post('/user-modify', require('./admin/user-modify'))
可以看到请求处理函数均是从 admin 文件夹中引入的模块,里面包含和导出的是各个请求的处理代码(函数)。
// 主入口文件app.js
// 导入二级路由对象home(假设我也实现了这个)
const home = require('./router/home.js');
// 导入二级路由对象admin
const admin = require('./router/admin');
// 将请求交由匹配的二级路由对象进行处理
app.use('/home', home);
app.use('/admin', admin)
5、Express 路由参数
app.get('/find/:id', (req, res) => {
console.log(req.params);
});
// 访问localhost:80/fine/123时输出结果{id: 123}
6、静态资源处理
通过 Express 内置的 express.static 可以方便地开放静态资源,例如 img、css、js 文件等。
// 开放静态资源文件,在请求静态资源时使用绝对路径
app.use(express.static(path.join(__dirname, 'public')));
题外知识:第三方模块 serve-static 实现静态资源访问服务。
const path = require('path');
// 引入serve-static模块获取创建静态资源服务功能的方法
const serveStatic = require('serve-static');
// 调用方法创建静态资源服务并指定静态资源服务目录
const serve = serveStatic(path.join(__dirname, 'public'));
// 启用静态资源服务功能
app.on('request', (req, res) => {
serve(req, res);
})
7、参数的获取
对于参数的获取在前面已经写了,在这里我想记录一下有关 post 请求参数解析格式的配置问题。
前端请求保存在请求体中的参数并不是只有一种格式,即不仅仅是 json 字符串的格式(学到 ajax 部分就会了解到有关知识)。因此在后端对这类参数的解析也就需要使用正确的格式进行解析,不然解析的到的参数将会是一个空的 json 对象(本人亲测)。
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
// 以json格式解析参数
const jsonParser = bodyParser.json({extended: false});
// 以x-www-form-urlencoded格式解析参数
const urlencodeParse = bodyParser.urlencoded({extended: false});
app.get('/', jsonParser, (req, res) = {
console.log(req.body);
})
app.post('/user-edit', urlencodedParser, (req, res) => {
console.log(req.body);
})
中间件可以接收多个请求处理函数,我们可以将 Express 内置的配置参数解析格式的函数也以回调函数的方式传入。但是也可以在一个 use() 中间件中配置,此时不再只是一个路由内使用这种格式解析参数。
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
// 配置解析格式,后面所有的路由都使用这种格式(除非单独或者再次配置)
app.use(bodyParser.urlencoded({extended: false}));
使用 express-art-template 之前我们需要先了解一下 art-template 的知识,下面是有关模板引擎 art-template 的一些基础知识笔记。
1、什么是模板引擎?
渲染页面前基本都有一个将从后台数据库取得的数据和静态页面进行字符串拼接的过程。无论是在后端将他们拼接后发送至前端渲染;还是前端发起 ajax 请求,后端响应数据后在前端进行拼接,大量的进行字符串和数据拼接都是繁琐又易错的操作,还会造成维护性低等问题。模板引擎是一种让开发者以更友好的方式拼接字符串,使项目代码更加清晰、更易于维护的第三方模块。
2、怎么使用模板引擎?
下载后引入即可使用,可以在使用前设置模板的根目录和模板的默认后缀,也可以向模板中导入变量(例如用于格式化日期类型数据的函数)。之后使用模板语法告诉模板引擎,模板与数据应该如何进行拼接。其中 art-template 的这些操作如下:
const template = require('art-template');
const path = require('path');
// 向模板中导入变量,以dataFormat为例
template.defaults.imports.dataFormat = dataFormat;
// 设置模板根目录
template.defaults.root = path.join(__dirname, 'views');
// 设置模板默认后缀
template.defaults.extname = '.art';
// 将特定模板与特定数据进行拼接
// 没有设置根目录和默认后缀时,模板需要填写完整的相对路径(./views/index.art)
const html = template('index', {
data: {
name: 'TKOP_',
age: 18,
joinDate: new Date()
}
});
<div>
<span>{{data.name}}span>
<span>{{data.age}}span>
<span>{{dateFormat(data.joinDate, 'yyyy-mm-dd')}}span>
div>
上面只是模板引擎的一个简单使用介绍,接下来逐一了解一下 art-template 的语法。板引擎同时支持两种模板语法:标准语法和原始语法。标准语法具有很高的可读性,原始语法则具有强大的逻辑处理能力。
3、将某项数据输出在模板中
<h3>{{value}}h3>
<h3>{{a ? b : c}}h3>
<h3>{{a + b}}h3>
<h3><%= value %>h3>
<h3><%= a ? b : c %>h3>
<h3><%= a + b %>h3>
4、模板引擎默认不会解析数据中携带的 HTML 标签,会将其转义后输出。但是在确定数据内容中的标签安全的情况下,可以将数据进行原文输出。
<h3>{{@ value }}h3>
<h3><%- value %>h3>
5、条件判断
{{if 条件}} ... {{/if}}
{{if 条件}} ... {{else if 条件}} ... {{/if}}
<% if (条件) { %> ... <% } %>
<% if (条件) { %> ... <% } else if (条件) { %> ... <% } %>
6、循环
{{each target}}
{{$index}}{{$value}}
{{/each}}
<% for(let i = 0; o < target.length; i++) { %>
<%= i %><%= target[i] %>
<% } %>
7、模板共同部分的抽离和引入
可以将模板中的公共部分(例如相同的头部、尾部等)抽离到单独的模板文件中。然后在各个网页的对应位置将其引入。引入的语法如下列所示:
{{include './common/header.art'}}
<% include('./common/header.art') %>
8、模板的继承
使用模板继承可以将网站 HTML 骨架抽离到单独的文件中,其他页面可以继承骨架文件。注意和上述的模板共同部分的抽离和引入区别开,上面是将整块相同的结构抽离引入,而继承是继承相同的骨架。继承后根据需要对骨架中的占位标签进行填充即可得到完整的子模板。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
{{block 'title'}}{{/block}}
{{block 'link'}}{{/block}}
head>
<body>
{{block 'main'}}{{/block}}
{{block 'script'}}{{/block}}
body>
html>
{{extend './common/layout.art'}}
{{block 'link'}}
<link rel="stylesheet" href="/admin/lib/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/admin/css/base.css">
{{/block}}
{{block 'main'}}
{{include './common/header.art'}}
{{/block}}
{{block 'script'}}
{{/block}}
8、express-art-templete 模板引擎
为了使 art-template 模板引擎能够更好地和 Express 框架配合,模板引擎官方在原 art-template 模板引擎地基础上封装了 express-art-template 。因此在使用 Express 模板引擎前首先需要同时下载 art-template 和 express-art-template 模块。
npm install art-template express-art-template
// 当渲染后缀为art的模板时使用express-art-template
app.engine('art', require('express-art-template'));
// 设置模板存放目录
app.set('views', path.join(__dirname, 'views'));
// 设置模板文件的默认后缀
app.set('view engine', 'art');
users = [{
name: 'TKOP_',
age: 18
}, {
name: '扬尘',
age: 20
}]
app.get('/', (req, res) => {
// 拼接模板和数据并渲染
res.render('index', usrs);
});
10、app.locals 对象
将变量设置到 app.locals 对象下面,这个数据在所有的模板中都可以获取到。
// 在登录路由处理模块中验证用户账号密码后,将其信息设置在app.locals对象下
req.app.locals.userInfo = user;
因为服务器对象不直接存在于该模块文件中,所以需要使用 req.app 获取。user 为从数据库得到的该用户的信息数据对象。
{{userInfo && userInfo.userName}}