Node.js基础笔记
1. Node.js介绍
1. 为什么要学Node.js
1. 企业需求
- 具有服务端开发经验更好
- front-end (前端)
- back-end (后端)
- 全栈开发工程师(全干)
- 基本的网站开发能力
- 服务端
- 前端
- 运维部署
2.
2. Node.js是什么
1. Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
- Node.js不是一门语言
- Node.js不是库,不是框架
- Node.js是一个JavaScript运行时环境
- 简单点来讲就是Node.js可以解析和执行JavaScript代码
- 以前只有浏览器可以解析执行JavaScript代码
- 也就是说现在的JavaScript可以完全脱离浏览器来运行,一切都归功于Node.js
2. 浏览器中的JavaScript
- ECMAScript
- 基本语法
- if (循环)
- function (函数)
- Object (对象)
- Array (数组)
- BOM
- DOM
3. Node.js中的JavaScript
- 没有BOM、DOM
- ECMAScript
- 在Node这个JavaScript执行环境中为JavaScript提供了一些服务器级别的操作API
- 例如文件读写
- 网络服务的构建
- 网络通信
- HTTP服务器
- 。。。
- 构建于Chrome的V8引擎之上
- 代码只是具有特定格式的字符串而已
- 引擎可以认识它,引擎可以帮你去解析和执行
- Google Chrome的V8引擎是目前公认的解析执行JavaScript代码最快的
- Node.js的作者把 Google Chrome 中的 V8 引擎移植了出来,开发了一个独立的 JavaScript 运行时环境
- Node.js uses an event-driven,non-blocking I/O model that makes it lightweight and efficient
- event-driven 事件驱动
- non-blocking I/O model 非阻塞IO模型(异步)
- lightweight and efficient 轻量和高效
- Node.js’ package ecosystem,npm,is the largest ecosystem of open sourse libraries in the world
- npm是世界上最大的开源库生态系统
- 绝大多数JavaScript相关的包都存放在了npm中,这样做的目的是为了让开发人员更方便的去下载使用
- 例如:
npm install jquery
3. Node.js能做什么
- Web服务器后台
- 命令行工具
- npm(node)
- git (c)
- hexo(node)
- 。。。
- 对于前端开发工程师来讲,接触 node 最多的是它的命令行工具
- 自己写的很少,主要是使用别人第三方的
- webpack
- gulp
- npm
4. 预备知识
- HTML / CSS
- JavaScript
- 基本的命令行操作
- cd — 打开目录、切换目录、返回上级目录、返回根目录
- dir — 查看目录内容
- ls — 显示文件或目录
- mkdir — 创建文件夹
- rm — 删除文件
- 具有服务端开发经验更佳
5. 相关资源
- 《深入浅出Node.js》
- 朴灵
- 偏理论,几乎没有任何实战型内容
- 理解原理底层有帮助
- 结合课程的学习去看
- 《Node.js权威指南》
- API讲解
- 没有实战,没有业务
- JavaScript 标准参考教程(alphad): http://javascript.ruanyifeng.com/
- Node入门:http://www.nodebeginner.org/index-zh-cn.html
- 官方API文档:https://nodejs.org/dist/latest-v6.x/docs/api/
- 中文文档(版本比较旧):http://www.nodeclass.com/api/node.html
- CNODE社区:http://cnodejs.org
- CNODE-新手入门:http://cnodejs.org/getstart
6. Node.js 能学到什么
1. B/S编程模型
- Browser-Server
- back-end
- 任何服务端技术这种BS编程模型都是一样,和语音无关
- Node 只是作为我们学习BS编程模型的一个工具而已
2. 模块化编程
- RequireJS
- SeaJS
@import('文件路径')
- 以前认知的 JavaScript 只能通过 script 标签来加载
- 在 Node.js中可以像
@import()
一样来引用加载 JavaScript脚本文件
3. Node常用API
- 。。。
4. 异步编程
- 回调函数
- Promise
- async
- generator
5. Express Web 开发框架
6. ECMAScript 6
7. 前端高级框架
- Vue.js
- React
- Angular
2. Node.js 起步
1. 安装Node环境
- 查看当前 Node 环境的版本号
- 下载:https://nodejs.org/en/download/
- 安装
- 确认 Node 环境是否安装成功
- 打开命令行,输入
node --version
- 打开命令行,输入
- 环境变量
2. HelloWorld
- 创建编写的 JavaScript 脚本文件
- 打开终端,定位到脚本文件所属目录
- 输入
node 文件名
执行对应的文件
注意:文件名不要用node.js
来命名,也就是说除了node
,其他随便起,最好别用中文
3. 解析执行 JavaScript
// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')
4. 文件读写及简单错误处理
- 文件读取:
// 浏览器中的 Javascript 是没有文件操作的能力的
// 但是 Node 中的 JavaScript 具有文件操作的能力
// fs 是 file-system 的简写,就是文件系统的意思
// 在 Node 中如果想要进行文件操作,就必须引入 fs 这个核心模块
// 在 fs 这个核心模块中,就提供了所有的文件操作相关的 API
// 例如:fs.readFile 就是用来读取文件的
// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')
// 2. 读取文件
// 第一个参数就是要读取的文件路径
// 第二个参数是一个回调函数
// 成功
// data 数据
// error null
// 失败
// data undefined 没有数据
// error 错误对象
fs.readFile('./data/hello.txt',function(error,data){
// 文件中存储的其实都是二进制数据 0 和 1
// 为什么看到的不是二进制,是由于二进制转换为了十六进制了
// 可以通过 toString 方法把其转化为我们能认识的字符
// console.log(data.toString());
// 错误判断
if(error){
console.log('读取文件失败')
}else{
console.log(data.toString());
}
})
- 文件写入:
// 第一个参数:文件路径
// 第二个参数:文件内容
// 第三个参数:回调函数
// error
// 成功:
// 文件写入成功
// error 是 null
// 失败:
// 文件写入失败
// error 就是错误对象
fs.writeFile('./data/你好.md','大家好,给大家介绍一下,我是Node.js',function(error){
// 判断
if(error){
console.log('写入失败');
}else{
console.log('写入成功');
}
})
5. 简单的 http 服务
- 发送请求
// 在 Node 中专门提供了一个核心模块: http
// http 这个模块的职责就是帮你创建编写服务器的
// 1. 加载 http 核心模块
var http = require('http')
// 2. 使用 http.createServer() 方法创建一个 Web 服务器
// 返回一个 Server 实例
var server = http.createServer()
// 3. 服务器要干嘛?
// 提供服务: 对数据的服务
// 发请求
// 接受请求
// 处理请求
// 反馈(发送响应)
// 注册 request 请求事件
// 当客户端请求过来,就会自动触发服务器的 request 请求事件,然后执行第二个参数;回调处理函数
server.on('request',function(){
console.log('收到客户端的请求了');
})
// 4. 绑定端口号,启动服务器
server.listen(3000,function(){
console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问');
})
- 发送响应
var http = require('http');
var server = http.createServer();
// request 请求事件处理函数,需要接收两个参数;
// Request 请求对象
// 请求对象可以用来获取客户端的一些请求信息,例如请求路径
// Response 响应对象
// 响应对象可以用来给客户端发送响应信息
server.on('request',function(request,response){
console.log('收到客户端的请求了,请求路径是:'+ request.url);
// response 对象有一个方法:write 可以用来给客户端发送响应数据
// write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待
response.write('hello node.js');
// 告诉客户端,话说完了,可以呈递用户
response.end();
})
server.listen(3000,function(){
console.log('服务器启动成功,可以通过 localhost:3000 进行访问');
})
6. 根据不同请求路径返回不同数据
// 根据不同的请求路径返回不同数据
var http = require('http');
// 1. 创建 Server
var server = http.createServer();
// 2. 监听 request 请求事件,设置请求处理函数
server.on('request',function(req,res){
// console.log('收到请求了,请求路径是:'+req.url);
// res.write('hello');
// res.write(' world');
// res.end();
// 上面的方式比较麻烦,推荐使用更简单的方式,直接 end 的同时发送响应数据
// res.end('hello world');
// 根据不同的请求路径发送不同的响应结果
// 1. 获取请求路径
// req.url 获取到的是端口号之后的那一部分路径
// 也就是说所有的 url 都是以 / 开头的
// 2. 判断路径处理响应
var url = req.url;
// if(url === '/'){
// res.end('index page')
// }else if (url === '/login') {
// res.end('login page')
// }else {
// res.end('404 Not Found')
// }
if (url === '/products') {
var products = [
{
name: '苹果',
price: 8888
},
{
name: '香蕉',
price: 5888
},
{
name: '菠萝',
price: 3888
}
]
// 响应内容只能是二进制数据或者字符串
// 数字
// 对象
// 数组
// 布尔值
// 都不行
res.end(JSON.stringify(products))
}
})
// 3. 绑定端口号,启动服务
server.listen(80,function(){
console.log('服务器启动成功, 可以访问!');
})
7. each 与 forEach
jQuery 的 each 和原生的 JavaScript 方法 forEach
- EcmaScript 5 提供的
- 不兼容 IE8
- jQuery 的 each 由 jQuery 这个第三方库提供
- jQuery 2 以下的版本是兼容 IE8 的
- 他的 each 方法主要用来遍历 jQuery 实例对象(伪数组)
- 同时它也可以作为低版本浏览器中 forEach 替代品
- jQuery 的实例对象不能使用 forEach 方法,如果想要使用必须转为数组才可以使用
[].slice.call(jQuery实例对象)
8. 文件操作路径和模块路径
- 文件操作路径:
在文件操作的相对路径中
./data/a.txt 相对于当前目录
data/a.txt 相对于当前目录
/data/a.txt 绝对路径,当前文件模块所处磁盘根目录
c:/xx/xx.... 绝对路径
fs.readFile('./data/a.txt',function(err,data){
if(err){
console.log(err);
return console.log('读取失败');
}
console.log(data.toString());
})
- 模块操作路径:
// 这里如果忽略了,则也是磁盘根目录
require('/data/foo.js')
// 相对路径
require('./data/foo.js')
// 模块加载的路径中的相对路径不能省略 ./
9. 使用 nodemon 自动重启
- 我们这里可以使用一个第三方命名航工具:
nodemon
来帮我们解决频繁修改代码重启服务器问题 - nodemon 是一个基于 Node.js 开发的一个第三方命令行工具,我们使用的时候需要独立安装:
// 在任意目录执行该命令都可以
// 也就是说,所有需要 --global 来安装的包都可以在任意目录执行
npm install --global nodemon
安装完毕之后,使用:
node app.js
// 使用 nodemon
nodemon app.js
只要是通过 nodemon app.js
启动的服务,则它会监视你的文件变化,当文件发生变化的时候,自动帮你重启服务器
3. Node 中的 JavaScript
- ECMAScript
- 没有 BOM, DOM
- 核心模块
- 第三方模块
- 用户自定义模块
1. 核心模块
- Node 为 JavaScript 提供了很多服务器级别的 API ,这些 API 绝大多数都被包装到了一个具名的核心模块中了。
- 例如文件操作的
fs
核心模块,http 服务构建的http
模块,path
路径操作模块,os
操作系统信息模块。。。 - 以后只要说这个模块是一个核心模块,就要马上想到如果想要使用它,就必须:
// 用来操作文件系统
var fs = require('fs')
// 用来加载 http
var http = require('http')
// 用来获取操作系统
var os = require('os')
// 用来操作路径
var path = require('path')
...
2. 简单的模块化
- JavaScript 天生不支持模块化,
require
,exports
,是 Node.js 才有的 require
是一个方法- 他的作用就是用来加载执行文件模块中的代码
- 在 Node 中,模块有三种:
- 具名的核心模块,例如
fs
,http
- 用户自己编写的文件模块
- 相对路径必须加 ./
- 可以省略后缀名
- 相对路径中的 ./ 不能省略,否则报错
- 在 Node 中,没有全局作用域,只有模块作用域
- 外部访问不到内部
- 内部也访问不到外部
- 具名的核心模块,例如
a.js
var foo = 'aaa'
console.log('a start');
function add(x,y){
return x + y;
}
// Error:Cannot find module 'b'
// require('b')
require('./b.js');
// 推荐:可以省略后缀名
require('./b')
console.log('a end');
console.log('foo 的值是' + foo);
b.js
// 不能访问外部文件
// 外部文件也不能访问此文件
console.log('b start');
console.log(add(10,20)); // 不能获取到 a.js 中的 add 方法
var foo = 'bbb'; // 与 a.js 中的 foo 不冲突
require('./c.js');
console.log('b end');
c.js
console.log('ccc');
显示结果:
3. 加载与导出
-
模块作用域默认是封闭的
-
如何让模块与模块之间进行通信
-
有时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要的是为了使用里面的某个成员
-
require 方法有两个作用:
- 加载文件模块并执行里面的代码
- 拿到被加载文件模块导出的接口对象
-
在每个文件模块中都提供了一个对象:exports
- exports 默认是一个空对象
- 你要做的就是把所有需要被外部访问的成员挂载到这个 exports 对象中
a.js
var bExports = require('./b');
var fs = require('fs');
console.log(bExports.foo);
console.log(bExports.add(10,20));
console.log(bExports.age);//未挂载,访问不到
bExports.readFile('./a.js');
fs.readFile('./a.js',function(err,data){
if (err) {
console.log('读取文件失败');
}else{
console.log(data.toString());
}
})
b.js
var foo = 'bbb'
// console.log(exports);
exports.foo = 'hello'
exports.add = function(x,y){
return x+y;
}
exports.readFile = function(path,callback){
console.log('文件路径:',path);
}
var age = 18;
function add(x,y){
return x-y;
}
4. 第三方模块
- 第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
npm
- 开放人员可以把写好的框架,库发布到
npm
上 - 使用者在使用的时候就可以很方便的通过
npm
来下载
- 开放人员可以把写好的框架,库发布到
- 使用方式:
var 名字 = require('npm install 包名)
node_modules
node_modules/express
node_modules/express/package.json
node_modules/express/package.json main
- 如果
package.json
或者package.json main
不成立,则查找备选项:index.js
- 如果以上条件都不成立,则继续进入上一级目录中的
node_modules
按照上面的规则继续查找 - 如果直到当前文件模块所属磁盘根目录都找不到,最后报错:
can not find module xxx
5. http
- require
- 端口号
- ip 地址定位计算机
- 端口号定位具体的应用程序
- Content-Type
- 服务器最好把每次响应的数据是什么内容类型都告诉客户端,而且要正确的告诉
- 不同的资源对应的 Content-Type 是不一样,具体参照:http://tool.oschina.net/commons
- 对于文本类型的数据,最好都加上编码,目的是为了防止中文解析乱码问题
- 通过网络发送文件
- 发送的并不是文件,本质上来讲发送时文件的内容
- 当浏览器收到服务器响应内容之后,就会根据你的 Content-Type 进行对应的解析处理
6. 异步编程
- 如果需要得到一个函数内部异步操作的结果,这时候必须通过
回调函数
来获取 - 在调用的位置传递一个函数进来
- 在封装的函数内部调用传递进来的函数
- 不成立的情况:
function add(x,y){
console.log(1)
setTimeout(function(){
console.log(2)
var ret = x + y
return ret
}, 1000)
console.log(3)
// 到这里执行就结束了,不会等到前面的定时器,所以直接就返回了默认值 undefined
}
console.log(add(10,20)) // => undefined
- 不成立的情况:
function add(x,y){
var ret
console.log(1)
setTimeout(function(){
console.log(2)
ret = x + y
}, 1000)
console.log(3)
return ret
}
console.log(add(10,20)) // => undefined
- 回调函数:
function add(x,y,callback){
// callback 就是回调函数
// var x = 10
// var y = 20
// var callback = function(ret){ console.log(ret) }
console.log(1)
setTimeout(function(){
var ret = x + y
callback(ret)
},1000)
}
add(10,20,function(ret){
console.log(ret)
})
- 基于原生 XMLHTTPRequest 封装 get 方法:
function get(url,callback){
var oReq = new XMLHttpRequest()
// 当请求加载成功之后要调用指定的函数
oReq.onload = function(){
// 我现在需要得到这里的 oReq.responseText
callback(oReq.responseText)
}
oReq.open("get",url,true)
oReq.send()
}
get('data.json', function(data){
console.log(data)
})
4. Web 服务器开发
1. ip 地址和端口号
- ip地址用来定位计算机,端口号用来定位具体的应用程序
- 一切需要联网通信的软件都会占用一个端口号,端口号的范围从 0-65536 之间
- 在计算机中有一些默认端口号,最好不要去使用,例如 http 服务的 80
- 可以同时开启多个服务,但一定要确保不同服务占用的端口号不一致才可以
- 一台计算机中,同一个端口号同一时间只能被一个程序占用
- 所有联网的程序都需要进行网络通信
- 计算机中只有一个物理网卡,而且同一个局域网中,网卡的地址必须唯一
- 网卡是通过唯一的 ip 地址来进行定位的
var http = require('http')
var server = http.createServer()
// 2. 监听 request 请求事件,设置请求处理函数
server.on('request',function(req,res){
console.log('收到请求了,请求路径是:' + req.url);
console.log('请求我的客户端的地址是:',req.socket.remoteAddress,req.socket.remotePort);
res.end('hello nodejs')
})
server.listen(3000,function(){
console.log('服务器启动成功,可以访问!');
})
2. Content-Type 响应内容类型
- http://tool.oschina.net/
- 在服务器默认发送的数据,其实是 utf8 编码的内容,但是浏览器不知道你是 utf8 编码的内容
- 浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码去解析
- 中文操作系统默认是 gbk
- 解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的
- 在 http 协议中,Content-Type 就是用来告知对方我发送的数据内容是什么类型
var http = require('http')
var server = http.createServer()
server.on('request',function(req,res){
// res.setHeader('Content-Type','text/plain;charset=utf-8')
// res.end('hello 世界')
var url = req.url;
if(url === '/plain'){
// text/plain 就是普通文本
res.setHeader('Content-Type','text/plain; charset=utf-8')
res.end('hello 世界')
}else if(url === '/html'){
// 如果你发送的是 html 格式的字符串,则也要告诉浏览器我给你发送的是 text/html 格式的内容
res.setHeader('Content-Type','text/html; charset=utf-8')
res.end('<p>hello html <a href = ''>点我</a></p>')
}
})
server.listen(3000,function(){
console.log('Server is running');
})
3. 发送文件数据
- 结合 fs 发送文件中的数据
- Content-Type
- http://tool.oschina.net/commons
- 不同的资源对应的 Content-Type 是不一样的
- 图片不需要指定编码
- 一般只为字符数据才指定编码
var fs = require('fs')
var http = require('http')
var server = http.createServer()
server.on('request',function(req,res){
var url = req.url
if (url === '/') {
// 肯定不这么干
// res.end('<!DOCTYPE html><html lang="]" dir="ltr"><head><meta charset="utf-8"><title></title></head><body><h1>首页</h1></body></html>')
// 我们要发送的还是在文件中的内容
fs.readFile('./resource/index.html',function(err,data){
if (err) {
res.setHeader('Content-Type', 'text/plain;charset=utf-8')
res.end('请求失败')
} else {
// data 默认是二进制数据,可以通过 .toString 转化为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end(data)
}
})
} else if(url === '/img'){
// url: 统一资源定位符
// 一个 url 最终其实是要对应到一个资源的
fs.readFile('./resource/1.jpg',function(err,data){
if (err) {
res.setHeader('Content-Type', 'text/plain;charset=utf-8')
res.end('请求失败')
}else{
// data 默认是二进制数据,可以通过 .toString 转化为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
// 图片就不需要指定编码了,因为我们常说的编码一般指的是:字符编码
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
})
server.listen(8000, function() {
console.log('Server is running');
})
4. 实现 Apache 目录列表渲染
- 如何得到 wwwDir 目录列表中的文件名和目录名
- fs.readdir
- 如何将得到的文件名和目录名替换到 template.html 中
- 在 template.html 中需要替换的位置预留一个特殊的标记(就像以前使用模版引擎的标记一样)
- 根据 files 生成需要的 HTML 内容
var http = require('http')
var fs = require('fs')
var server = http.createServer()
var wwwDir = 'D:/Movie/www'
server.on('request',function(req,res){
var url = req.url
fs.readFile('./template.html',function(err, data){
if(err){
return res.end('404 not found')
}
fs.readdir(wwwDir,function(err,files){
if(err){
return res.end('can not find www dir')
}
// 2.1 生成需要替换的内容
var content = ''
files.forEach(function(item){
// 在 ES6 中的 ` 字符串中,可以使用 ${} 来引用变量
content += `
<tr>
<td data-value="apple/"><a class="icon dir" href="/D:/Movie/www/apple/">${item}/</td>
<td class="detailsColumn" data-value="0"></td>
<td class="detailsColumn" data-value="1509589967">2017/11/2 上午10:32:47</td>
</tr>
`
})
// 2.3 替换
data = data.toString()
// 普通的字符串解析替换,浏览器看到的结果就不一样了
data = data.replace('>_<',content)
console.log(data);
// 3. 发送解析替换过后的响应数据
res.end(data)
})
})
})
// 3. 绑定端口号,启动服务
server.listen(3000,function(){
console.log('running');
})
5. 在 Node 中使用模板引擎
-
在 node 中使用模版引擎
-
art-template 不仅可以在浏览器使用,也可以在 node 中使用
-
安装:
npm install art-template
-
该命令在哪里执行就会把包下载到哪里,默认会下载到 node_modules 目录中
-
node_modules 不要改,也不支持改
-
在需要使用的文件模块中加载 art-template
- 只需要使用 require 方法加载就可以了: require(‘art-template’)
- 参数中的 art-template 就是你下载的包的名字
- 也就是说你 install 的名字是什么,则你 require 中的就是什么
-
查文档,使用模版引擎的 API
var template = require('art-template')
var fs = require('fs')
fs.readFile('./tpl.html',function(err,data){
if(err){
return console.log('读取文件失败');
}
// 默认读取到的 data 是二进制数据
// 而模版引擎的 render 方法需要接收的是字符串
// 所以我们在这里需要把 data 二进制数据转为字符串才可以给模版引擎使用
var ret = template.render(data.toString(),{
name: 'Jack',
age: 18,
province: '北京',
hobbies:[
'写代码',
'唱歌',
'打篮球'
]
})
console.log(ret);
})
6. 统一处理静态资源
- app application 应用程序
- 把当前模块所有的依赖项都声明在文件模块最上面
- 为了让目录结构保持统一清晰,所以我们约定,把所有的 HTML 文件都放到 views(视图)
- 我们为了方便的统一处理这些静态资源,所以我们约定把所有的静态资源都存放在 public 的目录中
- 哪些资源能被用户访问,哪些资源不能被用户访问,我现在可以通过代码来进行非常灵活的
- 统一处理:
- 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
- 所以我们就直接可以把请求路径当作文件路径来直接进行读取
var http = require('http')
var fs = require('fs')
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (url.indexOf('/public/') === 0) {
fs.readFile('.' + url,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
7. 客户端渲染与服务端渲染
- 客户端渲染
- 服务器端渲染
- 客户端渲染与服务端渲染的区别
- 客户端渲染不利于 SEO 搜索引擎优化
- 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
- 所以你会发现真正的网站既不是纯异步也不是纯服务端渲染出来的
- 而是两者结合起来的
- 例如京东的商品列表就采用的是服务端渲染,目的是为了 SEO 搜索引擎优化
- 而它的商品评论列表为了用户体验,而且也不需要 SEO 优化,所以采用的是客户端渲染
5. 留言本案例
1. 页面跳转及404处理
var http = require('http')
var fs = require('fs')
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if(url === '/post.html'){
fs.readFile('./views/post.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (url.indexOf('/public/') === 0) {
fs.readFile('.' + url,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else{
// 其他的都处理成 404 找不到
fs.readFile('./views/404.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
2. 渲染评论首页
- html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/public/css/main.css">
</head>
<body>
<div class="header-container">
<div class="page-header">
<h1>Example page header <small>Subtext for header</small></h1>
<a href="/post.html" class="btn btn-success">发表留言</a>
</div>
</div>
<div class="comments container">
<ul class="list-group">
{{each comments}}
<li class="list-group-item">{{ $value.name }}说:{{ $value.message }}
<span class="pull-right">{{ $value.dateTime }}</span>
</li>
{{/each}}
</ul>
</div>
</body>
</html>
- js
var http = require('http')
var fs = require('fs')
var template = require('art-template')
// 模拟数据
var comments = [
{
name: '张三',
message: '今天天气不错',
dateTime: '2015-10-16'
},
{
name: '张三2',
message: '今天天气不错',
dateTime: '2015-10-16'
},
{
name: '张三3',
message: '今天天气不错',
dateTime: '2015-10-16'
},
{
name: '张三4',
message: '今天天气不错',
dateTime: '2015-10-16'
},
{
name: '张三5',
message: '今天天气不错',
dateTime: '2015-10-16'
},
{
name: '张三6',
message: '今天天气不错',
dateTime: '2015-10-16'
},
]
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
var htmlStr = template.render(data.toString(),{
comments:comments
})
res.end(htmlStr)
})
}else if(url === '/post.html'){
fs.readFile('./views/post.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (url.indexOf('/public/') === 0) {
fs.readFile('.' + url,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else{
// 其他的都处理成 404 找不到
fs.readFile('./views/404.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
3. 处理表单get请求
- 对于这种表单提交的请求路径,由于其中具有用户动态填写的内容
- 所以你不可能通过去判断完整的 url 路径来处理这个请求
- 结论:对于我们来讲,其实只需要判定,如果你的请求路径是 /pinglun 的时候,那我就认为你提交的表单已经过来了
- 注意:这个时候无论 /pinglun?xxx 之后是什么,我都不用担心了,
- 因为我的 pathname 是不包含 ? 之后的那个路径
- 一次请求对应一次响应,响应结束这次请求也就结束了
- 我们已经使用 url 模块的 parse 方法把请求路径中的查询字符串给解析成一个对象了
- 所以接下来要做的就是:
- 获取表单提交的数据 parseObj.query
- 将当前时间日期添加到数据对象中,然后存储到数组中
- 让用户重定向跳转到首页 /
- 当用户重新请求 / 的时候,我数组中的数据已经发生变化了,所以用户看到的页面也就变了
http
.createServer(function(req,res){ // 简写方式
// 使用 url.parse 方法将路径解析为一个方便操作的对象
// 第二个参数为 true 表示直接将查询字符串转为一个对象
// 通过 (query)属性来访问
var parseObj = url.parse(req.url,true)
// 单独获取不包含查询字符串的路径部分(该路径不包含 ? 之后的内容)
var pathname = parseObj.pathname
if(pathname === '/'){ // 首页
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
var htmlStr = template.render(data.toString(),{
comments:comments
})
res.end(htmlStr)
})
}else if(pathname === '/post'){ // 发表评论
fs.readFile('./views/post.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (pathname.indexOf('/public/') === 0) {
// 统一处理:
// 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
// 所以我们就直接可以把请求路径当作文件路径来直接进行读取
fs.readFile('.' + pathname,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if(pathname === '/pinglun'){
var comment = parseObj.query
comment.dateTime = '2020'
comments.unshift(comment)
// 服务器这个时候已经把数据存储好了,接下来就是让用户重新请求 / 首页,就可以看到最新的留言内容了
// 通过响应头来实现服务端重定向
res.writeHead(302,{
'Location': '/'
})
res.end()
}else{
// 其他的都处理成 404 找不到
fs.readFile('./views/404.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
4. 表单提交重定向
- 如何通过服务器让客户端重定向?
- 状态码设置为 302 临时重定向
- statucCode
- 在响应头中通过 Location 告诉客户端往哪儿重定向
- setHeader
- 状态码设置为 302 临时重定向
- 如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location
- 然后对该地址发起新的请求
- 所以你就能看到客户端自动跳转了
else if (pathname.indexOf('/public/') === 0) {
// 统一处理:
// 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
// 所以我们就直接可以把请求路径当作文件路径来直接进行读取
fs.readFile('.' + pathname,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if(pathname === '/pinglun'){
var comment = parseObj.query
comment.dateTime = '2020'
comments.unshift(comment)
// 服务器这个时候已经把数据存储好了,接下来就是让用户重新请求 / 首页,就可以看到最新的留言内容了
// 通过响应头来实现服务端重定向
res.writeHead(302,{
'Location': '/'
})
res.end()
}
6. Node 中的模块系统
-
使用 Node 编写应用程序主要就是在使用:
- EcmaScript 语言
- 和浏览器不一样,在 Node 中没有 BOM,DOM
- 核心模块
- 文件操作的 fs
- http 服务的 http
- url 路径操作模块
- path 路径处理模块
- os 操作系统信息
- 第三方模块
- art-template
- 必须通过 npm 来下载才可以使用
- 自定义模块
- EcmaScript 语言
-
在 Node 中没有全局作用域的概念
-
在 Node 中,只能通过
require
方法来加载执行多个 JavaScript 脚本文件 -
require
加载只能是执行其中的代码,文件与文件之间由于是模块作用域,所以不会有污染的问题- 模块完全是封闭的
- 外部无法访问内部
- 内部也无法访问外部
-
模块作用域固然带来了一些好处,可以加载执行多个文件,可以完全避免变量命名冲突污染的问题
-
但是某些情况下,模块与模块实需要进行通信的
-
在每一个模块中,都提供了一个对象:
exports
-
该对象默认是一个空对象
-
你要做的就是把需要被外部访问使用的成员手动的挂载到
exports
接口对象中 -
然后谁来
require
这个模块,谁就可以得到模块内部的exports
接口对象
1. 什么是模块化
- 文件作用域
- 通信规则
- 加载 require
- 导出
2. CommonJS 模块规范
在 Node 中的 JavaScript 还有一个很重要的概念,模块系统。
- 模块作用域
- 使用 require 方法用来加载模块
- 使用 exports 接口对象用来导出模块中的成员
1. 加载 require
- 语法:
var 自定义变量名称 = require('模块')
- 两个作用:
- 执行被加载模块中的代码
- 得到被加载模块中的
exports
导出接口对象
2. 导出 exports
- Node 中是模块作用域,默认文件中所有的成员只在当前文件模块有效
- 对于希望可以被其他模块访问的成员,我们就需要把这些公开的成员都挂载到 exports 接口对象中就可以了
- 导出多个成员(必须在对象中)
exports.a = 123
exports.b = 'hello'
exports.c = function(){
console.log('ccc')
}
exports.d = {
foo: 'bar'
}
- 导出多个成员也可以这么来写:
module.exports = {
foo: 'bar'
add: function(){}
}
- 导出单个成员(拿到的就是:函数,字符串):
module.exports = 'hello'
以下情况会覆盖:
module.exports = 'hello'
// 以这个为准,后者会覆盖前者
module.exports = function(x,y){
return x + y
}
也可以这样来导出多个成员:
module.exports = {
add: function(){
return x + y
},
str: 'hello'
}
3. 原理解析
exports 和 module.exports 的一个引用:
console.log(exports === module.exports) // => true
exports.foo = 'bar'
// 等价于
module.exports.foo = 'bar'
4. exports 和 module.exports 的区别
- 每个模块中都有一个 module 对象
- module 对象中有一个 exports 对象
- 我们可以把需要导出的成员都挂载到 module.exports 接口对象中
- 也就是:
module.exports.xxx = xxx
的方式 - 但是每次都
module.exports.xxx = xxx
很麻烦,点儿的太多了 - 所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:
exports
exports === module.exports
结果为true
- 所以对于:
module.exports.xxx = xxx
的方式完全可以:exports.xxx = xxx
- 当一个模块需要导出单个成员的时候,这个时候必须使用:
module.exports = xxx
的方式 - 不要使用
exports = xxx
不管用 - 因为每个模块最终向外
return
的是module.exports
- 而
exports
只是module.exports
的一个引用 - 所以即便你为
exports = xxx
重新赋值,也不会影响module.exports
- 但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的
module.exports = {
a: 123
}
// 重新建立 exports 和 module.exports 之间的引用关系
exports = module.exports
exports.foo = 'bar'
5. require 方法加载规则
- 优先从缓存加载
- 判断模块标识
- 核心模块
- 第三方模块
- 自定义模块
// 如果是非路径形式的模块标识
// 路径形式的模块
// ./ 当前目录,不可省略
// ../ 上一级目录,不可省略
// /xxx 几乎不用
// d:/a/foo.js 绝对路径绝对不用
// 首位的 / 在这里表示的是当前文件模块所属磁盘根路径
// .js 后缀名可以省略
// require('./foo.js')
// 核心模块的本质也是文件
// 核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
// require('fs')
// require('http')
// 第三方模块
// 凡是第三方模块都必须通过 npm 来下载
// 使用的时候就可以通过 require('包名') 的方式来进行加载才可以使用
// 不可能又任何一个第三方包和核心模块的名字是一样的
// 既不是核心模块,也不是路径形式的模块
// 先找到当前文件所处目录中的 node_modules 目录
// node_modules/art-template
// node_modules/art-template/package.json 文件
// node_modules/art-template/package.json 文件中的 main 属性
// main 属性中就记录了 art-template 的入口模块
// 然后加载使用这个第三方包
// 实际上最终加载的还是文件
// 如果 package.json 文件不存在或者 main 指定的入口模块是也没有
// 则 node 会自动找该目录下的 index.js
// 也就是说 index.js 会作为一个默认备选项
// 如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找
// 如果上一级还没有,则继续往上上一级查找
// 如果知道当前磁盘根目录还找不到,最后报错:
// can not find module xxx
// var template = require('art-template')
// 注意:我们一个项目有且只有一个 node_modules ,放在项目根目录中,
// 这样的话项目中所有的子目录中的代码都可以加载到第三方包中
// 不会出现有多个 node_modules
// 模块查找机制
// 优先从缓存加载
// 核心模块
// 路径形式的文件模块
// 第三方模块
// node_modules/art-template
// node_modules/art-template/package.json 文件
// node_modules/art-template/package.json 文件中的 main 属性
// index.js 备选项
// 进入上一级目录找 node_modules
// 按照这个规则依次往上找,直到磁盘根目录还找不到,最后报错:Can not find module xxx
// 一个项目有且只有一个 node_modules 而且是存放到项目的根目录
3. npm
- node package manager
1. npm 网站
2. npm 命令行工具
- npm 的第二层含义就是一个命令行工具,只要安装了 node 就已经安装了 npm
- npm 也有版本这个概念
- 可以通过在命令行中输入:
npm --version
- 升级 npm:
npm install --global npm
3. 常用命令
npm init
npm init -y
可以跳过向导,快速生成
npm install
- 一次性把 dependencies 选项中的依赖项全部安装
npm i
npm install 包名
- 只下载
npm i 包名
npm install --save 包名
- 下载并且保存依赖项(
package.json
文件中的 dependencies 选项) npm i -S 包名
- 下载并且保存依赖项(
npm uninstall 包名
- 只删除,如果有依赖项会依然保存
npm un 包名
npm uninstall --save 包名
- 删除的同时也会把依赖信息也去除
npm un -S 包名
npm help
- 查看使用帮助
npm 命令 --help
- 查看指定命令的使用帮助
- 例如我忘记了 uninstall 命令的简写了,这个时候,可以输入
npm uninstall --help
来查看使用帮助
4. 解决 npm 被墙问题
npm 存储包文件的服务器在国外,有时候会被墙,速度很慢,所以我们需要解决这个问题。
- http://npm.taobao.org/
- 淘宝的开发团队把 npm 在国内做了一个备份
- 安装淘宝的
cnpm
:
// 在任意目录执行都可以
// --global 表示安装到全局,而非前目录
// --global 不能省略,否则不管用
npm install --global cnpm
- 接下来你安装包的时候把之前的
npm
替换成cnpm
// 这里还是走国外的 npm 服务器,速度比较慢
npm install jquery
// 使用 cnpm 就会通过淘宝的服务器来下载 jquery
cnpm install jquery
- 如果不想安装
cnpm
又想使用淘宝的服务器来下载:
npm install jquery --registr=https://registry.npm.taobao.org
- 但是每一次手动这样加参数很麻烦,所以我们可以把这个选项加入配置文件中:
npm config set registry https://registry.npm.taobao.org
// 查看 npm 配置信息
npm config list
- 只要经过了上面命令的配置,则你以后所有的
npm install
都会默认通过淘宝的服务器来下载。
4. package.json
-
建议每一个项目都要有一个 package.json 文件(包描述文件,就像产品的说明书一样),给人踏实的感觉。
-
这个文件可以通过
npm init
的方式来自动初始化出来。
-
对于我们目前来讲,最有用的事那个
dependencies
选项,可以用来帮助我们保存第三方包的依赖信息。 -
如果你的
node_modules
删除了也不用担心,我们只需要:npm install
就会自动把package.json
中的dependencies
中所有的依赖项都下载回来- 建议每个项目的根目录下都有一个
package.json
文件 - 建议执行
npm install 包名
的时候都加上--save
这个选项,目的是用来保存依赖项信息
- 建议每个项目的根目录下都有一个
5. package-lock.json
- npm 5 以前是不会有
package-lock.json
这个文件的 - npm 5 以后才加入了这个文件
- 当安装包的时候,npm 都会生成或者更新
package-lock.json
这个文件- npm 5 以后的版本安装包不需要加
--save
参数,它会自动保存依赖信息 - 当安装包的时候,会自动创建或者是更新
package-lock.json
这个文件 package-lock.json
这个文件会保存node_modules
中所有包的信息(版本,下载地址)- 这样的话重新
npm install
的时候速度就可以提升
- 这样的话重新
- 从文件来看,有一个
lock
称之为锁- 这个
lock
是用来锁定版本的 - 如果项目依赖了
1.1.1
版本 - 如果你重新
install
其实会下载最新版本,而不是1.1.1
- 我们的目的就是希望可以锁住
1.1.1
这个版本 - 所以这个
package-lock.json
这个文件的另一个作用就是锁定版本号,防止自动升级新版
- 这个
- npm 5 以后的版本安装包不需要加
7. Express
- 原生的
http
在某些方面表现不足以应对我们的开发需求,所以我们就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码更高度统一 - 在 Node 中,有很多 Web 开发框架,我们这里以学习
express
为主 - http://www.expressjs.com.cn
1. Express 起步
1. 安装
npm install --save express
2. hello world
const express = require('express')
const app = express()
app.get('/',(req,res) => res.send('hello world'))
app.listen(3000,() => console.log('Example app listening on port 3000!'))
3. 基本路由:
路由器
-
请求方法
-
请求路径
-
请求处理函数
-
get:
// 当你以 GET 的方法请求 / 的时候,执行对应的处理函数
app.get('/',function(req,res){
res.send('hello world')
})
- post:
// 当你以 POST 的方法请求 / 的时候,指定对应的处理函数
app.post('/',function(req,res){
res.send('Got a POST request')
}
4. static-server 静态服务
// /public资源
app.use(express.static('public'))
// /static资源
app.use(express.static('files'))
// /public/xxx
app.use('/public',express.static('public'))
// /static/xxx
app.use('/static',express.static('public'))
app.use('/static',express.static(path.join(_dirname,'public')))
2. Express 安装 art-template 模版引擎
1. 安装
npm install --save art-template
npm install --save express-art-template
2. 相关配置
app.engine('art',require('express-art-template'))
3. 如何使用
app.get('/',function(req,res){
// express 默认会去项目中的 views 目录找 index.html
res.render('index.html'.{
title: 'hello world'
})
})
- 如果希望修改默认的
views
视图渲染存储目录,可以:
// 注意:第一个参数 views 千万不要写错
app.set('views',目录路径)
3. 在 Express 中获取表单 GET 请求参数
Express 内置了一个 API ,可以直接通过 req.query
来获取
req.query
4. 在 Express 获取表单 POST 请求体数据
在 Express 中没有内置获取表单 POST 请求体的 API ,这里我们需要使用一个第三方包:body-parser
- 安装:
npm install --save body-parser
- 配置:
var express = require('express')
// 0. 引包
var bodyParser = require('body-parser')
var app = express()
// 配置 body-parser
// 只要加入这个配置,则在 req 请求对象上会多出来一个属性:body
// 也就是说你就可以直接通过 req.body 来获取表单 POST 请求体数据了
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
- 使用:
app.use(function(req,res){
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
// 可以通过 req.body 来获取表单 POST 请求体数据
res.end(JSON.stringify(req.body,null,2))
})
5. 路由模块的提取
根据不同的请求方法+请求路径设置具体的请求处理函数
- 入口模块
app.js
var express = require('express')
var router = require('./router')
var app = express()
// 把路由容器挂载到 app 服务中
app.use(router)
app.listen(3000,function(){
console.log('running 3000...');
})
module.exports = app
- 路由模块
router.js
var express = require('express')
var fs = require('fs')
// 使用 Express 中的 Router 方法
// 1. 创建一个路由容器
var router = express.Router()
// 2. 把路由都挂载到 router 路由容器中
router.get('/students',function(req,res){
// readFile 的第二个参数是可选的,
// 传入 utf8 就是告诉它把读取到的文件直接按照 utf8 编码转成我们能认识的字符
// 除了这样来转换之外,也可以通过 data.toString() 的方式
fs.readFile('./db.json','utf8',function(err,data){
if(err){
return res.status(500).send('Server error.')
}
// console.log(data); // string
// 从文件中读取到的数据一定是字符串
// 所以这里一定要手动转成对象
var students = JSON.parse(data).students
students: students
})
})
})
// 3. 把 router 导出
module.exports = router
8. Mongo DB
1. 关系型数据库和非关系型数据库
表就是关系,或者说表与表之间存在关系
- 所有的关系型数据库都需要通过
sql
语言来操作 - 所有的关系型数据库在操作之前都需要设计表结构
- 而且数据表还支持约束
- 唯一的
- 主键
- 默认值
- 非空
- 非关系型数据库非常的灵活
- 有的非关系型数据库就是 key-value 对儿
- 但是 MongoDB 是长的最像关系型数据库的非关系型数据库
- 数据库 -> 数据库
- 数据表 -> 集合(数组)
- 表记录 -> (文档对象)
- MongoDB 不需要设计表结构
- 也就是说你可以任意的往里面存数据,没有结构性这一说
2. 启动和关闭数据库
- 启动:
// mongodb 默认使用执行 mongod 命令所处盘符根目录下的 /data/db 作为自己的数据存储目录
// 所以在第一次执行该命令之前先自己手动新建一个 /data/db
mongod
- 如果想要修改默认的数据存储目录,可以:
mongod --dbpath=数据存储目录路径
- 停止:
// 在开启服务的控制台,直接 Ctrl+C 即可停止
// 或者直接关闭开启服务的控制台也可以
3. 连接和退出数据库
- 连接:
// 该命令默认连接本机的 MongoDB 服务
mongo
- 退出:
// 在连接状态输入 exit 退出连接
exit
4. 基本命令
命令 | 作用 |
---|---|
show dbs | 查看显示所有数据库 |
db | 查看当前操作的数据库 |
use 数据库名称 | 切换到指定的数据(如果没有会新建) |
插入数据 |
5. 在 Node 中如何操作 MongoDB 数据
1. 使用官方的 mongodb 包来操作
2. 使用第三方 mongoose 来操作 MongoDB 数据库
- 第三方包:mongoose 基于 MongoDB 官方的 mongodb 包再一次做了封装
- 官网:http://mongoosejs.com/
- 官方指南:http://mongoosejs.com/docs/guide.html
- 官方 API 文档:http://mongoosejs.com/docs/api.html
6. MongoDB 数据库的基本概念
- 可以有多个数据库
- 一个数据库中可以有多个集合(表)
- 一个集合中可以有多个文档(表记录)
- 文档结构很灵活,没有任何限制
- MongoDB非常灵活,不需要像 MySQL 一样先创建数据库,表,设计表结构
- 在这里只需要:当你需要插入数据的时候,只需要指定往哪个数据库的哪个集合操作就可以了
- 一切都由 MongoDB 来帮你自动完成建库建表这件事儿
{
qq:{
users:[
{name: '张三', age: 15},
{name: '李四', age: 15},
{name: '王五', age: 15},
...
],
products:[
],
...
},
taobao:{
},
baidu:{
}
}
7. 设计 Scheme 发布 Model
1. 连接数据库
- 指定连接的数据库不需要存在,当你插入第一条数据之后就会自动被创建出来
mongoose.connect('mongodb://localhost/itcast')
2. 设计集合结构
- 集合结构也就是表结构
- 字段名称就是表结构中的属性名称
- 约束的目的是为了保证数据的完整性,不要有脏数据
var userSchema = new Schema({
username: {
type: String,
required: true // 必须有
},
password: {
type: String,
required: true
},
email: {
type: String
}
})
3. 将文档结构发布为模型
- mongoose.model 方法就是用来将一个架构发布为 model
- 第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称
- mongoose 会自动将大写名词的字符串生成小写复数的集合名称
- 例如这里的 User 最终会变为 users 集合名称
- 第二个参数:架构 Schema
- 返回值:模型构造函数
var User = mongoose.model('User',userSchema)
4. 获取模型构造函数
- 当我们有了模型构造函数之后,就可以使用这个构造函数对 users 集合中的数据为所欲为了(增删改查)
8. Mongodb 增删改查
1. 增加数据
var admin = new User({
username: 'admin',
password: '123456',
email: '[email protected]'
})
// 增加数据
admin.save(function(err,ret){
if(err){
console.log('保存失败');
}else{
console.log('保存成功');
console.log(ret);// 插入的数据
}
})
2. 查询数据
// 查询所有数据
User.find(function(err,ret){
if (err) {
console.log('查询失败');
}else{
console.log(ret);
}
})
// 按条件查询所有数据
User.find({
username: 'zs' // 查询条件
},function(err,ret){
if(err){
console.log('查询失败');
}else{
console.log(ret);
}
})
// 按条件查询单个目标
User.findOne({
username: 'zs',
password: '123456'
},function(err,ret){
if(err){
console.log('查询失败');
}else{
console.log(ret);
}
})
3. 删除数据
// 删除数据
User.remove({
username: 'zs'
},function(err,ret){
if(err){
console.log('删除失败');
}else{
console.log('删除成功');
console.log(ret);
}
})
4. 更新数据
// 更新数据
User.findByIdAndUpdate('abc',{
password: '123'
},function(err,ret){
if(err){
console.log('更新失败');
}else{
console.log('更新成功');
}
})
9. Node.js连接MySQL数据库
var mysql = require('mysql');
// 1. 创建连接
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'xxx',
database : 'xxx'
});
// 2. 连接数据库
connection.connect();
// 3. 执行数据操作
connection.query('SELECT 1 + 1 AS solution', function(error,results,fields){
if (error) throw error;
console.log('The solution is:', results[0].solution);
});
// 4. 关闭连接
connection.end();
10. Promise
- 无法确定顺序的代码:
var fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘读取失败’);
// 抛出异常
// 1. 阻止程序的执行
// 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
fs.readFile('./data/b.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘读取失败’);
// 抛出异常
// 1. 阻止程序的执行
// 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
fs.readFile('./data/c.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘读取失败’);
// 抛出异常
// 1. 阻止程序的执行
// 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
- 回调函数
var fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘读取失败’);
// 抛出异常
// 1. 阻止程序的执行
// 2. 把错误消息打印到控制台
throw err
}
console.log(data);
fs.readFile('./data/b.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘读取失败’);
// 抛出异常
// 1. 阻止程序的执行
// 2. 把错误消息打印到控制台
throw err
}
console.log(data);
fs.readFile('./data/c.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘读取失败’);
// 抛出异常
// 1. 阻止程序的执行
// 2. 把错误消息打印到控制台
throw err
}
console.log(data);
})
})
})
- 为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6 中新增了一个API:
Promise
1. Promise 容器概念
2. Promise 基本语法
var fs = require('fs')
// 在 EcmaScript 6 中新增了一个 API Promise
// Promise 是一个构造函数
// 创建 Promise 容器
// 1. 给别人一个承诺 I promise you
// Promise 容器一旦创建,就开始执行里面的代码
var p1 = new Promise(function(resolve,reject){
fs.readFile('./data/a.txt','utf8',function(err,data){
if(err){
// 失败了,承诺容器中的任务失败了
// console.log(err);
// 把容器的 Pending 状态变为 Rejected
// 调用 reject 就相当于调用了 then 方法的第二个参数函数
reject(err)
}else{
// 承诺容器中的任务成功了
// console.log(data);
// 把容器的 Pending 状态变为成功 Resolved
// 也就是说这里调用的 resolve 方法实际上就是 then 方法传递的那个 function
resolve(data)
}
})
})
// p1 就是那个承诺
// 当 p1 成功了 然后(then)做指定的操作
// then 方法接收的 function 就是容器中的 resolve 函数
p1
.then(function(data){
console.log(data);
},function(err){
console.log('读取文件失败了',err);
})
3. Promise API
var fs = require('fs')
// 在 EcmaScript 6 中新增了一个 API Promise
// Promise 是一个构造函数
// 创建 Promise 容器
// 1. 给别人一个承诺 I promise you
// Promise 容器一旦创建,就开始执行里面的代码
var p1 = new Promise(function(resolve,reject){
fs.readFile('./data/a.txt','utf8',function(err,data){
if(err){
// 失败了,承诺容器中的任务失败了
// console.log(err);
// 把容器的 Pending 状态变为 Rejected
// 调用 reject 就相当于调用了 then 方法的第二个参数函数
reject(err)
}else{
// 承诺容器中的任务成功了
// console.log(data);
// 把容器的 Pending 状态变为成功 Resolved
// 也就是说这里调用的 resolve 方法实际上就是 then 方法传递的那个 function
resolve(data)
}
})
})
var p2 = new Promise(function(resolve,reject){
fs.readFile('./data/b.txt','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
var p3 = new Promise(function(resolve,reject){
fs.readFile('./data/c.txt','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
// p1 就是那个承诺
// 当 p1 成功了 然后(then)做指定的操作
// then 方法接收的 function 就是容器中的 resolve 函数
p1
.then(function(data){
console.log(data);
// 当 p1 读取成功的时候
// 当前函数中 return 的结果就可以在后面的 then 中 function 接收到
// 当你 return 123 后面就接收到 123
// return ‘hello’ 后面就接收到 ‘hello’
// 没有 return 后面收到的就是 undefined
// 上面那些 return 的数据没什么用
// 真正有用的是:我们可以 return 一个 Promise 对象
// 当 return 一个 Promise 对象的时候,后续的 then 中的方法的第一个参数作为 p2 的 resolve
return p2
},function(err){
console.log('读取文件失败了',err);
})
.then(function(data){
console.log(data);
return p3
})
.then(function(data){
console.log(data);
console.log('end');
})
- Promise API 代码图示
4. 封装 Promise API
var fs = require('fs')
function pReadFile(filePath){
return = new Promise(function(resolve,reject){
fs.readFile('filePath,'utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then(function(data){
console.log(data);
return pReadFile('./data/b.txt')
})
.then(function(data){
console.log(data);
return pReadFile('./data/c.txt')
})
.then(function(data){
console.log(data);
})
11. 中间件
- http://expressjs.com/en/guide/using-middleware.html
- 中间件的本质就是一个请求处理方法,我们把用户从请求到响应的整个过程分发到多个中间件中去处理,这样做的目的是提高代码的灵活性,动态可扩展的
- 同一个请求所经过的中间件都是同一个请求对象和响应对象
1. 应用程序级别中间件
- 万能匹配(不关心任何请求路径和请求方法):
app.use(function(req,res,next){
console.log('Time:',Date.now())
next()
})
- 只要是以
'/xxx'
开头的
app.use('/a',function(req,res,next){
console.log('Time:',Date.now())
next()
})
2. 路由级别中间件
- get:
app.get('/',function(req,res){
res.send('Hello World!')
})
- post:
app.post('/',function(req,res){
res.send('Got a POST request')
})
- put:
app.put('/user',function(req,res){
res.send('Got a PUT request at /user')
})
- delete:
app.delete('/user'.function(req,res){
res.send('Got a DELETE request at /user')
})
3. 错误处理中间件
app.use(function(err,req,res,next){
console.error(err.stack)
res.status(500).send('Something broke!')
})