node.js 介绍
node.js初识
- node.js 平台是基于 Chrome V8 JavaScript 引擎构建。
- 基于 node.js 可以开发控制台程序(命令行程序、CLI程序)、桌面应用程序(GUI)(借助 node-webkit、electron 等框架实现)、Web 应用程序(网站)
node.js 有哪些特点?
- 事件驱动(当事件被触发时,执行传递过去的回调函数)
- 非阻塞 I/O 模型(当执行I/O操作时,不会阻塞线程)
- 单线程
- 拥有世界最大的开源库生态系统 —— npm。
事件循环(Event-Loop)
事件循环会监视调用栈,以及回调队列
如果调用栈中为空,这个时候,就会将回调队列中的第一个元素放到调用栈中调用
异步-非阻塞
js是单线程的 异步的功能是由webapi或者nodeapi提供的!!## 事件循环(Event-Loop)
事件循环会监视调用栈,以及回调队列
如果调用栈中为空,这个时候,就会将回调队列中的第一个元素放到调用栈中调用
异步-非阻塞
js是单线程的 异步的功能是由webapi或者nodeapi提供的!!
进程和线程
- 每一个 正在运行 的应用程序都称之为进程。
- 每一个应用程序运行都至少有一个进程
- 进程是用来给应用程序提供一个运行的环境
- 进程是操作系统为应用程序分配资源的一个单位
- 用来执行应用程序中的代码
- 在一个进程内部,可以有很多的线程
- 在一个线程内部,同时只可以干一件事
- 而且传统的开发方式大部分都是 I/O 阻塞的
- 所以需要多线程来更好的利用硬件资源
Node 中将所有的阻塞操作交给了内部实现的线程池
Node 本身主线程主要就是不断的往返调度
REPL环境
- REPL 全称: Read-Eval-Print-Loop(交互式解释器)
- R 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
- E 执行 - 执行输入的数据结构
- P 打印 - 输出结果
- L 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c、.exit或者process.exit() 按钮退出。
- 在REPL中编写程序 (类似于浏览器开发人员工具中的控制台功能)
- 直接在控制台输入
node
命令进入 REPL 环境
- 按两次 Control + C 退出REPL界面 或者 输入
.exit
退出 REPL 界面
- 按住 control 键不要放开, 然后按两下 c 键
node.js中JavaScript 文件名命名规则
- 不要用中文
- 不要包含空格
- 不要出现node关键字
- 建议以 '-' 分割单词
node.js命令
process.stdout.write
这个方法可以用来输出内容,而且不会自动换行
如果要换行可以加上\n
fs模块--文件的读写
var fs = require("fs"); // 引用fs模块
// 写
fs.writeFile("文件路径", "要写的内容", "编码格式", function(err){
if(err){
throw err;
}
console.log("写入成功")
})
// 读
fs.readFile("文件路径", function(err, data){
//data是一个Buffer 字节数组
//获取字符串,需要自己toString
})
// 读
fs.readFile("文件路径", "utf-8", function(err, data){
//data是一个字符串
})
// 创建一个目录
fs.mkdir('./test-mkdir',function(err){
if(err){
console.log("创建文件夹出错");
console.log(err)
}else{
console.log("创建成功")
}
})
path模块相关
__dirname -- 获取当前文件所在目录的完整路径(伪全局)
__filename -- 获取当前文件的完整路径 (伪全局)
path.join() -- 用来拼接路径
path.join(__dirname,"css","index.css");
http模块
直接上代码
//1. 引入http模块
var http = require("http");
// 2. 创建服务实例
var server = http.createServer();
// 3. 注册请求事件(每当有请求来的时候,就会触发该事件)
server.on("request",function(request,response){
// 设置响应头
response.writeHead(200,"OK",{
"Content-Type":"text/html;charset=utf-8"
})
//给浏览器端返回数据(要给响应体中添加内容)
response.write("");
//在所有的响应信息添加完毕之后,需要结束响应
response.end();
})
//4. 开启服务实例的监听功能
server.listen(端口号, function(){
//监听开启成功后会执行的函数
console.log("服务启动成功")
})
request对象
- url 浏览器请求的地址(包含两部分 路径?参数)
- method 浏览器发送该请求的方式 GET POST
- headers 请求头中所有的信息(对象)
- rawHeaders 请求头中所有的信息(数组)
- httpVersion 请求头中包含的协议的版本
response对象
response.setHeader("键", "值") 往请求头中新增信息
-
response.writeHead(状态码, 状态描述信息, {往请求头中新增信息的键值对})
- setHeader和writeHead都可以用来设置响应头
- writeHead是直接将内容写入响应头中,而setHeader是在end的时候才写入
- 如果同时使用setHeader和writeHead设置了响应信息,那么会合并,如果有相同的内容则以writeHead的为主
response.write(data, "编码格式", callback) 给响应体中追加内容
response.end(data) 通知服务器 所有的响应内容已经发送完毕 如果传入了data,就相当于是先调用response.write将数据追加之后再response.end结束响应 每次响应都需要调用这个end方法
response.statusCode -- 设置状态码
response.statusMessage -- 设置状态码对应的状态信息
mime (第三方模块)
在页面请求的时候,不同的资源是有不同的content-type的值的,如果通过response.url一一匹配设置太麻烦了,可以通过 mime 模块设置不同类型资源的Content-Type
npm install mime
- 在代码中直接引用
var mime = require('mime')
server.on("request",function(request,response){
if(request.url.startsWith("/static")){
response.writeHead(200,"OK",{
"Content-Type":mime.getType("文件路径")
})
}
})
获取post请求发送的数据
如果post传过来的数据时大量的话,那么data事件可能会触发多次,所以可声明一个空数组,用来存放每次触发data事件的时候传过来的一段数据,end事件是当数据全部传送完毕的时候会调用的函数,这个时候再把数组中每一次传过来的数据拼接起来,就得到了完整的数据。
server.on("request", function(req, res){
var chunkArr = []; // 用来保存post请求传过来的数据
req.on("data", function(chunk){
chunkArr.push(chunk); // 将此次数据存放起来
})
req.on("end", function(){
// 拼接全部数据
var result = Buffer.concat(chunkArr);
})
})
获取get请求发送的数据
get请求的数据是在URL地址栏里的,所以可利用 url模块 或者 querystring 模块获取
- url模块
var url = require("url");
var urlObj = url.parse(request.url,true); // 返回的是个对象,包含了url里面的信息
urlObj.query // query这个属性返回的是参数(也即是get方式的数据)对象
underscore和arttemplate --node.js模板--第三方模块
// underscore
var _ = require("underscore");
var render = _.template("模板字符串");
var resultHtml = render("数据");
response.end(resultHtml); // 发送给客户端
// arttemplate
// 直接自己读文件
var resultHtml = template("模板字符串",数据);
// 根据模板字符串生成渲染函数
var render = template.compile("模板字符串");
var resultHtml = render(数据);
node.js模块化
NOde采用的模块化结构是按照CommonJS规范
模块与文件是意义对应关系,即加载一个模块,实际上就是加载该模块对应的模块文件
-
模块特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被
缓存
了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。 - 模块加载的顺序,按照其在代码中出现的顺序。
-
模块分类
文件模块 -- 就是我们自己写的功能模块文件
核心模块 -- Node 平台自带的一套基本的功能模块,也有人称之为 Node 平台的 API
第三方模块 -- 社区或第三方个人开发好的功能模块,可以直接拿回来用
1. 定义模块
将代码写在一个js文件中,这个js文件就是一个模块了
module.exports是一个对象,这个对象会在模块被创建出来的时候,同时被创建
// module.exports.成员 = 内容;
var say = function(){};
module.exports.say = say;
// 也可以直接给module.exports赋值
module.exports = {
say : say
}
// 注意: 虽然exports和module.exports指向的是同一个对象的的引用地址,但是模块暴露给外接的属性或方法,只能直接赋值给module.exports,因为模块最终的返回值是 module.exports
2. 引用模块
-
核心模块
- require("核心模块名称")
-
文件模块
require("文件的路径,必须以/ ./ ../开头")
文件路径中的.js路径可以省略,如果省略 .js 则node会查找 以该文件为名字 以 .js .node .json 为后缀的文件来加载,如果没有,就报错了
-
目录模块
require("目录路径,必须以/ ./ ../开头")
当以目录路径为参数的时候node.js会查找该目录下的 package.json 文件,会加载其 main 属性所对应的文件
如果没有 package.json 或者package.json中没有main属性 则去加载目录中 index.js index.node
-
node_modules中的模块
require("模块名称");
(这么写是不带./ / ../)
如果这么写,先回去核心模块中查找,如果找不到,则去当前目录的父路径中的node_modules文件夹中查找对应的模块,如果还是找不到,就会一直沿着父路径往上找,直到找到硬盘根目录
如何查看要查找的路径(module.paths)
3. 删除模块缓存
let absolutepath = path.join(__dirname, `../data/test.json`);
delete require.cache[absolutepath];
require(absolutepath);
express
express是什么
express是一个简单高效用来开发web应用的框架(基于node.js)
怎么用它
- 安装
- npm install express -S
- 引用并使用
//1. 引入
var express = require("express");
//2. 创建实例
var app = express();
//4. 注册路由
app.use(express.static("static"))
//3. 启动服务监听
app.listen(8888,function(){
console.log("http://localhost:8888")
})
express.static(root,[options])
root 参数指的是静态资源文件所在的根目录,写绝对路径。
options 对象是可选的(http://www.expressjs.com.cn/4x/api.html)
express 中的中间件
中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量。
-
常规中间件(应用级中间件)的回调函数,一般有3个参数
- req, res, next
- 其中next()是一个函数,通过这个函数把执行流程交给下一个中间件
-
可以挂载中间件的部分方法
- app.use()
- app.get()、app.post()、app.put()、app.delete()、...等各种请求方法
- 注意:在挂载中间件时 next() 方法的重要性,不调用该方法无法执行到下一个中间件
-
中间件的应用场景
- 利用中间件实现记录日志功能
- 利用中间件模拟body-parser功能
路由注册
- app.method
// 只接受指定方式的请求
app.get("/index",function(req,res){})
app.post()
- app.use
// 可以接受任意方式的请求
// 请求路径pathname,必须以路由路径开头
app.use("/index",function(req,res){})
// index.html index/s/a 都可以
- app.all
// 可以接受任意方式的请求
// 请求路径pathname必须和路由路径完全一样
app.all("/index",function(req,res){})
// index/s/a 不可以
res对象新增功能
-
res.send()
- send可以将对象 字符串 数组 数字 等等数据返回给浏览器
- 一次请求中只能用一次
- 他里面自动给响应报文头中添加了一些内容 Content-type
-
res.sendFile()
- 可以用来向浏览器发送文件内容
res.json
res.jsonp
res.status(状态码).end(""); -- 设置状态码
res.redirect('http://google.com'); -- 重定向
req对象获取GET方式或POST方式的请求数据
req.query 直接就可以获取到get请求的参数对象
-
req.body 可以获得POST提交方式的请求数据内容,但是需要配置
- 如果form表单中的enctype属性未设置或者设置为application/x-www-form-urlencoded,那么req.body就可以通过body-parser解析之后获取到请求数据
- 但是如果设置为 multipart/form-data这样的格式 ,那么req.body是拿不到内容的!!
-
req.body的配置
- 下载包 npm install body-parser -S
- 引包 var parser = require("body-parser");
- 将其挂载到app上 app.use(parser.urlencoded({extended: false}));
- 注意: extend属性如果设置为true, 则express最终将数据转换成对象的时候使用的就是qs模块,如果是false则使用的是querystring模块!
- 注意: 挂载use这局代码一定写在app.use(router)之上,先让parser处理POST请求的数据,然后再让路由去处理相关数据的逻辑代码
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc
var app = require('express')();
var bodyParser = require('body-parser');
var multer = require('multer');
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use(multer()); // for parsing multipart/form-data
app.post('/', function (req, res) {
console.log(req.body);
res.json(req.body);
})
路由对象
// router.js文件中
var express = require("express")
var router = express.Router();
router.get()
router.use()
// app.js文件中
var app = express();
app.use(router);
nodemon 自动刷新应用
1、全局下载 nodemon
npm install -g nodemon
2、 修改 package.json的 scripts
{
"name": "myapp",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "nodemon ./bin/www" // here here
},
"dependencies": {
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "~4.16.0",
"http-errors": "~1.6.2",
"morgan": "~1.9.0",
"pug": "2.0.0-beta11"
}
}
模板引擎(集成在express中如何使用)
var express = require("express");
var app = express();
// 1. 指定的为指定的后缀的模板文件,指定相应的模板引擎
app.engine("html", require("express-art-template"));
//2. 将模板的后缀通过set方法进行设置
app.set('view engine', 'html');
//3. 设置模板文件存放的目录
app.set("views", "./views")
app.get("/index", function(req, res){
//res.render("不带后缀的模板文件的名称(这个文件会去设置好的模板文件的存放目录去查找)", {数据}, function(err, html){
//err是异常信息
//html 模板最终的渲染结果,最终生成的html代码
// })
res.render("index", {list: [{name: "呵呵"}, {name: "呵呵"}, {name: "哈哈"}]}, function(err, html){
res.send(html);
})
})
弹框下载
- node.js原生实现
res.setHeader("Content-Type", "application/octet-stream")
res.setHeader("Content-Disposition", "attachment; filename=xxx.txt")
//要返回的文件内容,需要自己手动进行读取,通过res.write获取res.end返回给浏览器
fs.readFile(path.join(__dirname, "xxx.js"),"utf-8", function(err, data){
res.end(data);
})
- express实现
res.download("文件路径");
使用node.js发送请求
-
流程介绍
- 引入http模块
- 给http注册request事件
- 设置请求参数
- 获取响应信息(和用node.js原生代码,拿POST方式提交数据一样)
- 注册 发生错误的事件
- 结束请求
代码示例
var http = require("http");
var req = http.request({
host:"www.qiushibaike.com",
hostname:"www.qiushibaike.com",
method:"GET",
port:443,
path:"/",
headers:{
"Connection":" keep-alive",
"Pragma":" no-cache",
"Cache-Control":" no-cache",
// 必须要写!!
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
//注意:gzip如果在请求头中出现了,那么服务器端在响应请求的时候,会将所有的数据进行压缩,以提高传输效率
//如果是浏览器在请求,那么浏览器拿到压缩后的数据之后,会自行解压,然后进行展示
//nodejs中请求到数据之后,并不能自行解压,所以我们不在请求头加这个内容!
}
},function(response){
var buffer = []; // 用来存放返回信息
response.on("data",function(chunk){
buffer.push(chunk);
});
response.on("end",function(){
buffer = Buffer.concat(buffer);
buffer.toString("utf-8")
})
})
req.on("error", function(err){
throw err;
})
// end事件不要忘记注册!
req.end();
cheerio 处理服务器返回来的HTML代码
-
咋弄
- 下载包 npm install cheerio -S
- 引包 var cheerio = require("cheerio")
- 加载返回回来HTML数据 var $ = cheerio.load("html代码")
- 然后就和jQuery一样来操作里面的元素就阔以了!
代码示例
var cheerio = require("cheerio");
// 通过http,拿到的HTML格式的数据经过buffer拼接,转码,发送给cheerio
var $ = cheerio.load(Buffer.concat(buffer).toString("utf-8"));
// 用来存放自己想要的东西
var qiubaArr = [];
$(".article").each(function(index, ele){
// 拿到所有author
var author = $(ele).find(".author h2").text()
var content = $(ele).find(".content>span").text();
// 把所有数据存起来,然后
qiubaArr.push({
author,
content
})
})
CRUD
使用 MongoDB 官方提供的 mongodb 驱动包操作 MongoDB 数据库
安装:
npm install mongodb --save
CRUD:
参考文档:
- https://www.npmjs.com/package/mongodb#connecting-to-mongodb
- http://mongodb.github.io/node-mongodb-native/2.2/
var mongodb = require('mongodb')
// 连接路径URL
var url = 'mongodb://localhost:27017/itcast'
var MongoClient = mongodb.MongoClient
// ================== 插入数据 ==================
// 1. 连接数据库(打开冰箱门)
// MongoClient.connect(url, function (err, db) {
// if (err) {
// throw new Error('连接失败')
// }
// // 2. 把大象放到冰箱
// db.collection('heros').insert({ name: '张飞', gender: '男', age: 23 }, function (err, result) {
// if (err) {
// throw new Error('插入数据失败')
// }
// console.log(result)
// // 3. 关上冰箱门
// db.close()
// })
// })
// ================== /插入数据 ==================
// ================== 查询数据 ==================
// MongoClient.connect(url, function (err, db) {
// if (err) {
// throw new Error('连接失败')
// }
// // 查询所有
// // db.collection('heros').find({}).toArray(function (err, docs) {
// // if (err) {
// // throw new Error('查询数据失败')
// // }
// // console.log(docs)
// // })
// // 按条件查询
// db.collection('heros').find({name: '张飞'}).toArray(function (err, docs) {
// if (err) {
// throw new Error('查询数据失败')
// }
// console.log(docs)
// })
// })
// ================== /查询数据 ==================
// ================== 更新数据 ==================
// MongoClient.connect(url, function (err, db) {
// if (err) {
// throw new Error('连接失败')
// }
// db.collection('heros').updateOne({name: '张飞'}, {
// $set: {
// age: 20
// }
// }, function (err, result) {
// if (err) {
// throw new Error('更新失败')
// }
// console.log(result)
// })
// })
// ================== /更新数据 ==================
// ================== 删除数据 ==================
// MongoClient.connect(url, function (err, db) {
// if (err) {
// throw new Error('连接失败')
// }
// db.collection('heros').deleteOne({name: '张飞'}, function (err, result) {
// if (err) {
// throw new Error('删除失败')
// }
// console.log(result)
// })
// })
// ================== /删除数据 ==================
在node做本地服务器的项目中,做到文件下载
- 流的方式
router.post('/:cubeId/download', function(req, res, next) {
let fileName = 'file';
// console.log(res)
// console.log(req.body);
// console.log(res.body.params);
let currFilePath = path.join(__dirname,`../data/file.txt`);
let f = fs.createReadStream(currFilePath);
res.writeHead(200, {
'Content-Type': 'application/force-download',
'Content-Disposition': `attachment; filename=file.txt`
});
f.pipe(res);
})