node.js小滴课程笔记
中文文档:http://nodejs.cn/
nodejs是javascript的运行环境
node.js遵守common.js规范
module.exports 用来导出 可以导出对象也可以导出变量
每一个模块的作用域是独立的
初始化node项目 npm init -y
在node下那个木中定义全局变量用 global.变量=值;
在其它模块可以直接使用 变量
require用来导入模块
Buffer缓冲器,用来处理二进制
常用api:
Buffer.alloc(10)创建一个长度为10的buffer
Buffer.from([1,2,3]) 将数组[1,2,3]转换成Buffer
Buffer.byteLength(Buffer.from([1,2,3]))buffer的长度
Buffer.isBuffer(Buffer.from([1,2,3]))是否是buffer
Buffer.concat([buf1,buf2])合并buffer 第二个参数可以指定长度 长的截取 短的填充
npm install chokidar --save-dev
使用:
const chokidar = require("chokidar");
chokidar.watch("./").on("all",(event,path) => {//all代表 监听该目录下所有文件
console.log(event+" --- "+path)
})
上面的监听发现,把当前文件夹下的node_modules文件都监听了,我们应该如何忽略呢?
chokidar.watch("./",{//ignored 代表忽略的文件夹路径
ignored:"./node_modules"
}).on("all",(event,path) => {//all代表 监听该目录下所有文件
console.log(event+" --- "+path)
})
文件流:
主要有:读取流和写入流,当然还有其他的分类
下面来介绍一下读取流
const fs = require("fs");
let rs = fs.createReadStream('./streamTest.js',{
highWaterMark:100,//这里指定每次获取100b的文件大小
});//创建一个读取流 (读取自身)
let count = 1;//这里做一个计数
rs.on("data",chunk => {//读取每一块数据时的监听事件 默认每次读取64*1024b 也就是64kb的数据 流主要是通过buffer的形式传输的
console.log(chunk.toString())
console.log(count++);//每次读取完一段后 count计数自增1
})
rs.on("end",()=>{//监听读取结束事件
console.log("读取完成")
})
下面来写一个 写入流的 例子:
let ws = fs.createWriteStream("./a.txt");//创建一个写入流
let num = 1;//创建一个计数
let timer = setInterval(() => {
if(num <= 10){
ws.write(num + "");//将计数 写入 这里参数只能接受 字符串或者 buffer对象 所以转化成了字符串
num++;//计数 自增1
}else{
ws.end("写入完成");//执行结束方法 结束方法也可以写入内容
clearInterval(timer);//清除计时器
}
}, 200);
ws.on("finish",()=>{//监听写入完成的事件
console.log("写入完成")
})
let rs = fs.createReadStream("./streamTest.js");//创建一个读取流 读取当前文件
let ws = fs.createWriteStream("./a.txt");//创建一个写入流 写入到当前目录下的a.txt下
ws.on("finish",()=>{//这里加一个 写入流的监听事件
console.log("写入完成")
})
// 利用管道 将读取流和写入流 连接到一起
rs.pipe(ws);
nodejs的path模块:
const {basename,dirname,extname,join,normalize,resolve,format,parse,sep,win32} = require("path");
console.log(basename("/nodejs/2-6/index.js"));//返回最后一部分的内容 index.js
console.log(basename("/nodejs/2-6/index.js",".js"));//返回最后一部分的内容 并且将后缀名去掉 index
console.log(dirname("/nodejs/2-6/index.js"));//返回最后一部分内容所在的目录 /nodejs/2-6
console.log(extname("index.js"));//返回文件后缀 .js
console.log(join("nodejs","//seddir","index.js"));//拼接路径 nodejs\seddir\index.js 并且我们如果多写了/ 他会自动修复 然后拼接
console.log(normalize("/nodejs//seddir/index.js"));// 规范化路径 我们故意多加了一条/ 然后这个方法修复了 \nodejs\seddir\index.js
console.log(resolve("./pathTest.js"));//返回绝对路径 D:\node教程\demo\2-6\pathTest.js
let pathObj = parse("/nodejs/test/index.js");//解析路径成路径对象 parse和format是可以相互转化的
console.log(pathObj);//{root: '/',dir: 'nodejs/test',base: 'index.js',ext: '.js',name: 'index'}
console.log(format(pathObj));// /nodejs/test\index.js
console.log(sep);;//返回当前系统 路径分隔符 \ 记住 他不是方法,是path上的属性 mac下应该是/
console.log(win32.sep);//window系统下的 路径分隔符 \
// __filename :当前文件的 绝对路径 __dirname:当前文件的绝对目录 这两个api是node自带 不用专门引入
console.log(__filename);//D:\node教程\demo\2-6\pathTest.js 他和resolve有一定的区别 当执行文件不同时 resolve 的路径值得注意 它是相对于执行文件的绝对路径将文件名拼接到后面的 而__filename不会受执行文件的影响
console.log(__dirname);// D:\node教程\demo\2-6>
events事件触发器:大多数api都是基于事件触发器来触发的
下面来 举个小例子:
const EventEmitter = require("events");
class MyEmitter extends EventEmitter{};//用es6的方式创建一个(事件触发器的)类 此类是通过继承EventEmitter类创建的
let myEmitter = new MyEmitter();//这里创建一个(事件触发器的)实例
myEmitter.on("hi",()=>{//在事件触发器实例上绑定一个事件 hi
console.log("hi事件触发了")
})
myEmitter.emit("hi");//通过这个事件触发器实例 的emit方法 触发 hi 事件
那么如何传参呢? 回调函数和 emit参数中 第一个以后的参数 一一对应
myEmitter.on("hi",(a,b)=>{
console.log("hi事件触发了:"+ (a + b))
})
myEmitter.emit("hi",1,8);
用once绑定的事件只能触发一次,多次触发 无效
myEmitter.once("hello",()=>{//once绑定的事件 只能触发一次 多次触发不生效
console.log("hello事件触发了")
})
myEmitter.emit("hello");
function fn1(a,b){//方法1 console.log("hi事件触发了 事件带参:"+ (a + b)) } function fn2(){//方法2 console.log("hi事件触发了 事件不带参:") } myEmitter.on("hi",fn1);//给hi事件绑定上 fn1 方法 myEmitter.on("hi",fn2);//再给hi事件绑定上 fn2 方法 myEmitter.emit("hi",1,8); // 将fn2 冲hi事件中移除 myEmitter.removeListener("hi",fn2); myEmitter.emit("hi",1,8);//再执行一次 此次只执行fn1回调了
可以用监听器实例上的removeAllListeners方法,将指定事件上的方法全部移除:
myEmitter.removeAllListeners("hi");
myEmitter.emit("hi",1,8);//没执行任何 回调 因为都移除了
核心模块util:
util的callbackify方法可以使async函数变成有回调风格的函数:
const util = require("util");
async function hello(){
return "hello world";
}
let helloCb = util.callbackify(hello);//将hello方法 转换成有回调风格的 方法
helloCb(((err,data) => {
if(err) throw err;
console.log(data);
}))
const util = require("util");
const fs = require("fs");
fs.stat("./utilTest.js",(err,data)=>{//fs.stat用来查看 文件信息 是一个回调风格的函数
if(err) throw err;
console.log(data)
})
let stat = util.promisify(fs.stat);//将回调风格的函数 转化成 promise风格的函数
stat("./utilTest.js").then(data=>{
console.log(data)
}).catch(err=>{
console.log("出错了");
console.log(err)
})
当然也可以用async的方式来写
const util = require("util");
const fs = require("fs");
let stat = util.promisify(fs.stat);//将回调风格的函数 转化成 promise风格的函数
async function statFn(){
try{
let statInfo = await stat("./utilTest.js");
console.log(statInfo)
}catch(e){
console.log(e);
}
}
statFn()
util.types.isDate(new Date);//判断是否为日期类型 true util.types.isDate("2020/06/22");//false
当然还可以判断许多其它的,可以查文档
const http = require("http")
const server = http.createServer((req,res) => {
res.writeHead(200,{'content-type':'text/html'});
res.end('<h1>hello world</h1>');
})
server.listen(3000,()=>{
console.log("监听了3000端口")
})
url.parse方法:用来解析路径
下面举个小例子:
const url = require("url")
console.log(url.parse("https://api.xdclass.net/pub/api/v1/web/product/find_list_by_type?type=2"));
Url { protocol: 'https:', slashes: true, auth: null, host: 'api.xdclass.net', port: null, hostname: 'api.xdclass.net', hash: null, search: '?type=2', query: 'type=2', pathname: '/pub/api/v1/web/product/find_list_by_type', path: '/pub/api/v1/web/product/find_list_by_type?type=2', href: 'https://api.xdclass.net/pub/api/v1/web/product/find_list_by_type?type=2' }
//加上第二个参数 默认是false 设为true,会解析query参数 console.log(url.parse("https://api.xdclass.net/pub/api/v1/web/product/find_list_by_type?type=2",true));
Url { protocol: 'https:', slashes: true, auth: null, host: 'api.xdclass.net', port: null, hostname: 'api.xdclass.net', hash: null, search: '?type=2', query: [Object: null prototype] { type: '2' }, pathname: '/pub/api/v1/web/product/find_list_by_type', path: '/pub/api/v1/web/product/find_list_by_type?type=2', href: 'https://api.xdclass.net/pub/api/v1/web/product/find_list_by_type?type=2' }
如何处理get请求:
const url = require("url")
const http = require("http")
const server = http.createServer((req,res) => {
// 创建一个对象,将请求的url解析后存起来
let urlObj = url.parse(req.url,true);
// 我们将query参数 返回到页面上
res.end(JSON.stringify(urlObj.query))
});
server.listen(3000,() => {
console.log("监听3000端口");
})
如何处理post请求:
const url = require("url")
const http = require("http")
const server = http.createServer((req,res) => {
// 监听post请求,因为post请求 是以流的形式 传递参数,所以 需要不断的监听
let postData = '';
// 监听post请求的 数据 要用监听流的形式监听
req.on("data",chunk => {
//这里chunk本来是buffer对象 由于用了字符串拼接 所以 隐式的将buffer对象转换成了字符串类型
postData += chunk;
})
// 监听传输结束事件
req.on("end",()=>{
console.log(postData);
})
// 返回
res.end(JSON.stringify({
data:"请求成功",
code:0,
}))
});
server.listen(3000,() => {
console.log("监听3000端口");
})
整合get/post请求:
const url = require("url")
const http = require("http")
const server = http.createServer((req,res) => {
if(req.method === 'GET'){
let urlObj = url.parse(req.url,true);
res.end(JSON.stringify(urlObj.query))
}else if(req.method === 'POST'){
let postData = '';
req.on("data",chunk => {
postData += chunk;
})
req.on("end",()=>{
console.log(postData);
})
res.end(JSON.stringify({
data:"请求成功",
code:0,
}))
}
});
server.listen(3000,() => {
console.log("监听3000端口");
})
补充一下,post请求可以用postman发送,百度一下可以下载
使用nodemon自动重启工具:
nodemon可以让node项目自动重启,我们之前改点代码,需要手动重启项目才能生效,nodemon工具可以在我们改动代码后自动重启。
推荐全局安装:npm install -g nodemon
比如说要启动 server.js
之前的启动是:node server
那么用nodemon启动就是:nodemon server
定义命令:
先初始化一下项目:npm init -y
之后出现了package.json文件
找到script字段,自定义一个命令
"scripts": { "start":"nodemon reqTest.js" },
然后我们就可以 通过 npm run start来启动项目了
路由初始化以及接口开发:
我们先写个最原始的接口,通过请求的路径和请求方法来判定具体是哪一个接口
server.js
const http = require("http")
const url = require("url")
const server = http.createServer((req,res)=>{
// 将返回类型变成json格式 编码为utf-8 防止中文乱码
res.writeHead(200,{'content-type':'application/json;charset=UTF-8'});
// 拿到路径参数
let urlObj = url.parse(req.url,true);
if(urlObj.pathname === '/api/aa' && req.method === 'GET'){
//若路径名是/api/aa 并且是get请求 则返回query参数
res.end(JSON.stringify(urlObj.query))
}else{
res.end('404 not found')
}
})
server.listen(3000,()=>{
console.log("服务启动 3000端口")
})
可以看到以上是写了一个接口,如果写很多个接口,都放到server.js文件中,那么这个文件就会显的臃肿,那么接下来我们就可以整合一下接口。
创建一个router文件夹,创建index.js
const url = require("url")
function handleRequest(req,res){
// 拿到路径参数
let urlObj = url.parse(req.url,true);
if(urlObj.pathname === '/api/aa' && req.method === 'GET'){
//若路径名是/api/aa 并且是get请求 则返回query参数
return{
msg:"获取成功",
code:0,
data:urlObj.query
}
}
if(urlObj.pathname === '/api/post' && req.method === 'POST'){
//若路径名是/api/post 并且是post请求
return{
msg:"获取成功",
code:0,
}
}
}
module.exports = handleRequest;
server.js对应改为:
const http = require("http")
const routerModal = require("./router/index");
const server = http.createServer((req,res)=>{
// 将返回类型变成json格式 编码为utf-8 防止中文乱码
res.writeHead(200,{'content-type':'application/json;charset=UTF-8'});
let resultData = routerModal(req,res);
if(resultData){
//如果resultData有数据 就把数据返回出去
res.end(JSON.stringify(resultData));
}else{
res.writeHead(404,{'content-type':'text/html'})
res.end('404 not found')
}
})
server.listen(3000,()=>{
console.log("服务启动 3000端口")
})
实战用户列表增删改查:(不过现在还没连接数据库,数据仍然是假的)
很显然,我们需要些4个接口,用户列表获取、新增、删除、更新
server.js:比之前多了个获取post数据的方法,自定义给req对象上加了个body属性,将post传来的数据赋值给了它
const http = require("http")
const routerModal = require("./router/index");
const getPostData = function(req){
//封装一个获取post的数据的方法
return new Promise((resolve,reject) => {
if(req.method !== 'POST'){
//如果不是post请求 就返回个空对象
resolve({});
return;
}
let postData = '';
req.on("data",chunk => {
postData += chunk;
})
req.on("end",()=>{
resolve(JSON.stringify(postData))
})
})
}
const server = http.createServer((req,res)=>{
// 将返回类型变成json格式 编码为utf-8 防止中文乱码
res.writeHead(200,{'content-type':'application/json;charset=UTF-8'});
getPostData(req).then(data=>{//这里的data就是post请求传来的数据
req.body = data;//自定义给req对象加一个body属性,将post传过来的数据赋值给body
let resultData = routerModal(req,res);
if(resultData){
//如果resultData有数据 就把数据返回出去
res.end(JSON.stringify(resultData));
}else{
res.writeHead(404,{'content-type':'text/html'})
res.end('404 not found')
}
})
})
server.listen(3000,()=>{
console.log("服务启动 3000端口")
})
router/index.js
const url = require("url")
const {getUserList,addUser,deleteUser,updateUser} = require("../controller/user")
function handleRequest(req,res){
// 拿到路径参数
let urlObj = url.parse(req.url,true);
if(urlObj.pathname === '/api/getUserList' && req.method === 'GET'){
//获取用户列表接口
let resultData = getUserList();
return resultData;
}
if(urlObj.pathname === '/api/addUser' && req.method === 'POST'){
//用户新增接口
let resultData = addUser(req.body);
return resultData;
}
if(urlObj.pathname === '/api/deleteUser' && req.method === 'POST'){
//删除用户接口
let resultData = deleteUser(urlObj.query.id);
return resultData;
}
if(urlObj.pathname === '/api/updateUser' && req.method === 'POST'){
//删除用户接口
let resultData = updateUser(urlObj.query.id,req.body);
return resultData;
}
}
module.exports = handleRequest;
controller/user.js:这一个文件专门是用来处理数据的,user.js是专门用来处理用户数据的,分好模块。现在是模拟数据,将来会从操作数据库
module.exports = { getUserList(){ //获取用户列表 return [ {id:1,name:"tom",city:"北京"}, {id:2,name:"xiaoming",city:"广州"}, {id:3,name:"xiaohua",city:"上海"}, ] }, addUser(userObj){ // 新增用户 console.log(userObj); return { code:0, msg:"新增成功", data:null } }, deleteUser(id){ //删除用户 console.log(id) return { code:0, msg:"删除成功", data:null } }, updateUser(id,userObj){ //更新用户信息 console.log(id,userObj); return { code:0, msg:"更新成功", data:null } } }
解决接口跨域问题:
我们在项目中写个html页面,用jquey去请求我们之前写的接口:
这里给大家推荐个vscode插件:live server 下载安装后,打开当前html文件,点击右下角GO live 就可以将当前html放到一个服务器上面运行,很方便‘
我们在html发送了一条请求;
$.ajax({ url:"http://127.0.0.1:3000/api/getUserList", success:function(res){ console.log(res) } })
然后报了跨域。
如何解决跨域呢?
我们只需要在服务器的响应对象上加上:这样所有不同的域就可以跨域请求了
// 设置跨域处理 res.setHeader("Access-Control-Allow-Origin","*");
正常在服务器上是不会设置 * 号的,这样就相当于把接口共享了,一般会设置指定的可访问的地址
// 设置跨域处理 res.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500");
结合数据库改造用户列表接口 (增):
数据库配置:
config/db_config.js:
let dbOption; dbOption = { connectionLimit:10,//同时创建连接的最大连接数 host:"localhost",//连接地址 user:"root",//用户 password:"123456",//密码 port:'3306',//端口 database:"user_test",//要连接的数据库 } module.exports = dbOption;
数据库连接以及query方法封装:
db/conn.js:
const mysql = require("mysql")
const dbOption = require("../config/db_config")
// 创建连接池
const pool = mysql.createPool(dbOption);
// 封装一个 sql语句执行方法 因为这个执行方法有可能在很多文件重复调用 所以封装一下
// 接受sql语句,以及参数
function query(sql,params){
return new Promise((resolve,reject) => {
pool.getConnection((err,conn) => {
if(err){
reject(err);
return;
}
// 执行sql语句
conn.query(sql,params,(err,result) => {
// 不管是否 报错 首先将连接 释放掉
conn.release()
if(err){
reject(err);
return;
}
resolve(result);
})
})
})
}
module.exports = query
server.js:响应有点更改
const http = require("http")
const routerModal = require("./router/index");
const getPostData = function(req){
//封装一个获取post的数据的方法
return new Promise((resolve,reject) => {
if(req.method !== 'POST'){
//如果不是post请求 就返回个空对象
resolve({});
return;
}
let postData = '';
req.on("data",chunk => {
postData += chunk;
})
req.on("end",()=>{
resolve(JSON.parse(postData))
})
})
}
const server = http.createServer((req,res)=>{
// 设置跨域处理
res.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500");
// 将返回类型变成json格式 编码为utf-8 防止中文乱码
res.writeHead(200,{'content-type':'application/json;charset=UTF-8'});
getPostData(req).then(data=>{//这里的data就是post请求传来的数据
req.body = data;//自定义给req对象加一个body属性,将post传过来的数据赋值给body
let result = routerModal(req,res);
if(result){
result.then(resultData => {
//把数据返回出去
res.end(JSON.stringify(resultData));
})
}else{
res.writeHead(404,{'content-type':'text/html'})
res.end('404 not found')
}
})
})
server.listen(3000,()=>{
console.log("服务启动 3000端口")
})
router/index.js:没有发生改变
const url = require("url")
const {getUserList,addUser,deleteUser,updateUser} = require("../controller/user")
function handleRequest(req,res){
// 拿到路径参数
let urlObj = url.parse(req.url,true);
if(urlObj.pathname === '/api/getUserList' && req.method === 'GET'){
//获取用户列表接口
let resultData = getUserList();
return resultData;
}
if(urlObj.pathname === '/api/addUser' && req.method === 'POST'){
//用户新增接口
let resultData = addUser(req.body);
console.log(resultData,"index.js")
return resultData;
}
if(urlObj.pathname === '/api/deleteUser' && req.method === 'POST'){
//删除用户接口
let resultData = deleteUser(urlObj.query.id);
return resultData;
}
if(urlObj.pathname === '/api/updateUser' && req.method === 'POST'){
//删除用户接口
let resultData = updateUser(urlObj.query.id,req.body);
return resultData;
}
}
module.exports = handleRequest;
controller/user.js:的addUser方法发生了改变
const query = require("../db/conn");
module.exports = {
getUserList(){
//获取用户列表
return [
{id:1,name:"tom",city:"北京"},
{id:2,name:"xiaoming",city:"广州"},
{id:3,name:"xiaohua",city:"上海"},
]
},
async addUser(userObj){
// 新增用户
console.log(userObj);
let {name,city,sex} = userObj;
let sql = 'insert into user (name,city,sex) values (?,?,?)';
let resultData = await query(sql,[name,city,sex]);
console.log(resultData,"user.js");
if(resultData){
return {msg:"新增成功"}
}else{
return {msg:"新增失败"}
}
},
deleteUser(id){
//删除用户
console.log(id)
return {
code:0,
msg:"删除成功",
data:null
}
},
updateUser(id,userObj){
//更新用户信息
console.log(id,userObj);
return {
code:0,
msg:"更新成功",
data:null
}
}
}
目录结构:

结合数据库改造用户列表接口:(查)
user.js:
async getUserList(urlParams){ let {name,city} = urlParams; let sql = 'select * from user where 1=1'; if(name){ sql += ' and name = ?'; } if(city){ sql += ' and city = ?';//注意前面流一个空格 要不查询数据库就会 报错 说太靠近了 } console.log(sql); let resultData = await query(sql,[name,city]); //获取用户列表 return resultData; },
urlParams参数记得在index.js中传过来
// 拿到路径参数 let urlObj = url.parse(req.url,true); if(urlObj.pathname === '/api/getUserList' && req.method === 'GET'){ //获取用户列表接口 let resultData = getUserList(urlObj.query); return resultData; }

浙公网安备 33010602011771号