中文文档: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"));//拼接路径 nodejsseddirindex.js 并且我们如果多写了/ 他会自动修复 然后拼接 console.log(normalize("/nodejs//seddir/index.js"));// 规范化路径 我们故意多加了一条/ 然后这个方法修复了 odejsseddirindex.js console.log(resolve("./pathTest.js"));//返回绝对路径 D: ode教程demo2-6pathTest.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/testindex.js console.log(sep);;//返回当前系统 路径分隔符 记住 他不是方法,是path上的属性 mac下应该是/ console.log(win32.sep);//window系统下的 路径分隔符 // __filename :当前文件的 绝对路径 __dirname:当前文件的绝对目录 这两个api是node自带 不用专门引入 console.log(__filename);//D: ode教程demo2-6pathTest.js 他和resolve有一定的区别 当执行文件不同时 resolve 的路径值得注意 它是相对于执行文件的绝对路径将文件名拼接到后面的 而__filename不会受执行文件的影响 console.log(__dirname);// D: ode教程demo2-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; }