前端-NodeJS
一、NodeJs介绍
1. 功能
- NodeJs不是语言、库、框架
- 是JavaScript运行环境,执行JavaScript代码
- 使得JavaScript可以完全脱离浏览器执行
2. 浏览器中的JavaScript与NodeJs中JavaScript对比
1) 浏览器中
- EcmaScript:基本语法、if、var、function、object、array
- BOM
- DOM
2) NodeJs中
- EcmaScript
- 没有DOM和BOM
- 服务器级别的操作:文件的读写、网络服务器的转发、网络通信、http服务器、待处理
3. NodeJs特点
- even-driven:事件驱动
- non-blocking I/O model:异步
- lightweight and effcient:轻量与高效
4. npm
- npm是世界上最大的开源生态库
- 绝大多数的javascript相关的包
- 例:npm install jquery
5. NodeJs作用
- web服务器后台
- 命令行工具:npm、git、hexo
6. 书籍
- 深入浅出Node.js
- node.js权威指南
- javascript标准参考教程:阮一峰
- node入门
- 官方api文档
- 中文文档
- CNODE社区
7. 所学知识点
- BS:Browser-Server
- 模块化编程
- 异步编程
- Express Web开发工具
- Ecmascript 6
8. 安装
二、入门
1. Hello.js
var foo = 'hello Node'
console.log(foo)
- cmd执行
- git bash
- powershell
node hello.js 执行
2. node没有DOM和BOM
3. node具有文件读写功能,要引入fs模块
读文件
// 浏览器中的Javascript没有文件操作功能
// Node中的javascript有
// fs-- file system
// fs模块拥有所有文件的读写操作
// 1.使用require加载fs核心模块
var fs = require('fs')
// 2. 读取文件
// 第一个参数:要读取文件的路径
// 第二个参数:回调函数
// error:
// 如果读取失败,error就是错误对象
// 如果读取成功,error就是null
// data:
// 如果读取成功,data就是读取到的数据
// 如果读取失败,error就是错误对象
//
fs.readFile('./data/hello.txt',function(error,data){
// 原始数据二进制,转为十六进制
console.log(data)
// toString
})
写文件
var fs = require('fs')
// 参数
// 1. 路径
// 2. 内容
// 3. 回调函数
// error
// 成功:文件写入成功,error为null
// 失败:文件写入失败,error错误对象
fs.writeFile('./data/wt.txt','你好',function(error){
console.log('文件读写成功')
})
3. 创建服务器 http
核心模块:http
// 引入核心模块
var http = require('http')
// 创建服务器
var server = http.createServer()
// request: 注册request事件,放客户端请求过来,就会自动触发服务器 request 请求事件,
// 然后执行第二个参数,回调处理函数
// response: 相应对象
server.on('request'function(request,response){
console.log('客户端收到请求')
console.log('收到客户端的请求,路径:'+ request.url)
// response对象中的write方法可以给客户端相应数据
// write可以使用多次,但是最后一定要使用end结束响应
response.write('Hello')
response.end()
})
// 开启端口号
server.listen(3000,function(){
console.log('服务器启动成功')
})
相应小例子
var http = requrire('http')
var server = http.createServer()
server.on('request',function(request,response){
// 相应的路径都是端口号后面的,以 /
var url = request.url
// 虚拟json数据
var products = [
{
name: 'iphone',
price: 5000
},
{
name: 'huawei',
price: 3000
}
]
if(url === '/'){
response.end('主页')
}else if(url === '/login'){
response.end('登录页面')
}else if(url === 'products'){
response.end(JSON.stringify(products))
}else{
response.end('页面不存在')
}
})
server.listen(80,function(){
console.log('服务器启动成功')
})
4. 常用的模块
看官网
- 文件操作
- http
- url路径操作
- path路径处理
- os操作系统
Node中的模块有:
- 具名的核心模块,fs、http
- 用户自己编写的文件模块
Node中只用模块作用域,没有全局作用域
require作用:
- 加载文件模块并执行里面的代码
- 拿到被加载文件模块的接口对象
exports:
- 默认是一个空对象
a.js
var exports = require('./b')
console.log(exports.foo)
b.js
var foo = 'bbb'
exports.foo = 'hello'
exports.add = function(x,y){
return x + y
}
执行a.js 输出 hello
IP地址用于定位计算机
端口号用于定位应用程序
解决相应乱码问题
response.setHeader('Content-Type','text/plain;charset=utf-8')
// 根据相应设置响应头文件类型
if(url === 'plain'){
response.setHeader('Content-Type','text/plain;charset=utf-8')
response.end('hello')
}else if(url === '/html'){
response.setHeader('Content-Type','text/html;charset=utf-8')
response.end('<p>hello</p>')
}else if(url === '/ab2.jpg'){
response.setHeader('Content-Type','image/jpeg')
}
每种资源对应的Content-Type是 不一样的
无分号代码风格:
- (
- [
- ` 反引号 很nice 写什么格式呈现什么格式
以以上三种开头的时候,则在前面补上分号用以避免语法的解析错误
三、实现展示目录功能 这个例子讲的是模板引擎
var http = require('http')
var server = http.createServer()
var myDir = 'C:/Users/yan/Desktop/NodeTest'
server.on('request',function(req,resp){
var url = req.url
fs.readFile(./template.html,function(error,data){
if(error){
return resp.end('404 not found')
}
fs.readdir(myDir,function(error,files){
if(err){
return resp.end('Can't found the file')
}
})
})
})
模板引擎与Vue很类似
art-template
- npm install art-template
- 加载art-template var template = require('art-template')
- 查文档
这个例子讲的是服务器端渲染
服务端渲染:在网页源代码中能够找到对应的商品
客户端渲染:在网页源代码中找不到
四、留言本案例
1. 链式调用
var http = require('http')
var fs = require('fs')
http
.createServer(function(req,resp){
resp.end('hello')
})
.listen(3000,function(req,resp){
console.log('server is running')
})
2. 为了方便的统一处理这些静态资源,将这些静态资源都放在public目录中
五、CommonJS模块规范
模块系统:
- 模块作用域
- 使用require方法加载模块
- 使用exports接口对象用来导出模块中的成员
值界到处某个变量使用:module.exports = 'hello'
也可导出多个对象
module.exports = {
add: function(){
return x + y
},
str: 'hello'
}
Nodejs简化---module.exports = exports
exports.a = 123
// 重新赋值,其以后添加的结果无效
exports = {}
exports.foo = 'bar'
执行后结果:a:123
最终返回的是module.exports
分析:
exports.foo = 'bar'
module.exports.a = 123
// 这里已经断开连接,最终return的是module.exports
// 所以无论exports中的成员变量是什么都没用
exports = {
a:'bar'
}
module.exports.foo = 'haha'
exports.c = 456
// 重新建立联系
exports = module.exports
exports.a = 789
slice
require方法加载规则
- 优先缓存加载
- 判断标识符:核心模块、第三方模块、自定义模块
七、npm和package.json
1. 先导
npm是包管理工具
package.json就像产品说明书,里面包括依赖,通常是npm install --save xxx
npm init向导方式
- npm i xxx --save : Dependencies
- npm i xxx --save-dev : devDependencies 这是开发环境 ,在生产环境中此处依赖不加载
2. npm命令
1) 官方网站
2) npm --version
使用npm install --global npm进行升级
3) 常用命令
- npm init 其中使用npm inti -y可以跳过向导,快速生成
- npm install 一次性把dependencies选项的依赖全部安装
- npm install 包名 只下载
- npm install --save 包名 下载并保存依赖
- npm uninstall --save 包名 删除的同时也会把依赖信息也去除
- npm help
- npm 命令 --help 查看具体命令帮助
4) 镜像
npm install --global cnpm 全局
cnpm install jquery 使用淘宝服务器
也可使用npm config set registry https://npm.taobao.org,则npm可以使用淘宝进行下载
npm config list
3. package.json
npm 5版本以前不会有package-lock.json文件
npm 5版本之后会有package-lock.json文件
package-lock.json保存了所有依赖的信息
lock:用于锁定版本
八、express
是一个框架 expressjs.com
1) 创建express-demo
mkdir express-demo
npm init --y
npm install --save express
2) 引包
var express = require('express')
var app = express()
app.get('/',function(req,resp){
resp.send('hello express')
})
app.get('/about',function(req,resp){
resp.send('express')
})
app.listen(3000,function(){
console.log('app is runnning at port 3000')
})
3) 指定公开目录
app.use('/public',express.static('./public/'))
4) 热部署
nodemon
npm install --global nodemon
使用nodemon app.js 自动监视文件的变化自动重启服务
5) 路由
- 请求方法
- 请求路径
- 处理函数
6) 使用art-template
npm install --save art-template
npm install --save express-art-template
app.engine('art',require('express-art-template))
resp.render('模板名',{模板数据}) express约定,视图都在view目录下
var express = require('express')
var app = express()
app.engine('html',require('express-art-template))
app.get('/',function(req,resp){
resp.send('hello express')
})
app.get('/about',function(req,resp){
resp.render('index.html',{
title:'管理系统'
})
})
app.listen(3000,function(){
console.log('app is runnning at port 3000')
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>{{ title }}</h1>
</body>
</html>
7) 在express中获取post请求数据
使用插件body-parser
** 获取get请求参数值界直接使用req.query
8) CRUD
var students = JSON.parse(data).students
express中的router var router = express.Router()
讲操作模块封装好
异步回调函数
function fn(callback){
// var callback = function(data){
// console.log(data)
// }
setTimeout(function(){
var data = 'hello'
callback(data)
},1000)
}
// 如果需要获取一个函数中异步操作的结果,则必须通过回调函数来获取
fn(function(data){
console.log(data)
})
回调函数:在一个函数中调用另外一个函数就是callback 主要是为获取异步的数据
最简单的例子:
function a(){
return 1
}
function b(aa){
return 2 + aa
}
var c = 0
c = b(a())
console.log(c)
// 结果为3
异步操作
var a = 0
function bb(x){
console.log(x)
}
function timer(x){
setTimeout(function(){
a = 6
},x)
}
console.log(a)
timer(3000)
bb(a)
// 结果 0 0
var a = 0
function bb(x){
console.log(x)
}
function timer(x,callback){
setTimeout(function(){
a = 6
callback(a)
},x)
}
console.log(a)
timer(3000,bb)
// 结果 0 6
理解:
9) MongoDB
关系型数据库
非关系型数据库
下载与安装
连接与退出
- mongo
- exit
基本命令
- show dbs 显示所有数据库
- db 当前数据库
- use 数据库名 切换到指定的数据库
- 插入数据 db.students.insertOne({"name":"Jack"})
具体的学习直接看菜鸟教程
10) Node操作MongoDB
不建议使用mongo官方提供的包
使用第三方包 mongoose
数据库
表 --> 集合
九、Promise
为了解决回调地狱嵌套,ES6新增API--Promise
是一个构造函数
使用案列
var fs = require('fs')
console.log(1)
// 创建Promise容器
new Promise(function(){
console.log(2)
fs.readFile('/data/a.txt','utf-8',function(err,data){
if(err){
console.log('err')
}else{
console.log(3)
console.log(data)
}
})
})
console.log(4)
// 输出结果不一定,Promise中readFile是异步操作
Promise本身不是异步,其中的任务是异步的
var fs = require('fs')
// 创建Promise容器
var p1 = new Promise(function(resolve,reject){
fs.readFile('/data/a.txt','utf-8',function(err,data){
if(err){
// 失败,把容器的Pending状态变为Rejected
reject(err)
}else{
// 成功,把容器的Pending状态变为Resolved
resolve(data)
}
})
})
// 创建Promise容器
var p2 = new Promise(function(resolve,reject){
fs.readFile('/data/b.txt','utf-8',function(err,data){
if(err){
// 失败,把容器的Pending状态变为Rejected
reject(err)
}else{
// 成功,把容器的Pending状态变为Resolved
resolve(data)
}
})
})
// 获取Promise任务中的数据
p1
.then(function(data){
console.log(data)
// 当p1读取成功的时候
// 当前函数中的return的结果就可以在后面的then中的function接收到
// 当你return 123后面的就接收到123
// return 'hello' ...
// return p2 就接受p2任务的结果作为参数
},function(err){
console.log('读取文件失败',err)
})
.then(function(data){
console.log(data)
})
封装Promise API
应用场景
1) json-server插件的使用
npm install -g json-server
十、课程的项目
1. 初始化项目
- npm init -y
- git init
- Readme.md 项目简介
- .gitignore 忽略一些文件不git到仓库中
- npm i express mongoose nodemon
- public 公开资源 css img js
- app.js 主文件入口
- nodemon app.js 测试
var express = require('express')
var path = require('path')
var app = express()
app.use('/public',express.static(path.join(__dirname,'./public/')))
app.use('/node_modules',express.static(path.join(__dirname,'./node_modules/')))
app.get('/',function(req,resp){
resp.send('hello yan shiwu')
})
app.listen(3000,function(){
console.log('the server is running')
})
2. Node中Path模块学习
_dirname和_filename
- _dirname: 获取当前文件模块的绝对路径
- _filename: 获取当前文件的决定路径
Node的文件操作是以相对路径为基础的,不可靠,因此尽量使用动态绝对路径
3. 模板引擎的搭建
- npm i art-template
- npm i express-art-template
公共模板提出(使用art-template) 模板继承
于是就涉及到页面布局 layout
4. 引入bootstrap和jquery
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script src="/node_modules/bootstrap/dist/js/bootstrap.js"></script>
从而也可以动态引入所需样式 这对js央视也同理
5. 使用项目的页面
6. 路由的设置
新建routers文件夹,提取app.js中的路由
- session.js 处理 注册、登录、退出等业务
- topic.js 处理 博客发表、浏览等业务
路径 | 方法 | get参数 | post参数 | 是否需要登录 | 备注 |
/ | get | 渲染主页 | |||
/register | get |
| 渲染注册页面 | ||
/register | post | name、password、email | 处理注册请求 | ||
/login | get | 渲染登录页面 | |||
/login | post | name、password | 处理登录请求 | ||
/logout | get | 处理退出请求 |
1) 注册、登录与退出路由
注册模块:
第一步:路由
var express = require('express')
// 数据库
var User = require('../models/user.js')
// md5加密 npm i blueimp-md5
var md5 = require('blueimp-md5')
var router = express.Router()
router.get('/',function(req,resp){
resp.render('index.html')
})
router.get('/register',function(req,resp){
resp.render('register.html')
})
router.post('/register',function(req,resp){
})
module.exports = router
在app.js中挂载路由:
// 插件获取post请求数据
var bodyParser = require('body-parser')
// 加载路由
var router1 = require('./router/session.js')
// 配置解析表单post请求插件 要在路由挂载之前
// 使用body-parse
app.use(bodyParser.urlencoded({ extended:false }))
app.use(bodyParser.json())
// 使用路由(将路由挂在app中)
app.use(router1)
第二步:构建User mongodb数据库 user.js
var mongoose = require('mongoose')
// 连接数据库
mongoose.connect('mongodb://localhost/test');
var Schema = mongoose.Schema
var userSchema = new Schema({
email: {
type: String,
required: true
},
nickname: {
type: String,
required: true
},
password: {
type: String,
required: true
},
create_time: {
type: Date,
default: Date.now
},
last_modified_time: {
type: Date,
default: Date.now
},
avatar: {
type: String,
default: '/public/img/avatar-default.png'
},
bio: {
type: String,
default: ''
},
gender: {
type: Number,
enum: [-1,0,1],
default: -1
},
birthday: {
type: Date
},
status: {
type: Number,
// 没有权限限制
// 是否可以评论
// 是否可以登录使用
enum: [0,1,2],
default: 0
}
})
module.exports = mongoose.model('User',userSchema)
第三步:处理注册事件
router.post('/register',function(req,resp){
// 1. 获取表单数据 获取post请求数据,需要安装插件
// console.log(req.body)
var body = req.body
// 2. 操作数据库 用户名或邮箱是否存在 先不使用Promise
User.findOne({
$or: [
{
email: body.email
},
{
nickname: body.nickname
}
]
},function(err,data){
if(err){
return resp.status(500).json({
err_code: 500,
message: 'Server Error'
})
}
if(data){
return resp.status(200).json({
err_code: 1,
message: 'email or nickname is already exist'
})
}
// md5加密
body.password = md5(md5(body.password))
new User(body).save(function(err,data){
if(err){
return resp.status(500).json({
err_code: 500,
message: 'Server Error'
})
}
resp.status(200).json({
err_code: 0,
message: 'Ok'
})
})
})
})
// ES6
// router.post('/register', async function (req, res) {
// var body = req.body
// try {
// if (await User.findOne({ email: body.email })) {
// return res.status(200).json({
// err_code: 1,
// message: '邮箱已存在'
// })
// }
// if (await User.findOne({ nickname: body.nickname })) {
// return res.status(200).json({
// err_code: 2,
// message: '昵称已存在'
// })
// }
// // 对密码进行 md5 重复加密
// body.password = md5(md5(body.password))
// // 创建用户,执行注册
// await new User(body).save()
// res.status(200).json({
// err_code: 0,
// message: 'OK'
// })
// } catch (err) {
// res.status(500).json({
// err_code: 500,
// message: err.message
// })
// }
// })
第四步:返回数据给前端
前端的异步请求ajax
<script>
$('#register_form').on('submit', function (e) {
e.preventDefault()
var formData = $(this).serialize()
$.ajax({
url: '/register',
type: 'post',
data: formData,
dataType: 'json',
success: function (data) {
var err_code = data.err_code
if (err_code === 0) {
// window.alert('注册成功!')
// 服务端重定向针对异步请求无效
window.location.href = '/'
} else if (err_code === 1) {
window.alert('邮箱已存在!')
} else if (err_code === 2) {
window.alert('昵称已存在!')
} else if (err_code === 500) {
window.alert('服务器忙,请稍后重试!')
}
}
})
})
</script>
第五步:小细节 mongodb compass使用
表单具有默认的提交行为,默认是同步的,同步表单提交,浏览器会锁死等待服务端的响应结果
同步: <form action method> 无论服务器响应什么,都会自动覆盖当前内容
于是ajax异步
登录模块:
session--用户的活跃状态
express-session
npm i express-session
// google插件: EditThisCookie
也可使用插件讲session数据存储到MongoDB中,方便服务器重启后找回数据
十一、Express中间件
var express = require('express');
var app = express();
app.use(function (req, res, next) { //没有路由参数,默认匹配所有路径
console.log('app use2'); //输出在控制台
next(); //传递给url相匹配的下一个中间件
});
app.get('/', function (req, res, next) {
res.send('hello'); //输出在页面
next(); // 传递给下一个中间件
}, function (req, res, next) {
console.log('world'); //输出在控制台
next(); //传递给下一个中间件
});
app.get('/', function (req, res) {
console.log('two'); //输出在控制台
});
app.use(function (req, res) {
console.log('app use2'); //不会输出,因为在上一个中间件已经结束请求
});
app.listen(3000, function () {
console.log('listen 3000 port');
});