Nodejs基础部分
为什么要学习Node?
- Node使用Js语法去开发后端应用
- 一些公司要求前端掌握Node开发,公司业务已经在用Node开发
- Node生态系统比较活跃,有大量的开源库可以使用
- 现在又很多的前端开发工具大多都是基于Node开发的
Node是什么?
-
Node是一个基于Chrome V8引擎的Javascript代码运行环境
-
浏览器(软件)能够运行Javascript代码,浏览器就是Javascript代码的运行环境
-
Node(软件)能够运行Javascript代码,Node就是Javascript代码的运行环境
-
在浏览器中全局对象是window,在node中全局对象是global
-
global对象下面同样也有console.log\setTimeout等方法,只不过和window重名,并不是window的方法
Node运行环境搭建
1. 安装
- Node官方推荐大家使用Node稳定版本(LTS版本)
- NodeJs官网
- 可以使用系统命令行工具powershell(在开始菜单搜索
powershell
即可打开)查看当前node安装版本
2. 安装失败解决方法
1. 错误代号 2502、2503
失败原因:系统账户权限不足
解决:
以管理员身份运行powershell命令行工具
输入运行安装包命令 msiexec /package (node安装包位置路径\安装包名字)
比如msiexec /package C:\Node\node\node-v10.13.0-x64.msi
2. 执行命令报错
命令行中输入 node -v
报错
失败原因: Node安装目录写入环境变量失败
解决:将Node安装目录手动添加到环境变量,配置完毕重启命令行窗口
PATH
环境变量:存储系统中的目录,在命令行中执行命令的时候系统会自动去这些目录中查找命令的位置
Nodejs入门
1. Nodejs的组成
- Javascript 由三部分组成: ECMAScript、DOM、BOM
- Nodejs是由ECMAScript及Node环境提供的一些附加API组成,包括文件、网络、路径等等一些更加强大的API
2. Nodejs基础语法
- 所有的ECMAScript语法在Node环境中都可以使用,也就是说Js中变量声明,循环,条件语句等等,在Node环境下都可以使用
小技巧:
- 找到要打开的文件目录,按住shift键,点击右键,会出现‘在此处打开powershell窗口’,可以直接在命令行工具中打开想要执行的文件
- 在命令行工具中执行node命令时,如果文件名过长,我们只需要输入文件名的前几个字符,然后按下Tab键,命令行工具会自动补全我们要打开的文件名,以及文件的路径。
- 之前执行过的命令只需要按键盘的上键就可以输出执行过的命令行
- 命令行中输入clear然后按回车,所有之前输出的命令都被清除掉了
Nodejs模块化开发
1. 开发规范
- Nodejs规定一个Javascript文件就是一个模块,模块内部定义的变量和函数默认情况下外部无法得到
- 模块内部可以使用
exports
对象进行成员导出,使用require
方法进行导入其他模块
2. 成员导出
var version = 1.0;
const sayHi = name => `你好,${ name }`;
exports.version = version;
exports.sayHi = sayHi;
3. 成员导入
let a = require('./b.js');
// 导入时后缀可以去掉
console.log(a.version)
console.log(a.sayHi('zhangsan'));
4. 成员导出的另一种方式
module.exports.version = version;
module.exports.sayHi = sayHi;
exports
是 module.exports
的别名(地址引用关系),导出对象最终以 module.exports
为准
Nodejs系统模块
系统模块: Node运行环境提供的api,因为这些api都是以模块化的方式开发的,所以我们又称Node运行环境提供的api为系统模块
一、 fs模块(文件操作系统)
二、 path模块 (路径操作)
1. 路径拼接
为什么要进行路径拼接?
- 不同的操作系统路径的分隔符不统一
- /public/uploads/a
- Windows上是\
- Linux上是/
- 我们现在写的代码有可能要运行在Linux操作系统的,因为Linux通常被用作运行网站的服务器
比如网站中的头像上传功能,用户上传的头像文件实际上是要存储在服务器硬盘的某个文件夹中的,那么我们在程序文件中要想找到这个文件夹,就必须要拼接这个文件夹的路径,我们就需要使用系统模块path,它在内部会判断你当前使用的操作系统是什么,然后使用操作系统对应的路径分隔符进行拼接。
语法: path.join('路径', '路径', ...)
const path = require('path');
const finaPath = path.join('public', 'uploads', 'a');
console.log(finaPath)
相对路径VS绝对路径
- 大多数时候使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录
- 在读取文件或设置文件路径时都会选择绝对路径
- 使用__dirname获取当前文件所在的绝对路径
fs.readFile(path.join(__dirname, 'demo'), 'utf-8', (err, doc) => {
if(err == null) {
console.log(doc)
}
})
Nodejs第三方模块
什么是第三方模块?
别人写好的,具有特定功能的,我们能直接使用的模块就是第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所有又称为包。
1. 获取第三方模块
npmjs.com: 第三方模块的存储和分发仓库
npm(node package manager): node的第三方模块管理工具
- 下载:npm install 模块名称
- 卸载:npm uninstall 模块名称
全局安装和本地安装
- 命令行工具:全局安装
- 库文件:本地安装
2. nodemon 第三方模块
下载:
npm install nodemon -g
-
nodemon是一个命令行工具,用于辅助开发
-
在Nodejs中,每次修改文件都要在命令行中重新执行该文件,非常繁琐
-
nodemon能够在文件修改后自动执行
3. nrm 第三方模块
- 下载
npm install -g nrm
- 这个模块的主要作用就是切换第三方模块的下载地址
- 因为npm的下载地址在国外,下载过程时间往往比较长,使用
nrm ls
命令可以查看当前可切换的所有地址 - 然后我们使用命令
nrm use taobao
就可以切换下载地址为淘宝镜像 - 我们重新运行使用npm install ***的时候,会发现下载速度明显提升,主要原因就是切换了下载地址为国内的淘宝镜像
4. Gulp 第三方模块
基于node平台开发的前端构建工具
- 项目上线,html、css、js合并压缩
- 语法转换(less、es6...)
- 公共文件抽离
- 修改文件浏览器自动刷新
gulp使用
- 库文件下载:
npm install gulp
-
项目的根目录下建立
gulpfile.js
文件,这个文件名是固定的不能更改 -
重构项目的文件夹结构,
src
目录放置源代码,dist
目录放置构建后文件 -
在
gulpfile.js
中编写任务 -
在命令行工具中执行
gulp
任务
gulp 提供的方法
-
gulp.src()
获取任务要处理的文件 -
gulp.dest()
输出文件 -
gulp.task()
建立gulp任务 -
gulp.watch()
监听文件变化 -
gulp.pipe()
执行函数
gulp 命令行工具
命令行工具下载:
npm install gulp-cli -g
gulp 插件
gulp本身属于轻内核的第三方模块,所提供的方法只有上面四种,所有的其他功能都是通过插件的方式实现的。
比如:
- gulp-htmlmin:html文件压缩
- gulp-csso:压缩css
- gulp-babel:Javascript语法转化
- gulp-less:less语法转化
- gulp-uglify:压缩混淆Javascript
- gulp-file-include:公共文件包含
- browsersync:浏览器实时同步
var gulp = require('gulp');//基础库
// 引入我们的gulp组件
/*html*/
var sass = require('gulp-ruby-sass'), // CSS预处理/Sass编译
cssmin = require('gulp-minify-css'), //压缩css
useref = require('gulp-useref'),//
rev = require('gulp-rev'),
htmlmin =require('gulp-htmlmin'),
revReplace = require('gulp-rev-replace'),
revCollector = require('gulp-rev-collector'), //gulp-rev插件 用于html更改版本后的路径替换
uglify = require('gulp-uglify'), // JS文件压缩
eslint = require('gulp-eslint'),
imagemin = require('gulp-imagemin'), // imagemin 图片压缩
// pngquant = require('imagemin-pngquant'), // imagemin 深度压缩
rename = require('gulp-rename'), // 文件重命名
changed = require('gulp-changed'), // 只操作有过修改的文件
concat = require("gulp-concat"), // 文件合并
clean = require('gulp-clean'); // 文件清理
sourcemaps = require('gulp-sourcemaps');
var livereload = require('gulp-livereload'),//网页自动刷新
webserver =require('gulp-webserver');//本地服务器
var usemin = require('gulp-usemin');
var babel = require('gulp-babel');//配置es6
var plumber = require('gulp-plumber'); //输出错误日志
var sequence = require('gulp-sequence');
/* 全局设置
* -------------------------------*/
var srcPath ={
html: 'src',
sass: 'src/sass',
css:'src/css',
script:'src/js',
image: 'src/images',
json: 'src/json'
};
var destPath = {
html: 'dist',
css: 'dist/css',
script: 'dist/js',
image: 'dist/images'
};
/*html 样式 图片 js处理*/
//更换html里面的路径
gulp.task('htmlmin',function(){
var opations ={
removeComments: true,//清除HTML注释
collapseWhitespace: true,//压缩HTML
collapseBooleanAttributes: true,//省略布尔属性的值 <input checked="true"/> ==> <input />
removeEmptyAttributes: true,//删除所有空格作属性值 <input id="" /> ==> <input />
//removeScriptTypeAttributes: true,//删除<script>的type="text/javascript"
//removeStyleLinkTypeAttributes: true,//删除<style>和<link>的type="text/css"
//minifyJS: true,//压缩页面JS
minifyCSS: true//压缩页面CSS
};
gulp.src(destPath.html)
.pipe(htmlmin(opations))
.pipe(gulp.dest(destPath.html))
});
// 清理文件
gulp.task('clean', function() {
return gulp.src( [destPath.css+'/*.css',srcPath.css+'/*.css'], {read: false} ) // 清理maps文件
.pipe(clean());
});
gulp.task('rev',function(){
return gulp.src(['src/json/rev-manifest.json',srcPath.html+'/**/*.html'])
.pipe( revCollector({
replaceReved: true
}) )
.pipe(changed( destPath.html ))
.pipe(gulp.dest( destPath.html ));
});
gulp.task("sass",function(){
return sass('src/sass/**/*.sass',{style: 'compact', sourcemap: true})
.on('error', function(err){
console.error('Error!',err.message);//显示编译错误信息
})
.pipe(sourcemaps.init())//地图存放
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(gulp.dest('src/css'))
});
gulp.task('cssmin', function () {
gulp.src('src/css/*.css')
.pipe(cssmin())
.pipe(rev())
.pipe(gulp.dest('dist/css'))
.pipe(rev.manifest())
.pipe(gulp.dest(srcPath.json));
});
gulp.task('sequence', function (done) {
sequence(
'clean',['sass','cssmin','rev'],done)
});
gulp.task('eslint',function(){
return gulp.src([srcPath.script+'/!*.js','!node_modules/!**'])
//.pipe(eslint({configFile: 'eslintrc.json'}))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('script', function() {
return gulp.src( [srcPath.script+'/*.js'] ) // 指明源文件路径、并进行文件匹配,排除 .min.js 后缀的文件
.pipe(changed( destPath.script )) // 对应匹配的文件
.pipe(sourcemaps.init()) // 执行sourcemaps
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(uglify()) // 使用uglify进行压缩,并保留部分注释
.pipe(sourcemaps.write('maps')) // 地图输出路径(存放位置)
.pipe(gulp.dest( destPath.script )); // 输出路径
});
// imagemin 图片压缩
gulp.task('images', function(){
return gulp.src( srcPath.image+'/**/*' ) // 指明源文件路径,如需匹配指定格式的文件,可以写成 .{png,jpg,gif,svg}
.pipe(changed( destPath.image ))
.pipe(imagemin({
progressive: true, // 无损压缩JPG图片
svgoPlugins: [{removeViewBox: false}] // 不要移除svg的viewbox属性
//use: [pngquant()] // 深度压缩PNG
}))
.pipe(gulp.dest( destPath.image )); // 输出路径
});
gulp.task('concat', function () {
return gulp.src( srcPath.script+'/*.min.js' ) // 要合并的文件
.pipe(concat('libs.js')) // 合并成libs.js
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(gulp.dest( destPath.script )); // 输出路径
});
gulp.task('useref', function () {
return gulp.src('src/*.html')
.pipe(useref())
.pipe(gulp.dest('src'));
});
// 文件合并
gulp.task('concat', function () {
return gulp.src( srcPath.script+'/*.min.js' ) // 要合并的文件
.pipe(concat('libs.js')) // 合并成libs.js
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(gulp.dest( destPath.script )); // 输出路径
});
// 本地服务器
gulp.task('webserver', function() {
gulp.src( destPath.html ) // 服务器目录(.代表根目录)
.pipe(webserver({ // 运行gulp-webserver
livereload: true, // 启用LiveReload
open: true // 服务器启动时自动打开网页
}));
});
// 默认任务
gulp.task('webserver',function(){
gulp.src('./')//服务器跟目录
.pipe(webserver({
livereload:true,//启用livereload
open:true //服务器启动时自动打开页面
}))
});
//压缩合并js 还有css
gulp.task('usemin', function() {
return gulp.src(srcPath.html+'/**/*.html')
.pipe(usemin({
js: [ uglify()],
css:[ cssmin() ]
}))
.pipe(gulp.dest(srcPath.html));
});
// 监听任务
gulp.task('watch',function(){
gulp.watch( 'src/sass/**/*.sass', ['sequence']);/// / 监听根目录下所有.html文件
});
gulp.task('default',['webserver','watch']); //默认任务
/*es6*/
gulp.task('es6', function() {
return gulp.src('src/es6/app.js')
.pipe(plumber())
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('dist/js'));
});
/*-------------- 发布环境*/
/* = 发布环境( Release Task )
-------------------------------------------------------------- */
// 样式处理
gulp.task('sassRelease', function () {
return sass( srcPath.css, { style: 'compressed' }) // 指明源文件路径、并进行文件匹配(编译风格:压缩)
.on('error', function (err) {
console.error('Error!', err.message); // 显示错误信息
})
.pipe(gulp.dest( destPath.css )); // 输出路径
});
// 脚本压缩&重命名
gulp.task('scriptRelease', function() {
return gulp.src( [srcPath.script+'/*.js','!'+srcPath.script+'/*.min.js'] ) // 指明源文件路径、并进行文件匹配,排除 .min.js 后缀的文件
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(uglify({ preserveComments:'some' })) // 使用uglify进行压缩,并保留部分注释
.pipe(gulp.dest( destPath.script )); // 输出路径
});
// 打包发布
gulp.task('release', ['clean'], function(){ // 开始任务前会先执行[clean]任务
return gulp.start('sassRelease','scriptRelease','images'); // 等[clean]任务执行完毕后再执行其他任务
});
/* = 帮助提示( Help )
-------------------------------------------------------------- */
gulp.task('help',function () {
console.log('----------------- 开发环境 -----------------');
console.log('gulp default 开发环境(默认任务)');
console.log('gulp html HTML处理');
console.log('gulp sass 样式处理');
console.log('gulp script JS文件压缩&重命名');
console.log('gulp images 图片压缩');
console.log('gulp concat 文件合并');
console.log('----------------发布环境-----------------');
console.log('gulp release 打包发布');
console.log('gulp clean 清理文件');
console.log('gulp sassRelease 样式处理');
console.log('gulp scriptRelease 脚本压缩&重命名');
console.log('---------------------------------------------');
});
package.json 文件
package.json
是项目的描述文件,记录了当前项目信息,例如项目名称、版本、作者、GitHub地址、当前项目依赖了哪些第三方模块
使用 npm init -y
命令可以快速生成该文件 -y意思就是不填写任何信息全部使用默认。
使用 npm install
可以自动下载描述文件中的所有第三方模块
1. 项目依赖
- 在项目开发阶段和线上运行阶段,都需要依赖的第三方模块,称为项目依赖
- 使用
npm install
命令下载会默认被添加到package.json
文件的dependencies
字段中
2. 开发依赖
- 在项目开发阶段需要依赖,线上运行阶段不需要依赖的第三方包,称为开发依赖
- 使用
npm install
命令下载会默认被添加到package.json
文件的devDependencies
字段中
3. package-lock.json文件的作用
- 锁定包的版本,确保再次下载的时候不会因为包版本不同而产生问题
- 加快下载速度,因为该文件已经记录了项目所依赖的第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外工作
Nodejs模块的加载机制
- require方法根据模块路径查找模块,如果时完整路径,直接引入模块
- 如果模块后缀省略,先找同名js文件再找同名文件夹
- 如果找到了同名文件夹,找文件夹中的index.js
- 如果文件夹中没有index.js就会去当前文件夹中的package.js文件中查找选项中的入口文件
Nodejs服务器端部分
- 网站应用程序主要是由客户端和服务器端组成
- 客户端:在浏览器中运行的部分,就是用户可以看到并与之交互的界面程序,使用html\css\js构建
- 服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑
- Node网站服务器:能够接收客户端请求,并且能够响应请求
1. 创建web服务器
// 引用系统模块
const http = require('http');
// 创建web服务器
const app = http.createServer();
// 当客户发送请求的时候
// 为服务器添加request事件
app.on('request', (req, res) => {
// 请求响应
res.end('<h1>hi, user</h1>');
})
// 监听端口,listen方法
app.listen(3000)
console.log('服务器已启动,访问localhost:3000')
2. http请求与响应处理
客户端和服务器端的沟通规范
HTTP: 超文本传输协议
报文
在http响应和请求过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式
请求报文
客户端对服务器端说的话
请求方式:
- GET 获取数据的请求一般用get
- POST 添加数据的请求一般用post,一般的逻辑比如登录操作,也用post
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 获取请求方式
console.log(req.method);
})
app.listen(3000)
通过地址栏输入网址的形式一般发送的是get请求
表单提交一般用post
// method 指定当前表单的提交方式
// action 指定当前表单提交的地址
<form method="post" action="http://localhost:3000">
<input type="submit" value="提交"/>
</form>
请求地址:
app.on('request', (req, res) => {
// 通过req.method 来获取请求方式
console.log(req.method)
// 通过 req.url 来获取请求地址
console.log(req.url)
// 获取请求报文信息
console.log(req.headers)
})
下面我们要实现根据不同的请求地址响应不同的页面
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
if( req.url == '/index' || req.url == '/') {
res.end('我是首页')
} else if(req.url == '/list') {
res.end('我是list')
} else {
res.end('页面不存在')
}
})
app.listen(3000)
请求参数:
get请求参数:
在服务器端如何获取到请求参数?
const http = require('http');
// 用于处理url地址的内置模块
const url = require('url);
const app = http.createServer();
app.on('request', (req, res) => {
// 通过url.parse()方法可以返回请求地址以及参数的一些内容
// 第一个参数为要解析的url地址
// 第二个参数为将查询参数解析成对象的形式
let {query, pathname} = url.parse(req.url, true);
if( pathname == '/index' || pathname == '/') {
res.end('我是首页')
} else if(pathname == '/list') {
res.end('我是list')
} else {
res.end('页面不存在')
}
})
app.listen(3000)
post请求参数:
- 参数被放置在请求体中进行传输
- 获取post参数需要使用data事件和end事件
- 使用querystring系统模块将参数转换为对象格式
// html部分
<form action="http://localhost:3000" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
// js
const http = require('http');
const app = http.createServer();
// 处理请求参数模块
const querystring = require('querystring');
app.on('request', (req, res) => {
// post是通过事件来接收的,data事件和end事件
// data当请求参数传递的时候触发data事件
// 当参数传递完成的时候触发end事件
// post参数不是一次就接收完的,我们需要先声明一个变量
let postParams = '';
// 监听参数传输事件
req.on('data', params => {
postParams += params;
})
// 监听参数传输完毕事件
req.on('end', () => {
// 这个时候我们得到的内容是一个参数字符串(username=zhangsan&password=1234)
// 这个时候我们还可以使用url模块来处理参数吗?
// url模块是用来处理请求地址的,现在我们是直接想处理这样的一个字符串
// 如果这时候要转换成对象的形式
console.log(querystring.stringify(postParams));
})
// 对于客户端的每一次请求,服务器端都要去做出响应,所以end必须要加!否则就会处于一个等待状态
res.end('OK')
})
app.listen(3000)
响应报文
服务器端对客户端说的话
HTTP状态码
- 200请求成功
- 404请求的资源没有找到
- 500服务器端错误
- 400客户端请求有语法错误
设置状态码
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 设置状态码
res.writeHead(400)
res.end('hello')
})
app.listen(3000)
内容类型
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 第一个参数为状态码
// 第二个参数为一个对象,可以设置内容类型,编码类型
res.writeHead(200, {
'content-type': 'text/plain;charset=utf8'
})
res.end('<h1>你好nodejs</h1>')
})
app.listen(3000)
路由
路由是指客户端请求地址与服务器端程序代码的对应关系,简单的说,就是请求什么响应什么
// 实现路由功能
// 获取客户端的请求方式
// 获取客户端的请求地址
const http = require('http');
const url = require('url');
const app = http.createServer();
app.on('request', (req, res) => {
// 获取请求方式
const method = req.method.toLowerCase();
// 获取请求地址
const pathname = url.parse(req.url).pathname
res.writeHead('200', {
'content-type': 'text/html;charset=utf8'
})
if(method == 'get') {
if(pathname == '/' || pathname == '/index') {
res.end('home')
} else if (pathname == '/list') {
res.end('list')
} else {
res.end('no page')
}
}else if(method == 'post') {
}
})
app.listen(3000)
静态资源访问
服务器不需要处理,可以直接响应给客户端的资源就是静态资源比如css、js、image文件
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const app = http.createServer();
app.on('request', (req, res) => {
let pathname = url.parse(req.url).pathname;
pathname == '/' ? 'default.html' : pathname;
// 拼接查找静态文件存储的地址
let realPath = path.join(__dirname, 'public' + pathname);
// 利用第三方模块mime返回要访问的文件的类型
let type = mime.getType(realPath);
// 读取文件
fs.readFile(realPath, (error, result) => {
if(error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
})
res.end('文件读取失败!');
return;
}
res.writeHead(200, {
'content-type': type
})
res.end(result);
})
})
app.listen(3000)
动态资源访问
相同的请求地址可以传递不同的请求参数,得到不同的响应资源,这就是动态资源
3. Nodejs异步编程
1. 同步API,异步API
同步API:只有当前API执行完成后,才能继续执行下一个API
console.log(111)
console.log(222)
// 111,222
异步API:当前API的执行不会阻塞后续代码的执行
console.log(111)
setTimeout(() => {
console.log(222)
}, 2000)
console.log(333)
// 打印结果为111,333,222
// 也就是说定时器并没有阻塞后边代码的执行
同步API可以从返回值中拿到API执行的结果,但是异步API是不可以的
// 同步
function sum (a, b) {
return a + b
}
const result = sum(1, 2)
// 异步
function getMsg() {
setTimeout(() => {
return {msg: 'hello'}
}, 2000)
}
const msg = getMsg() // undefined
// 异步API中我们是无法通过返回值的方式去拿到返回结果的
异步API可以使用回调函数拿到返回结果
function getMsg(callback) {
setTimeout(() => {
callback({
msg: 'hello'
})
}, 2000)
}
getMsg((data) => {
console.log(data) // hello
})
同步API和异步API的执行顺序不一样,同步从上到下依次执行,前面的代码会阻塞后面的代码的执行
异步API不会等待API执行完成后再向下执行代码
可以用Promise解决Nodejs异步编程回调地狱问题
function getData() {
setTimeout(() => {
var name = '张三';
return name;
}, 100)
}
console.log(getData())// 这时候我们是获取不到内部name的数据的
解决方法一:利用增加一个callback的方式
function getData(callback) {
setTimeout(() => {
var name = '张三';
callback(name);
}, 100)
}
getData(function(aaa) {
console.log(aaa) // 这个aaa就是上面的name
})
解决方法二:Promise解决
var p = new Promise(function(resole, reject) {
setTimeout(() => {
var name = '张三';
resolve(name);
}, 100)
})
// 获取name
p.then((data) => {
console.log(data); // 此时的data就是name的数据
})
使用async和await:
/**
* 普通方法
function test() {
return '你好,nodejs';
}
console.log(test());
*/
async function test() {
return '你好,nodejs';
}
console.log(test()); // Promise { '你好,nodejs' }
async function main() {
var data = await test() // await必须得用在async里面,获取异步方法里面的数据
console.log(data);
}
main();
异步函数(async和await)是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步形式,让代码不再有回调函数嵌套,使代码更清晰
async关键字
- 普通函数定义前加async关键字,普通函数变成异步函数
- 异步函数默认返回Promise对象
- 在异步函数内部使用return关键字进行结果返回,结果会被包裹的promise对象中return关键字替代了resolve方法
- 在异步函数内部使用throw关键字抛出程序异常
- 调用异步函数then方法获取异步函数执行结果
- 调用异步函数catch方法获取异步函数执行的错误信息
await关键字
- await只能出现在异步函数中
- await 后面只能写Promise对象,写其他类型的API是不可以的
- await 关键字可以暂停异步函数向下执行,直到promise返回结果
数据库及环境搭建
1. 为什么要使用数据库
- 动态网站中的数据都是存储在数据库中
- 数据库可以用来持久存储客户端通过表单收集的用户信息
- 数据库软件本身可以对数据进行高效的管理
2. 什么是数据库
数据库即存储数据的仓库,可以将数据进行有序的分门别类的存储,它是独立与语言之外的软件,可以通过API操作它
常见的数据库有:mysql、mongoDB、oracle
模板引擎
模板引擎是node的第三方模块
让开发者以更加友好的方式拼接字符串,使项目代码更加清晰易于维护
1. art-template模板引擎
是由腾讯公司出品,是目前运行最快的模板引擎
下载: npm install art-template
使用 const template = require('art-template')
引入模板引擎
告诉模板引擎要拼接的数据和模板在哪 const html = template('模板路径', 数据)
// app.js中引入模板引擎
const path = require('path')
const template = require('art-template')
// template方法是用来拼接字符串的
// 第一个参数是模板路径,这里要写绝对路径
// 第二个参数是要在模板中显示的数据, 对象类型
// 返回拼接好的字符串
const views = path.join(__dirname, 'views', 'index.art')
const html = template(views, {
name: '张三',
age: 20
})
// index.art模板
// 普通的html结构
<div>
<span>{{name}}</span>
<span>{{age}}</span>
</div>
模板语法
art-template同时支持两种模板语法: 标准语法和原始语法
标准语法可以让模板更容易读写,原始语法具有强大的逻辑处理能力
输出
两种语法都是可以进行运算并返回运算结果的,比如三目运算符
标准语法: {{ data }}
原始语法: <%= data %>
原文输出
如果数据中携带html标签,默认模板引擎是不会解析标签的,会将其转义后输出
假设content内容为<h2>你好</h2>
,如果我们需要将h2标签解析后输出,我们需要如下操作:
标准语法: {{ @ content }}
原始语法:<%- content %>
条件判断
在模板中可以根据条件来决定显示哪块html代码
标准语法:{{ if 条件 }} ... {{ 条件 else if }} ... {{/if}}
{{ if age > 18}}
年龄大于18
{{ else if age < 15 }}
年龄小于十五
{{ else }}
年龄不符合
{{ /if }}
原始语法:<% if (条件) { %> ... <%} %>
<% if (age > 18) { %>
年龄大于18
<% } else if (age < 15) { %>
年龄小于15
<% } else { %>
年龄不符合要求
<% } %>
循环
标准语法:{{ each 数据 }} {{ /each }}
{{ each target}}
{{ $index }} {{ $value }}
{{ /each }}
原始语法:
<% for( var i = 0; i < target.length; i++) { %>
<%= i %> <%= target[i] %>
<% } %>
// 标准语法
<ul>
{{each users}}
<li>
{{$value.name}}
{{$value.age}}
</li>
{{/each}}
</ul>
// 原始语法
<ul>
<% for( var i = 0; i < users.length; i++) { %>
<li>
<%= users[i].name %>
<%= users[i].age %>
</li>
<% } %>
</ul>
子模板
使用子模板可以将网站公共部分(头部等)抽离到单独的文件中
标准语法:{{ include '模板' }}
原始语法:<% include('模板') %>
{{ include './header.art' }}
<% include('./header.art') %>
模板继承
使用模板继承可以将网站html骨架抽离到单独的文件中,其中页面模板可以继承骨架文件
P192-P200部分,未完成
Express框架
Express是一个基于Node平台的web应用程序开发框架,它提供了一系列的强大特性,帮助你创建各种web应用,我们可以使用 npm install express 命令下载,是node的第三方模块
1. Express框架特性
(1)提供了方便简洁的路由定义方式
所谓简洁路由方式就比如我们之前使用router这个第三方模块定义路由的方式,router这个第三方模块实际上就是从express框架中抽取出来的
(2)对获取http请求参数进行了简化处理
框架提供了更加简洁的方式接收请求参数,也就是说使用express框架不再对请求参数的格式进行转化,框架让我们拿到的直接就是对象类型,现在我们也不需要对请求对象添加data事件以及end事件,框架内部接收完请求参数并处理完以后,将参数作为请求对象的属性让我们直接获取。
(3)对模板引擎支持程度高,方便渲染动态html页面
框架可以非常容易的和模板引擎协同工作,方便开发
(4)框架提供了中间件机制,有效控制http请求
所谓中间件,我们暂且可以简单理解为对请求的拦截
(5)拥有大量第三方中间件对功能进行扩展
可以理解为基于框架还有另外的一些插件可以供我们实现各种功能
Express初体验:
// 引入express框架
// 这个返回值其实是一个方法,通过调用这个方法我们就可以创建网站服务器
// 不再需要引入http模块创建网站服务器
const express = require('express')
// 创建网站服务器
const app = express()
// express路由和router路由第三方模块的使用方法一致
app.get('/', (req, res) => {
// 对客户端进行响应,不再使用原生node的end方法进行响应
// 取而代之的是send()
// 1.send会自动检测响应内容的类型,帮我们把类型自动设置到响应头中
// 2.还可以帮我们设置响应内容的类型编码
// 3.还可以帮我们自动设置http状态码
res.send('hello express')
})
app.get('/list', (req, res) => {
// 使用框架可以直接向客户端响应一个对象,这在原生的node中end是做不到的
res.send({name: 'zhangsan', age: 20})
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
2. 中间件
中间件实际上就是框架提供的一堆方法,可以接收客户端发来的请求,可以对请求做出响应,也就是说中间件的作用是专门用来接收请求处理请求的,对于同一个请求express运行我们设置多个中间件,会按照中间件设置的顺序依次处理
中间件分为两部分:第一部分是框架提供的用于接收请求的中间件方法,第二部分是开发人员提供的用于处理请求的方法(请求处理函数)
比如路由就是中间件,get和post方法就是框架提供的接收请求的方式,方法的第二个参数就是开发人员提供的用于处理请求的方法。
可以针对同一个请求,设置多个中间件,对同一个请求多次处理
默认情况下,请求从上到下一次匹配中间件,一旦匹配成功,就会终止匹配
可以调用next方法将请求的控制权交给下一个中间件,知道遇到结束请求的中间件
app.get('/', (req, res, next) => {
req.name = '张三'
next()
})
app.get('/', (req, res) => {
req.send (req.name)
})
1. app.use中间件用法
我们有时候接收请求既想接收get请求,也想接收post请求,这时候我们就会用到app.use
中间件
app.use
中间件不区分接收的请求方式,匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求
app.use((req, res, next) => {
console.log(req.url)
next()
})
app.use第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就接收这个请求
app.use('/admin', (req, res, next) => {
console.log(req.url)
next()
})
// 中间件概念
const express = require('express')
const app = express()
// 接收所有请求的中间件
app.use((req, res, next) => {
console.log('走了app.use中间件1')
next()
})
app.use('/request', (req, res, next) => {
console.log('走了app.use中间件2')
next()
})
// 当我们请求路径设置为/list,那么此时就不会走/request这个中间件,所以只会打印'走了app.use中间件1'
app.get('/list', (req, res) => {
res.send('zhangsan')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
2. 中间件应用
2.1 路由保护
客户端在访问需要登录的页面时,可以先用中间件判断用户登录状态,如果用户未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面
// 中间件应用
const express = require('express')
const app = express()
app.use('/admin', (req, res, next) => {
let isLogin = false;
if (isLogin) {
// 让请求继续向下执行
next()
} else {
res.send('您还没有登录,拒绝访问')
}
})
app.get('/admin', (req, res) => {
res.send('您已经登录,可以访问')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
2.2 网站维护公告
在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护
// 中间件应用
const express = require('express')
const app = express()
app.use((req, res, next) => {
// 此时并没有调用next方法,此时不会继续向下执行
res.send('当前网站正在维护')
})
app.use('/admin', (req, res, next) => {
let isLogin = false;
if (isLogin) {
// 让请求继续向下执行
next()
} else {
res.send('您还没有登录,拒绝访问')
}
})
app.get('/admin', (req, res) => {
res.send('您已经登录,可以访问')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
2.3 定义404页面
// 中间件应用
const express = require('express')
const app = express()
app.use('/admin', (req, res, next) => {
let isLogin = true;
if (isLogin) {
// 让请求继续向下执行
next()
} else {
res.send('您还没有登录,拒绝访问')
}
})
app.get('/admin', (req, res) => {
res.send('您已经登录,可以访问')
})
// 在所有路由的最后面添加一个404页面的中间件,并设置状态码为404,支持链式调用
app.use((req, res, next) => {
res.status(404).send('当前页面不存在')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
3. 错误处理中间件
在程序执行过程中,不可避免的会出现一些无法预料的错误,比如读取文件失败,数据库连接失败,express框架提供了错误处理中间件来处理错误,要知道的是,在执行过程中一旦出现错误就不能向下执行了,如果想要在出错以后继续向下执行我们就需要在程序中捕获错误,加入错误处理。
错误处理中间件是一个统一处理错误的地方
app.use((err, req, res, next) => {
res.status(500).send('服务器发生未知错误)
})
接下来我们来写一个简单的错误处理:
// 错误处理中间件
const express = require('express')
const app = express()
app.get('/index', (req, res) => {
// 抛出一个错误
// 同步处理代码
throw new Error('程序发生了未知错误')
})
// 设置错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
注意:错误处理中间件只能处理同步代码抛出的错误,如果是异步代码,错误处理中间件是无法捕获到的,这时候我们需要手动去触发这个错误处理中间件
当程序出错时,调用next方法,并且将错误信息通过参数形式传递给next方法就可以触发错误处理中间件
// 错误处理中间件
const express = require('express')
const fs = require('fs')
const app = express()
app.get('/index', (req, res, next) => {
fs.readFile('/demo.txt', 'utf8', (err, result) => {
if(err != null) {
// 此时next传递了参数,代表要传递给错误处理中间件
next(err)
}
})
})
// 设置错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
4. 异步函数错误捕获
在nodejs中,异步API的错误信息都是通过回调函数获取的,支持Promise对象的异步API发生错误可以通过catch方法捕获,异步函数执行如果发生错误要如何捕获错误呢?
try catch可以捕获异步函数以及其他异步代码在执行过程中发生的错误,但是不能捕获其他类型API发生的错误(比如说回调函数或者Promise对象)
// 异步函数错误的捕获
const express = require('express')
const fs = require('fs')
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
const app = express()
app.get('/index', async (req, res, next) => {
try {
await readFile('./aaa.js')
} catch (error) {
next(error)
}
})
// 设置错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
3. Express请求处理
1. 构建模块化路由
虽然我们现在可以用app.get
或者 app.post
方法可以创建一个路由,但是在真实开发中路由是非常多的,如果我们把所有的路由信息罗列在一个文件中,那将是非常可怕的。为了解决这个问题提供了模块化构建方式,可以将不同类别的路由放置在不同的模块中,方便管理
接下来我们看创建模块化路由的基础代码:
// 构建模块化路由的基础代码
const express = require('express')
const app = express()
// 创建路由对象
const home = express.Router();
// 将路由和请求路径进行匹配
app.use('/home', home);
// 在home下继续创建路由(二级路由)
home.get('/index', (req, res) => {
res.send('欢迎来到展示页面')
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
参照上面的例子,我们可以把home和admin模块分别放到单独的js中,作为单独的模块,方便我们引用
// home.js
const home = express.Router();
home.get('/index', (req, res) => {
res.send('欢迎来到首页')
})
module.exports = home
// admin.js
const admin = express.Router();
admin.get('/index', (req, res) => {
res.send('欢迎来到管理页面')
})
moudle.exports = admin
// app.js
const home = require('./route/home');
const admin = require('./route/admin');
app.use('/home', home);
app.use('/admin', admin);
2. GET请求参数的获取
Express框架中使用req.query
即可获取GET参数,框架内部会将GET参数转换为对象并返回
const express = require('express')
const app = express()
app.get('/index', (req, res) => {
// 获取get请求参数
res.send(req.query)
})
app.listen(3000)
3. POST请求参数的获取
Express中接收post请求参数需要借助第三方包 body-parser
const express = require('express')
// 目前body-parser已经被弃用,直接使用express就可以替代它
const bodyParser = require('body-parser')
const app = express()
// 拦截所有请求,对请求进行处理
// extended: false 方法内部使用querystring模块处理请求参数的格式
// extended: true 方法内部使用第三方模块qs处理请求参数的方式
app.use(express.urlencoded({extended: false}))
app.post('/add', (req, res) => {
res.send(req.body)
})
app.listen(3000)
4. Express路由参数
const express = require('express')
const app = express()
app.get('/index/:id/:name', (req, res) => {
// 获取get请求参数 params 方法
res.send(req.params)
})
app.listen(3000)
// 访问地址 http://localhost:3000/index/123/zhangsan 返回值为:{ id: 123, name: zhangsan }
5. 静态资源处理
通过Express内置的express.static
可以方便地托管静态文件,例如img\css\js等文件
app.use(express.static('/static/public'));
使用app.use中间价拦截所有的请求,然后将请求交给express.static这个方法来处理,并且将静态资源目录告诉static方法,在方法内部会判断客户端发来的请求,是否是静态资源请求,如果是,方法内部直接将静态资源响应给客户端,中止当前请求。如果发来的请求不是静态资源请求,方法内部会调用next方法将请求的控制权交给下一个中间件,开启静态资源访问后,静态资源就可以用这样的方式访问了:
const express = require('express')
const path = require('path')
const app = express()
// 实现静态资源访问功能
app.use(express.static(path.join(__dirname, 'public')))
// 还可以给文件路径添加一个虚拟路径
app.use('/aaa', express.static(path.join(__dirname, 'public')))
// 这样我们通过绝对路径 + /aaa/public 也可以访问到静态资源文件
app.listen(3000)
6. express-art-template模板引擎
为了使art-template
模板引擎更好的和express
框架配合,模板引擎官方在原art-template
模板引擎的基础上封装了express-art-template
使用npm install art-template express-art-template
命令进行安装
const express = require('express')
const path = require('path')
const app = express()
// 告诉express框架使用什么模板引擎渲染什么后缀的模板文件
app.engine('art', require('express-art-template'))
// 告诉express框架,模板存放的位置使什么
// 第一个参数views是固定的,不可变的
// 第二个views是文件夹名字,可变
app.set('views', path.join(__dirname, 'views'))
// 告诉express框架模板的默认后缀是什么
app.set('view engine', 'art');
app.get('/index', (req, res) => {
// 在这里我们是用render方法渲染函数,这个方法是express提供的
// 第一个参数是模板的名字,第二个参数是一个对象,这个对象里面的属性在模板中是可以直接拿到的
// res.render这个方法内部帮我们做了很多事情:
// 1. 拼接了模板路径
// 2. 拼接了模板后缀
// 3. 哪一个模板和哪一个数据进行拼接
// 4. 将拼接结果响应给了客户端
res.render('index', {
msg: 'message'
})
})
app.listen(3000)
app.locals对象
将变量设置到app.locals对象下面,这个数据在所有的模板中都可以获取到
app.locals.users = [{
name: 'zhangsan',
age: 20
},{
name: 'lisi',
age: 22
}]
网友评论