- 作者为ryan dahl
- nodejs不是一门语言
- 不是一个框架不是一个库
- 是javasscript运行时环境,运行在chromeV8引擎上
- 事件驱动
- 非阻塞IO模型
- 轻量级及高效
- ECMAScript
- DOM
- BOM
- ECMAScript
- 服务器级别的API
- 文件读写
- 网络服务构建
- 网络通信
- http服务器等处理...
- nodejs的包管理器,是世界上最大的开源库生态系统
- web服务器后台
- 命令行工具
- npm(node)
- git(c语言)
- ...
- 前端接触最多的是命令行工具
- webpack
- gulp
- npm等
- 深入浅出nodejs
- 偏理论,没有实战性内容
- 理解底层原理
- nodejs权威指南
- API讲解
- JavaScript标准参考教程
- node入门
- cnode社区
- cnode入门
- nodejs为JS创建了许多服务器级别的API,这些API绝大部分都被封装到具名的核心模块中,例如:
- fs 操作文件
- os 获取操作系统信息
- path 编辑路径
- http 服务器相关
- ...
- require是个方法用来加载模块与获取被加载模块导出的对象
- 模块有三种类型:
- 具名的核心模块
- 用户自定义的文件模块。相对路径以./开头,且可以省略后缀名
- 第三方模块,会先读取当前文件父目录下的node_modules里的模块,没有继续向父目录中的查找,一直到根目录为止
- 在node里没有全局作用域,只有模块作用域
- 外部无法访问内部
- 内部无法访问外部
- 默认是封闭的
- 每个文件模块中都提供一个exports对象,默认是空对象
- require方法执行的返回值就是被引入模块中exports对象的值(可以使用闭包功能)
- 如果一个模块要直接导出某个成员而非挂载的方式,这时候必须使用对module.exports赋值的方式
- 文件作用域
- 通信规则
- 加载require
- 导出require
- commonJS
- requirejs AMD
- seajs CMD
- ECMAScript 6 modules
- 使用require来加载模块
- 获取模块导出的数据
- 如果是非路径形式则只加载核心模块与第三方模块
- 如果是以下路径形式, 则只加载自定义模块
/xxx./xxxc:/ddd../xxx- 首位的
/表示当前文件所属磁盘的根目录 aaa/xxx这种写法会报错,但是在文件路径中./前缀可以省略
- 如果是加载第三方模块,则加载顺序为:
- 先查找当前父目录下node_modules文件
- 寻找node_modules/xxx文件夹,xxx为加载的模块名称
- 寻找node_modules/xxx/package.json
- 寻找node_modules/xxx/package.json中main属性的文件名
- 按照main文件名加载指定入口文件
- 如果package.json或者main属性值没有指定,则尝试加载package.json所在目录下的index.js
- 没有找到则向上一级目录加载,然后循环1-7
- 到根目录还没有查找到,则报错: can not find module xxx
- node_modules一般放在项目根目录下,不可能一个项目有多个node_modules文件夹
- npm安装规则似乎也相似,只是package.json在node_modules文件夹父目录中而不是项目中的
let foo = require('foo')- 使用exports
//导出多个数据
exports.a = 'hello'
exports.b = 1
exports.c = {}- 使用module.exports
//导出单个数据
module.exports = 'hello'
//也可用来导出多个数据
module.exports = {
a: 'hello',
b: 1
}- exports在node中是module.exports的引用
/* node模块化的底层实现
var module = {
exports: {}
}
var exports = module.exports
*/
//开始执行文件中的代码
...code
/*
return modeule.exports
*/- 由于是引用且返回值是modeule.exports,因此当给exports或者module.exports重新赋值时,引用的地址就会丢失,就无法导出exports中的值了
- 当加载一个模块时node底层会判断这个模块是否被加载过,如果加载过就不再执行而是直接返回这个模块导出的结果
- node pacakage management
- npm init
- npm init -y 跳过向导快速生成
- npm install [包名] [--save]
- npm uninstall [包名] [--save]
- npm help 使用帮助
- 使用cnpm代替npm,源为淘宝源
npm install --global cnpm- 修改npm的下载源
npm config set registry http://registry.taobao.org- 一般每个项目的根目录都要有一个package.json文件,来描述这个项目的各种基础或依赖信息
- 可以使用npm init来初始化描述文件
- 安装包时可以加上--save选项来将下载的包当做依赖添加进package.json中
- 使用npm install时,可以将package.json中的依赖项自动下载下来
- 在npm v5.x.x的版本之后,安装新模块会自动将依赖添加进package.json中
- 并且会在下载新模块时自动添加或者更新package-lock.json文件
- 此文件会将新模块的所有嵌套依赖都包含进去,会加快从新下载模块的速度
- 使用npm install从新下载新模块的时候不会像老版本默认会更新依赖项中的版本而是按照此文件中的依赖版本进行下载
- fs.readFile(dir, function(error, data){})
- 成功时,data是数据,error是null
- 失败时,data是undefined,error是错误对象
- fs.writeFile(dir, str, (error)=>{})
- 成功时,error是null
- 失败时返回错误对象
- 创建服务器的简单流程
let http = require('http')
let server = http.createServer() //创建服务器实例
//注册事件时回调函数有两个参数
//请求对象request
//相应对象response
server.on('request', (req,res)=>{ //给服务器注册请求事件
console.log('收到客户端请求'+req.url) //url返回路径
//response对象有个方法write可以向客户端发送响应流
//write可以调用多次,但最后一定要用end结束,否则客户端一直等待
res.write('hello')
res.write(' world')
res.end('aaa') //可以只写这条语句表示将str写入流之后立即end
})
server.listen(3000, ()=>{ //监听3000端口号
console.log('服务器开启成功')
})- IP地址定位到计算机
- 端口号定位到特定的应用程序
指定浏览器解析服务器返回文件的方式
- text/plain 普通文本
- text/html html文本
- image/jpeg 图片
- charset 设置浏览器的编码解析格式,只对文本数据有效,也可以通过给html文件的meta设置charset来表示编码格式
- ...
- 可以通过oschina网站查看
- 读取相应html文件
- 将要修改的数据使用字符串编辑API进行修改
- 将修改好的文件返回给浏览器,必要时指定http头content-type
- 模板引擎不关心除了特定格式的字符串以外的任何东西
- 模板引擎一开始是被用于服务端然后才在浏览器上流行开来
- 在浏览器中使用模板引擎的流程
- 引入template-web.js文件
- 新建script标签指定type为text/template
- 新建script使用template的API来操作模板,由于浏览器不支持文件操作因此需要文件名的地方底层默认使用document.getElementById代替
- 获取转换好的文本通过innerText或者innerHTML插入到指定位置中
- 在nodejs中使用模板引擎流程
- 引入模板模块
- 使用template将模板转化成文本
- 将文本发送给浏览器
- 浏览器端渲染
- 发起请求获取页面
- 使用ajax请求数据
- 将数据渲染给模板然后绘制页面
- 不利于SEO搜索引擎优化
- 异步获取数据,速度更快,用户体验更好
- 服务端渲染
- 发起请求获取页面
- 服务端响应后渲染指定页面并将页面返回
- 浏览器端直接绘制页面
- 利于SEO搜索引擎优化
- 点击页面后再渲染速度比较慢
- 为了让目录结构更加清晰,因此约定静态资源统一放入pubilc文件中
- 哪些资源能被访问哪些资源不能被访问都能被服务端代码所控制
- 当服务端向客户端返回html或者css等文件时,浏览器会自动从上到下依次执行,当遇到外链如:
- img
- iframe
- link
- script
- audio
- video等时,会自动向服务端发起请求,此时这些外链的src,href就是url地址而非文件地址
- 服务端可以通过检查字符串的方式将pubic地址开放给浏览器自由访问
- 对于get表单请求由于url是动态的,因此不可能通过直接判断url判断执行代码
- 有url模块用来解析url路径
- url.parse(url, boolean) 第二个参数决定是否将查询字符串解析成对象
- 一次请求对应一次响应,响应结束请求也就结束了
- 当响应码为301(永久重定向)302(临时重定向)时,浏览器会自动检查响应头中的location的值并自动向此url发送新的请求,但是对于异步请求(ajax)重定向无效
- 永久重定向
- 每次重定向时浏览器不会对重定向的url发送新的请求,而是从磁盘缓存中加载
- 临时重定向
- 每次重定向时浏览器会对重定向的url发送新的请求
- 使用res.statusCode来设置响应码
- 辅助API测试
- 在此控制台中核心模块被自动加载,不需要再引入即可使用
- 可以使用第三方命令行工具
nodemon来监听文件修改,每次保存时会自动重启服务
npm install --global nodemon- 作者为TJ
- 此框架作用是封装http模块,使其使用更加方便
npm install express --save- express().get(rooter, func)
- express().use(rooter, func|express.static(path)) 使用rooter别名访问path文件
let express = require('express')
let app = express()
app.use(express.static('./public/'))//按照路由路径直接访问静态资源路径
app.use('/public/', express.static('./public/'))//按照/public/后的路由路径访问静态资源路径- express().listen(port, func)
安装
npm install --save art-template express-art-template配置
let express = require('express')
let app = express()
app.engine('html', require('art-template-express'))//设置以html为扩展名的文件将启动模板引擎来渲染
app.get('/', (req, res)=>{
res.render('index.html', {title: 'my template'}) //res默认没有render方法,当设置模板引擎后才会启用,第一个参数不能使用文件路径,express默认会从当前项目中的views文件夹中查找待渲染的文件
})
app.set('views', 'public') //修改模板默认查找路径,第一个参数不能写错安装
npm install --save body-parse配置
let bodyParse = require('body-parse')
app.use(bodyParse.urlencoded({extend: false}))
app.use(bodyParse.json())
app.post('/poster', (req, res)=>{
console.log(req.body) //返回值为请求体的对象
})app.use((req, res)=>{
})
- 当要获取一个函数内部的异步操作时,必须使用回调函数的方式
function add(x, y, callback){
setTimeout(()=>{
callback(x+y)
})
}
add(1,2,res=>{
console.log(res)
})- 数组的遍历方法,都是对函数作为参数的应用
- find
- findIndex
- every
- some
- map
- reduce
| 功能 | 路由 | get | post | 备注 |
|---|---|---|---|---|
| 读取学生目录 | /student | 读取文件,渲染文件中数据 | ||
| 添加学生目录 | /new | 跳转到能添加目录的界面 | ||
| 发送添加请求 | /new | id, name, age, gender, hobbies | 发送编写的数据 | |
| 修改学生目录 | /update | id | 跳转到能修改学生数据的页面 | |
| 发送修改请求 | /update | id, name, age, gender, hobbies | 发送修改好的数据 | |
| 删除学生目录 | /delete | id | 删除一条数据 |
- 由wordPress的开发团队进行开发
- 表就是关系或者说表与表之间的关系
- 所有关系型数据库都需要sql来操作
- 所有关系型数据库在操作前都需要设计表结构
- 数据库支持约束关系
- 主键
- 唯一
- 必须
- 等等
- 非关系型数据库就非常灵活甚至有的数据库就是key-value
- MongoDB是最像关系型数据库的非关系型数据库
- MongoDB不需要设计表结构
- 因此可以任意
| 关系型数据库 | MongoDB |
|---|---|
| 数据库 | 数据库 |
| 表 | 集合(数组) |
| 表纪录 | 文档对象 |
- 下载
- 安装
- 配置环境变量
- 控制台中输入mongod --version确保安装成功
- 在控制台输入
mongod会默认在控制台路径的根目录中的data文件来存放数据库数据, 没有此文件则报错 - 在控制台输入
mongod --dbpath=路径可以指定db文件安装目录 - 再次输入
mongod可以开启数据库 - 在控制台按下ctrl+c就能关闭数据库
- 在控制台输入
mongo会默认连接本地数据库 - 输入
exit能退出连接
show dbs显示当前所有的数据库,新数据库只有插入数据后才会显示use testbd切换到指定数据库中db查看当前所在数据库db.students.insertOne({"name": "a", "age": "11"})在当前数据库中建立集合students并插入一条数据show collections查看当前数据库中的所有集合db.students.find()查看students集合中所有的文档对象
- 安装
npm i mongoose - 使用
let mongoose = require('mongoose')
//第一步:连接数据库
mongoose.connect('mongodb://localhost/test')
//第二步:创建文档结构
let userSchema = new mongoose.Schema({
name: {
type: String,
require: true
},
password: {
type: String,
require: true
},
email : {
type: String,
require: false
}
})
//第三步:将文档结构应用给文档模型
//第一个参数: 约定是使用第一个字母为大写的单数形式,mongoose会创建一个将此参数调整为小写的复数形式的集合
//第二个参数:需要应用的文档结构
let Users = mongoose.model('User', userSchema)
//第四步:通过模型新增文档对象
Users.create({
name: '张三妹',
password: '1223445',
email: 'aaa@qq.com'
}, (err)=>{
if(err){
console.log('保存失败!')
}else{
console.log('保存成功!')
}
})
//或者实例化模型生成文档实例,最后save来保存修改
let user = new Users({
name: '张三妹',
password: '1223445',
email: 'aaa@qq.com'
})
user.save((err, ret)=>{
if(err){
console.log('保存失败!')
}else{
console.log('保存成功!', ret)
}
})- 查询
//查询满足条件的所有文档对象
//model.find(匹配条件, 投影, 筛选条件, 回调函数)
//投影决定查询出来的字段,可以通过'字段1 -字段2'的形式决定显示与不显示的字段
//筛选条件可用skip,limit,sort等
//model.count(conditions, callback) 用来返回查询到的数据数
users.find({name: '张三妹'}, (err, ret)=>{
if(err){
console.log('查询失败!')
}else{
console.log('查询成功!', ret)
}
})
//查询满足条件的第一条文档对象
users.findOne({name: '张三妹'}, (err, ret)=>{
if(err){
console.log('查询失败!')
}else{
console.log('查询成功!', ret)
}
})- 修改
//model.update(conditions, obj, [options], [callback])
//options: multi:true
users.findByIdAndUpdate({'_id': '5cf4cbbce4837e1c10075aa7'}, {
password: '1223'
}, (err, ret)=>{
if(err){
console.log('修改失败!', err)
}else{
console.log('修改成功!', ret) //ret值为查询到的文档对象修改前的数据
}
})
//通过递归修改
users.findById({'_id': '5cf4cbbce4837e1c10075aa7'}, (err, ret)=>{
if(err){
console.log('修改失败!', err)
}else{
ret.update({ //在查询到的文档基础上调用方法
$set: {
password: '1223'
}
},err=>{
if(err) return
console.log('修改成功!')
})
}
})- 删除
//model.remove
//model.deleteOne
//model.deleteMany
users.findByIdAndDelete({'_id': '5cf4cbbce4837e1c10075aa7'}, (err, ret)=>{
if(err){
console.log('删除失败!', err)
}else{
console.log('删除成功!', ret) //ret的值为删除前查询到的文档对象的数据
}
})- mongoose模块化 将连接流程封装到一个文件中,将模型对象直接赋值给module.exports
修改版为文件夹CRUD-express中的router-mongodb.js
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});
connection.connect();
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
connection.end();- 顺序编写异步函数执行顺序不确定
fs.readFile('a.txt', (err, data)=>{
if(err){
//JS语法,抛出异常,两个作用:
//1. 阻止程序继续执行
//2. 将错误消息打印到控制台中
throw err
}else{
console.log('a')
}
})
fs.readFile('b.txt', (err, data)=>{
if(err){
//JS语法,抛出异常,两个作用:
//1. 阻止程序继续执行
//2. 将错误消息打印到控制台中
throw err
}else{
console.log('b')
}
})
fs.readFile('c.txt', (err, data)=>{
if(err){
//JS语法,抛出异常,两个作用:
//1. 阻止程序继续执行
//2. 将错误消息打印到控制台中
throw err
}else{
console.log('c')
}
})- 当需要异步操作按照顺序执行时只能使用回调函数嵌套的方式
$.get('/a', (req, res)=>{
setTimeout(()=>{
$.post('/b', (req, res)=>{
setTimeout(()=>{
...
})
})
})
})- 这种编码方式嵌套层数过多,且会难以维护
function pReadFile(filepath){
return new Promise((resolve, reject)=>{
fs.readFile(filepath, (err, data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
pReadFile('a.txt')
.then(data=>{
console.log(data)
//如果返回的是一个promise对象,则下一个then方法中的形参就是这个promise对象resolve
//如果不是,则下一个then方法中的形参就是这个返回的值
return pReadFile('b.txt')
}).then(data=>{
console.log(data)
return pReadFile('c.txt')
}).then(data=>{
console.log(data, 'end')
})- 新版jQuery的ajax相关函数都支持promise的形式
function pAjax(router, callback){
return new Promise((resolve, reject)=>{
let xhr = new XMLHttpRequest()
xhr.onload = function(data){
callback&&callback(null, data)
resolve(JSON.parse(data))
}
xhr.onerror = function(err){
callback&&callback(err)
reject(err)
}
xhr.open('get', router)
xhr.send()
})
}
pAjax('/a')
.then(data=>{
console.log(data)
return pAjax('/b')
}).then(data=>{
console.log(data)
return pAjax('/c')
}).catch(err=>{
console.log(err)
})- blog文件夹
- path.baseName 获取带后缀名的文件名
- path.dirName 获取目录路径
- path.extName 获取文件后缀名
- path.parse 以上方法的结合
- path.isAbsolute 判断是不是绝对路径
- path.join //将多个路径转换成一个完整路径,会自动修复直接将路径相加时造成的错误
- __dirname 表示当前文件模块所处的绝对路径
- __filename 表示当前文件模块的绝对路径
- 在node中进行文件操作时,如果路径格式是相对路径,则node会自动按照执行node命令的命令行路径进行换算
- 为了避免没有按照预期路径读写文件,都可以使用
path.join(__dirname, './')的形式来表示需要读写的文件路径 - 模块文件路径标识就是按照文件的相对路径进行加载,因此无需按照上面的规则加载
.
|---app.js
|---controller
|---models
|---node_modules 第三方模块
|---package.json 包描述文件
|---package-lock.json 第三方包版本锁定文件
|---public 静态资源文件
|---README.md 项目描述文件
|---routes
|---views 存储视图目录
- 将用户敏感数据保存在服务端中并给用户生成一个经过加密处理过的cookie字符串
- 每次访问时比对用户的cookie字符串来验证用户身份从而执行一系列操作
- 安装
npm install express-session
- 引入
let session = require('express-session')- 配置
app.use(session({
secret: 'aaaa', //决定用户数据与什么字符串共同生成加密cookie,提高安全等级
resave: false, //决定是否重新更新当前session的生命周期当session过期时无论session数据有没有被修改过
saveUninitialized: false //决定是否要初始化session. 设置为false时,如果服务端没有保存过session数据则不会向客户端发送session的cookie
}))- 使用
req.session.user = user //保存session数据
req.session.user //获取session数据- 从请求到响应的过程中的一系列封装方法
- 每个封装方法就是中间件
- 由于这些封装方法在响应之前,因此顺序会有严格要求
- 甚至有些中间件会依赖其他中间件
- 同一次请求中的不同中间件中的req参数是同一个变量
- 当进入一个中间件时如果不调用next方法则会停留在这个中间件中
- 执行next方法会调用下一个匹配的中间件
- 在express中,对中间件有几种分类
- 不关心请求方法与请求路径的中间件,即任何请求都会进入这个中间件
app.use((req, res, next)=>{
next() //此方法会调用下一个匹配到的中间件,如果没有则默认在页面显示not get (路径)
})- 关心请求路径的中间件
app.use('/', (req, res, next)=>{})- 严格匹配请求方法与请求路径的中间件
app.get('/', (req, res, next)=>{})
app.post('/', (req, res, next)=>{})app.use('/', (req, res, next)=>{
let err = true
if(err){
next(new Error()) //当给next传入参数时,直接跳转到错误处理中间件
}
})
app.use('/', (err, req, res, next)=>{ //必须为四个参数否则不是错误处理中间件
console.log(err) //err的值是上个next中传入的参数值
res.send(404)
})