node学习之旅
1. 创建服务并进行简单的前端页面配置
// 引入http模块
const http = require('http');
const url = require('url')
// http.createServer创建一个web服务
// request, 获取浏览器url传递过来的信息
// response,给浏览器的响应信息
http.createServer(function (request, response) {
// 设置相应头,
// 状态码200,
// 编码为:text/html
// console.log(request.url)
response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
// 设置html页面的编码格式,解决乱码问题
response.write('<head><meta charset="UTF-8"></head>')
response.write("hello");
response.write('<h2>hello你好</h2>');
// 表示给页面上输出一句话,并结束响应
// response.end("hello")
response.end();//结束响应
}).listen(8081);
// 监听8081端口
console.log('Server running at http://127.0.0.1:8081/');
2.获取页面的get传值
const http = require('http');
const url = require('url')
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
// 设置html页面的编码格式,解决乱码问题
response.write('<head><meta charset="UTF-8"></head>')
response.write("hello");
// 获取页面的get传值
// 参数一:要解析的地址
// 参数二:是否将值转换成object
// request.url = http://127.0.0.1:8081/?name=zhangsan&age=12
// console.log(request)
if (request.url != "/favicon.ico"){
const temp = url.parse(request.url, true).query
console.log(url.parse(request.url, true))
console.log(temp)
console.log(`姓名:${temp.name}--年龄:${temp.age}`)
}
response.end();//结束响应
}).listen(8081);
// 监听8081端口
console.log('Server running at http://127.0.0.1:8081/');
3.自启动工具supervisor
supervisor会不停的监听应用下的所有文件,发现文件被修改就重新载入程序文件,这样就实现了修改了程序文件后马上就能看到变更后的结果
- 安装supervisor
cnpm install -g supervisor
- 使用supervisor代替node启动应用
supervisor 要启动的文件
// supervisor HttpUrl.js
- 修改代码并保存,刷新页面即可查看修改后的页面结果
4.模块、自定义模块
在nodejs中模块分为核心模块(http、url模块)以及文件模块(自定义模块)
自定义模块:将公共的功能抽离成单独的js文件作为一个模块,并使用exports或者module.exports暴露相关属性或方法即可。
- 新建含有公用功能的文件,文件结构与代码如下
- 使用抽离的js功能
// 引用
const public = require("./4_moudle/public.js")
console.log('public', public)
// 功能调用 (以下三种方法分别为公用js中的三种暴露方法的调用方法)
// 方法一:调用exports向外暴露属性或方法
//输出结果 public { UrlApi: [Function: UrlApi] }
var api = public.UrlApi('/api');
response.write(api)
// 方法二:调用module.exports向外暴露属性或方法
//输出结果 public [Function: UrlApi]
var api = public('/api');
response.write(api)
// 方法三:调用exports向外暴露属性或方法
// 输出结果:public { UrlApi: [Function: UrlApi], get: [Function], post: [Function] }
public.get()
public.post()
- 注意事项
1.使用exports向外暴露属性或方法:
返回的是模块函数
使用时需要用模块名称去调用(public.UrlApi)
2.使用module.exports向外暴露属性或方法:
返回的是模块对象本身,返回的是一个类
输出的直接是需要的对象或方法,可直接使用公用js中的方法或属性(public)
3.参考地址:
1.https://www.ucloud.cn/yun/95597.html
通过package.json文件配置项目文件路径(当路径不全时node在默认情况在只找文件夹下的index文件)
1. npm init 或 npm init -y 生成项目的配置文件(package.json文件)
2. package.js中查看入口文件配置main
5.node中的fs模块
- fs.stat 检测是文件还是目录
- fs.mkdir 创建目录(无则创建, 有则返回提示信息)
- fs.writeFile 创建并写入文件(无则创建, 有则替换)
- fs.appendFile 追加文件(无则创建, 有则追加)
- fs.readFile 读取文件
- fs.readdir 读取目录或文件夹
- fs.rename 重命名或移动文件
- fs.rmdir 删除空目录
- fs.unlink 删除文件
var fs = require('fs');
// stat(要检测的路径,回调函数)
fs.stat('./html',(err,data)=>{
if(err){
console.log(err);
return;
}
console.log(`是文件:${data.isFile()}`)
console.log(`是目录:${data.isDirectory()}`)
})
// mkdir(要创建的路径,目录的读写权限、默认777、选填,回调函数传递异常参数err)
fs.mkdir('./html/css',(err)=>{
if (err) {
console.log(err);
return;
}
console.log("mkdir success")
})
// writeFile(要创建文件的路径与文件名称,要写入的内容,设置文件的编码encoding、文件的读写权限默认438、选填,回调函数传递异常参数err)
fs.writeFile('./html/index.html','你好nodejs',(err)=>{
if (err) {
console.log(err);
return;
}
console.log("writeFile success")
})
// appendFile()
fs.appendFile('./html/css/base.css', 'body{color:red}', (err) => {
if (err) {
console.log(err);
return;
}
console.log("appendFile success")
})
// readFile(要检测的路径,回调函数返回的数据为buffer类型),执行需将上述的所有方法注释掉
fs.readFile('./html/index.html',(err,data)=>{
if (err) {
console.log(err.EEXIST);
return;
}
console.log("readFile success:", data)
console.log("readFile success & toString:", data.toString())//将buffer转换成string类型
})
// readdir()
fs.readdir('./html',(err,data)=>{
if (err) {
console.log(err);
return;
}
console.log("readdir success:",data)
})
// rename(旧路径,新路径,回掉函数)
fs.rename('./html/css/aaa.css', './html/css/content.css', (err) => {
if (err) {
console.log(err);
return;
}
console.log("重命名 success")
})
fs.rename('./html/js/index.css', './html/css/index.css', (err) => {
if (err) {
console.log(err);
return;
}
console.log("移动文件 success")
})
// unlink()
fs.unlink('./html/aa/a.js', (err) => {
if (err) {
console.log(err);
return;
}
console.log("unlink success")
})
// rmdir(),执行需将上述的所有方法注释掉
fs.rmdir('./html/aa', (err) => {
if (err) {
console.log(err);
return;
}
console.log("rmdir success")
})
最终目录结构如下
6.node中的fs模块功能练习
判断服务器上是否有upload目录,无则创建upload,有则不做操作(图片上传)
- 方法一:自己编码
var fs = require('fs');
var path = './upload';
fs.stat(path, (err, data) => {
if (err) {
// 无则创建upload
mkdir(path);
}else{
// 有则判断是文件夹还是文件,是文件则创建upload文件夹
if(data.isDirectory()){
// 是文件夹
console.log(`${path}目录存在`)
}else{
// 创建upload:如果存在同名的文件则先删除在创建
if(data.isFile()){
fs.unlink(path, (err) => {
if (err) {
console.log(err);
return;
} else {
mkdir(path)
}
})
}
}
}
})
function mkdir(dir){
fs.mkdir(dir, (err) => {
if (err) {
console.log(err);
return;
}
console.log(`${dir}目录创建成功`)
})
}
- 方法二:使用已有的mkdirp依赖模块
- 安装:cnpm install -S mkdirp
- 使用:
var mkdirp = require('mkdirp');
// 创建单个目录下的文件夹
mkdirp('./uploads', function (err) {
if (err) {
console.error(err)
}
});
//同时创建目录下的多个文件夹
mkdirp('./uploads/img', function (err) {
if (err) {
console.error(err)
}
});
找出指定文件夹wwwroot下面的所有目录,并以数组的形式输出
- 方法一:自己编码
var fs = require('fs');
var dirArr = [];
var path1 = './wwwroot';
fs.readdir(path1, (err, data) => {
if (err) {
console.log(err);
return;
}
// 输出的数组为空(循环先执行,在执行fs.stat)
// for(var i=0;i<data.length;i++){
// fs.stat(path1 + '/' + data[i], (err, stats) => {
// if(stats.isDirectory()){
// dirArr.push(data[i])
// }
// })
// }
// 递归自执行函数
(function getdir(i){
if (i == data.length){
// 执行完成
console.log(dirArr);
return;
}
fs.stat(path1 + '/' + data[i], (err, stats) => {
if(stats.isDirectory()){
dirArr.push(data[i])
}
getdir(i+1)
})
})(0) // 从0开始执行
})
/*
循环先执行,在执行定时器
for(var i=0;i<3;i++){
setTimeout(function(){
console.log(i)
},100)
}
// 3,3,3
*/
fs里面的所有方法全部为异步方法
2. 方法二:使用node的新特性(8+版本)async和await
- promise简介
// promise处理异步
// resolve成功的回调函数
// reject失败的回调函数
function getData(resolve, reject) {
setTimeout(function () {
var name = "zhangsan";
resolve(name);
}, 1000)
}
var p = new Promise(getData);
// 获取异步方法中的数据
p.then((data) => {
console.log(data)
})
- Async、Await简介
1.async是异步的简写,async用于申明一个异步的function,即async是让方法变成异步。(在方法前边加上async即表示该方法为一个异步方法,在外部调用该异步方法时会默认返回一个promise。)
2.await则是async wait的简写,await用于等待一个异步方法执行完成。(await必须用在async的方法里面)
3.异步方法返回一个字符串
async function test(){
return 'hello nodejs'
}
console.log(test()); //Promise { 'hello nodejs' }
// async 方法可以返回一个promise也可以返回一个字符串(会将字符串包装成一个promise)
async function main(){
var data = await test(); // 获取异步方法里面的数据
console.log(data)// 此时data不是promise,而是异步方法test里面的返回值
}
main()
4.异步方法返回一个promise
async function test() {
return new Promise((resolve, reject) => {
setTimeout(function () {
var name = "zhangsan";
resolve(name);
}, 1000)
})
}
async function main() {
var data = await test(); // 获取异步方法里面的数据
console.log(data) // 此时data是异步方法test里面的返回值
}
main()
- 功能实现
// 1.判断一个资源是目录还是文件
var fs = require('fs');
async function isDir(path){
// fs.stat为异步方法,因此需要使用return new Promise解决
return new Promise((resolve, reject)=>{
fs.stat(path, (err, stats) => {
if (err) {
console.log(err);
reject(err);
return;
}
if (stats.isDirectory()) {
resolve(true);
} else {
resolve(false);
}
})
})
}
// 2. 获取目录中的所有资源,循环遍历
function main() {
var path = './wwwroot';
var dirArr = [];
fs.readdir(path, async (err, data) => {
if (err) {
console.log(err);
return;
}
for(var i=0;i<data.length;i++){
if (await isDir(path + '/' + data[i])) {
dirArr.push(data[i])
}
}
console.log(dirArr)
})
}
main()
7.node中的流
以流的方式读取文件和写入文件
- 以流的方式读取文件:fs.createReadStream
var fs = require('fs');
// 以流的方式读取文件:fs.createReadStream
// 1. 创建读取流
// fs.createReadStream(要读取数据的文件路径)
var ReadStream = fs.createReadStream('./test.txt');
// 2. 监听读取的状态ReadStream.on,若监听到data则表示读取到了数据,
// 以流的形式读取时是分段读取的,可记录其读取的次数
var count = 0;
var str='';
ReadStream.on('data',(data)=>{
count ++
str+=data
})
// 读取结束
ReadStream.on('end',()=>{
console.log(str)
console.log(count)
})
// 读取出错
ReadStream.on('error',(err)=>{
console.log(err);
})
- 以流的方式写入文件:fs.createWriteStream
var fs = require('fs');
// 以流的方式写入文件:fs.createWriteStream
var str="";
for(var i=0;i<500;i++){
str += '我是用来测试写入流的数据。\n'
}
// 1. 创建写入流
// fs.createWriteStream(要写入内容的文件路径)
var WriteStream = fs.createWriteStream('./output.txt')
// 2. 写入数据(写入时是分段写入的)
WriteStream.write(str);
// 3. 标记写入完成(标记文件末尾)
WriteStream.end();
// 4. 监听写入流完成
WriteStream.on('finish',()=>{
console.log("写入完成")
})
管道流:从一个流中读取数据并写入到另一个流中(复制大文件且可以进行重命名)
var fs = require('fs');
// 管道流:从一个流中读取数据并写入到另一个流中(复制大文件且可以进行重命名)
// 1. 创建读取流
var ReadStream = fs.createReadStream("./test.txt")
// 2. 创建写入流
var WriteStream = fs.createWriteStream("./test1.txt")
// 3. 通过管道pipe写入数据
ReadStream.pipe(WriteStream)
8. 利用http、url、path、fs模块创建一个web服务器
web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以向浏览器等web客户端提供文档,也可以放置网站文件让全世界浏览,可以放置数据文件让全世界下载,目前最主流的三个web服务器是Apache、nginx、iis
// 1. 引入http模块,创建服务
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');
// 方法一:根据后缀名手动设置响应头
// var mime = require("./model/extName")
// 方法二:使用readFileSync同步读取文件数据,再根据文件后缀设置响应头
// var mime = require("./model/getExtNameFromFile")
// 方法三:使用回调函数读取文件数据,再根据文件后缀设置响应头
// var mime2 = require("./model/getExtNameFromFileCallBack")
// 方法四:
var events = require("events");
var EventEmitter = new events.EventEmitter();
var mime3 = require("./model/getExtNameFromFileEvents")
http.createServer(function (request, response) {
// 2. 获取页面的地址并过滤get的请求值
var pathname = url.parse(request.url).pathname;
// 3. 重置空请求、过滤无效的请求
if(pathname == "/"){
//默认加载首页
pathname = "/index.html"
}
if (pathname != "/favicon.ico"){
// 4. 引入fs模块,获取文件操作
fs.readFile('static/' + pathname,(err,data)=>{
if(err){
console.log("404")
fs.readFile("./static/404.html", (err, data404) => {
if (err) {
console.log(err)
return;
}
response.writeHead(404, {
'Content-Type': 'text/html;charset="utf-8"'
});
response.write(data404);
response.end();
})
}else{
// 5. 引入path模块,根据文件后缀名设置响应头
// 获取文件的后缀名
var extName = path.extname(pathname)
// 根据文件后缀名设置响应头
// 方法一、方法二
// var ExtNameResult = mime.getExtName(extName);
// response.writeHead(200, {
// 'Content-Type': `${ExtNameResult};charset="utf-8"`
// });
// response.write(data);
// response.end();
// 方法三
// mime2.getExtName(extName, function (result) {
// response.writeHead(200, {
// 'Content-Type': `${result};charset="utf-8"`
// });
// response.write(data);
// response.end();
// })
// 方法四
mime3.getExtName(extName);
// 监听广播
EventEmitter.on("on_data", function (result) {
response.writeHead(200, {
'Content-Type': `${result};charset="utf-8"`
});
})
response.end(data);
}
})
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
各类方法的js文件如下
方法一:
// extName.js
exports.getExtName = function (extName) {
switch (extName){
case ".html":
return "text/html";
case ".css":
return "text/css";
case ".js":
return "text/javascript";
default:
return "text/html";
}
}
方法二:
// getExtNameFromFile.js
var fs = require("fs");
exports.getExtName = function (extName) {
var data = fs.readFileSync("./index.json");
var Mimes=JSON.parse(data.toString())
return Mimes[extName] || "text/html";
}
// index.json文件内容见:https://blog.csdn.net/weixin_39893889/article/details/103912469
方法三:
// getExtNameFromFileCallBack.js
// 根据后缀名设置相应头,并根据回调函数读取数据
var fs = require("fs");
exports.getExtName = function (extName ,callback) {
// 根据回调函数读取数据
fs.readFile("./index.json", (err, data) => {
if (err) {
console.log(err)
return;
}
var Mime = JSON.parse(data.toString())
var result = Mime[extName] || "text/html";
callback(result);
})
}
方法四:
// getExtNameFromFileEvents.js
var fs = require("fs");
var events = require("events");
var EventEmitter = new events.EventEmitter();
exports.getExtName = function (extName) {
// 根据回调函数读取数据
fs.readFile("./index.json", (err, data) => {
if (err) {
console.log(err)
return;
}
var Mime = JSON.parse(data.toString())
var result = Mime[extName] || "text/html";
// 触发广播
EventEmitter.emit("on_data", result);
})
}
目录结构如下
9. 非阻塞i/o、异步,事件驱动
在java、 PHP或者.net等服务器端语言中, 会为每一个客户端连接创建一个新的线程,而每个线程需要耗费大约2MB内存。也就是说理论上, 一个8GB内存的服务器可以同时连接的最大用户数为4000个左右, 要让Web应用程序支持更多的用户, 就需要增加服务器的数量, 而Web应用程序的硬件成本当然就上升了。
node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个内部事件, 通过非阻塞I/o、事件驱动机制, 让Node程序宏观上也是并行的。
使用node.js,一个8GB内存的服务器, 可以同时处理超过4万户的连接。
非阻塞i/o、异步
// 对异步方法进行封装,并在封装的方法内部获取数值
function getMime() {
fs.readFile("../static/index.json", (err, data) => {
if (err) {
console.log(err)
return;
}
console.log(data.toString())
})
}
getMime()
// 对异步方法进行封装,并在外部调用时获取数值
function getMime() {
console.log("第一步执行")
fs.readFile("../static/index.json", (err, data) => {
console.log("第三步执行")
if (err) {
console.log(err)
return;
}
return data.toString() // 得到一个undefined
})
console.log("第二步执行")
}
console.log(getMime())
/**
* 得到undefined的原因
* fs.readFile是异步执行的,执行顺序为一、二、三,当程序第二步执行完成后,在执行第三步之前,遇见{}便会返回undefined
* undefined的解决方法
* 1. 用回调函数的方法获取数据
* 2. 用事件驱动的方法获取数据
*/
// 方法一:用回调函数的方法解决undefined
function getMime(callback) {
fs.readFile("../static/index.json", (err, data) => {
if (err) {
console.log(err)
return;
}
callback(data.toString())
})
console.log("第二步执行")
}
getMime(function (data) {
console.log(data)
})
// 方法二:用事件驱动的方法获取数据
var events = require("events")
var EventEmitter = new events.EventEmitter();
function getMime() {
fs.readFile("../static/index.json", (err, data) => {
if (err) {
console.log(err)
return;
}
// 触发广播
EventEmitter.emit("on_data", data.toString());
})
console.log("第二步执行")
}
// 执行方法
getMime()
// 监听广播
EventEmitter.on("on_data",function(mime){
console.log("EventEmitter-on_data:", mime)
})
事件驱动
nodejs有多个内置的事件,可通过引入events模块,并通过实例化EventEmitter类来绑定和监听事件.
nodejs是单进程单线程的应用程序,但是通过事件和回调支持并发,所有性能非常高.
nodejs的每一个API都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。
// 第一步:引入events模块
var events = require("events")
console.log(events)
// 第二步:实例化EventEmitter
var EventEmitter = new events.EventEmitter();
// 第三步:通过EventEmitter发送与接受广播
// 监听to_parent事件(广播)
EventEmitter.on("to_parent", function (data) {
console.log("接受到了这个广播:", data)
EventEmitter.emit("to_mime", "mime发送的数据")
})
EventEmitter.on("to_mime", function (data) {
console.log("接受到了这个广播:", data)
})
// 触发to_parent广播
setTimeout(() => {
EventEmitter.emit("to_parent", "发送的数据")
}, 1000)
10. 路由、ejs模板引擎
针对不同请求的URL,处理不同的业务逻辑
http.createServer(function (request, response) {
console.log(request.url)
var pathName = url.parse(request.url).pathname;
if (pathName == "/login") {
response.end("login");
} else if (pathName == "/register") {
response.end("register");
} else if (pathName == "/order") {
response.end("order");
} else {
response.end("index");
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
ejs模板引擎:后台模板,可以将数据库和文件读取的数据显示到html页面上,是一个第三方模块
- 安装ejs
cnpm install -S ejs
- 使用ejs
http.createServer(function (request, response) {
response.writeHead(200, {
'Content-Type': 'text/html;charset="utf-8"'
});
var pathName = url.parse(request.url).pathname;
if (pathName == "/login") {
// 需要将数据库中的数据渲染到模板上
ejs.renderFile("./view/login.ejs", [], function(err, str){
response.end(str);
});
} else {
var data = "我是后台数据"
ejs.renderFile("./view/other.ejs", {msg:data}, function(err, str){
response.end(str);
});
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
// ejs模板other.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
other ejs 模板
<h2><%= msg %></h2>
</body>
</html>
11. get,post
get:从指定的资源请求数据(一般用于获取数据)
- 通过request.method可以获得请求方式(get、post)
- 通过fs.appendFile将数据写入文件。
post:向指定的资源提交要被处理的数据(一般用于提交数据)
// 获取get或post的请求数据,并将post的数据写入文件
http.createServer(function (request, response) {
response.writeHead(200, {
'Content-Type': 'text/html;charset="utf-8"'
});
var pathName = url.parse(request.url).pathname;
var method = request.method.toLowerCase();
if (pathName == "/login") {
// 显示登录页面
ejs.renderFile("./view/login.ejs", [], function(err, str){
response.end(str);
});
} else if (pathName == "/dologin") {
// 执行登录操作
// request.method获取请求方式
if(method == "get"){
// get获取数据
console.log(url.parse(request.url,true).query)
response.end("dologin get");
}else if(method == "post"){
// post获取数据
var postStr = '';
request.on("data",function(chunk){
postStr+=chunk;
})
request.on("end",function(chunk){
// response.end(postStr);
fs.appendFile("login.txt", postStr, (err) => {
if (err) {
console.log(err);
return;
}
console.log("写入数据 success")
})
response.end("<script>alert('登录成功');history.back()</script>")
})
}
} else {
ejs.renderFile("./view/index.ejs", [], function(err, str){
response.end(str);
});
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
// login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>
<h2>登录</h2>
<form action="/dologin" method="post">
<input type="text" name="user" /><br/>
<input type="password" name="pwd" /><br />
<input type="submit" value="登录" /><br />
</form>
</div>
</body>
</html>