学习笔记-初识 Node.js (介绍常用模块 + 带一个注册 / 登录实践示例)

780 阅读7分钟

已经有很长一段时间没有写过文章了,这次也算又重新回归社区。亲爱的掘友们,我回来了!哈哈, 废话少说,今天来聊一聊我们前端最容易入手的后端语言node.js。其实相对node.js是早有耳闻,只是迫于没有系统学习,之前总是一知半解,甚至不知道到底该怎么用?用在哪里?最近一段时间,在大神的带领下,现在算是入了一点门儿,故写下此篇笔记,如文中有错,还望各位路过的大神帮忙指点一二。小女子在这厢有礼了!

一、简介

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
                                                     -nodejs官网的介绍

“简单的说 Node.js 就是运行在服务端的 JavaScript。”


1. 用途分析:

  1. 中间层(安全,性能高,降低主服务器的复杂度):

node.js可作为在客户端和服务端的中间层,用于搭界两端,避免主服务器直接暴露在外,也保证了主服务器的安全性;并且中间层可做一系列的定制,提高访问速度,降低主服务器的复杂度等等。

  1. 小型服务

node.js可作为小型服务搭建,使用更便捷

  1. 工具类开发

node.js计算速度较快,可用于开发各种打包,转编译等工具,例如:webpack,gulp,babel...


2. 前端学习的优势:

  1. 便于前端入手,语法和前台js一样
  2. 性能高(使用chrome 的 v8引擎)
  3. 更加利于和前端代码整合

3. 快速上手:

  1. 下载地址:

    官方:nodejs.org/en/

    中文:nodejs.cn/

  2. 命令行使用:

    可查看下载版本:

node -v

输入node 回车后,可在命令行直接进行计算,如下:

先输入node,告诉命令行你要在node 环境下执行任务:

node 

查看当前已经下载的node版本:

nvm list

image.png

修改当前使用的node版本(从下载列表中选择你需要的版本):

nvm use v14.0.0

最后查看当前版本:

node -v

image.png

下载其他版本的node:

nvm install v16.0.0

下载最稳定版本node:

n stable

拓展:

node 下载完成后会自带一个npm ,用来下载各类的库,工具等等。


二、系统自带模块方法

来介绍几个比较常用系统自带的系统模块,直接引用后即可使用。

介绍模块之前,我们先来一个小测试,用来测试我们的js文件是否可以正常执行:

console.log("node测试")

控制台输出:

测试完成,控制台可正常显示,可进行下一步操作。


接下来正式来介绍我们的模块。

1. http模块:

这个模块是node里面用的最多,最基础的模块,用于启动我们的服务,监听相应的端口。

//直接引入即可
const http =  require("http"); 

// 启动服务,并监听相应端口
http.createServer((req,res)=>{
   
    console.log("请求来了");
 
}).listen(8000)  // 8000 监听的端口号

接下来,去控制台执行此文件:

然后在浏览器手动输入我们监听的端口号:

localhost:8000

返回控制台,查看监听结果:

此时我们已经可以成功启动服务器,并且监听相应的端口

http.createServer中有两个参数 requestresponse,后面我们会多次用到,现在来看一下,这两个里面都可以返回写什么?

  • request:简写:req:
const http =  require("http"); 


http.createServer((req,res)=>{
   
    console.log(req);
 
}).listen(8000)  

里面包含众多参数,以至于我无法用截图来展示。

先介绍两个req.urlruq.method

const http =  require("http");

http.createServer((req,res)=>{

// req.url 拿到当前端口后面的path
// req.method 拿到当前请求的方式

    console.log(req.url,req.method)
 
}).listen(8000)

浏览器输入:

http://localhost:8000/root/abc

查看控制台输出:

  • response : res

先来看看里面都有什么

const http =  require("http");

http.createServer((req,res)=>{

    console.log(res)
    
}).listen(8000)

如同res一样,内容数不胜数

我们今天先讲其中的一个方法,res.write(),往浏览器写入内容:

const http =  require("http");

http.createServer((req,res)=>{

     res.write("abc") // 写入浏览器,只限字符串
    
     console.log("请求来了");
    
     res.end() //结束,浏览器才会正常执行
    
    console.log(res)
    
}).listen(8000)

接下来,去控制台执行此文件,在浏览器手动输入如下地址:

http://localhost:8000

查看浏览器展示效果,已经写入浏览器:


2. path模块:

用于解析路径,拿到对应的文件名,扩展名以及路径等。

  • path.dirname() 获取目录名称
const http = require("http");

const path = require("path");

http.createServer((req,res)=>{

    console.log(path.dirname(req.url));

}).listen(8000)

先在控制台启动服务器,运行上面的js代码,然后浏览器输入如下地址:

http://localhost:8000/root/build/a.text

返回查看控制台执行:

  • path.basename() 获取文件名
const http = require("http");

const path = require('path');

http.createServer((req,res)=>{

    console.log(path.basename(req.url))
    
}).listen(8000)

先控制台启动服务器,运行上面的js代码,然后浏览器输入如下地址:

http://localhost:8000/root/build/a.text

返回查看控制台执行:

  • path.extname() 获取文件扩展名
const http = require("http");

const path = require("path");

http.createServer((req,res)=>{

    console.log(path.extname(req.url)) 

}).listen(8000)

先控制台启动服务器,运行上面的js代码,然后浏览器输入如下地址:

http://localhost:8000/root/build/a.text

返回查看控制台执行:

  • path.resolve() 用于解析一系列混乱的路径,拿到最终的路径
const http = require("http");

const path = require("path");

http.createServer((req,res)=>{

    console.log(path.resolve("__dirname","build")) //拿到绝对路径
    console.log(path.resolve('root/a/b','../c','build',"..","strict")) //解析路径
    
}).listen(8000)

控制台启动服务器,运行上面的js代码,浏览器输入如下地址

http://localhost:8000

查看控制台执行


3. url模块

用于返回解析完成的url路径

const url = require("url");

let str="http://localhost:8080/root/a/b?a=1&b=8";

console.log(url.parse(str,true)) 

控制台启动服务器,运行上面的js代码,查看解析结果

第二个参数true可以将url中的query参数转换成json格式


4. node自带的querystring模块已经废弃,进行参数格式转换

可以下载替代品,使用方法也是一样的:

cnpm i querystringify  

  • querystring.parse() 将url的参数解析成json
const querystring = require("querystring");

console.log(querystring.parse("a=12&b=5&c=2")) 

控制台启动服务器,运行上面的js代码,查看解析结果

  • querystring.stringify()json反解析成以&连接的格式
const querystring= require("querystring");

let json={"a":5,"b":8};

console.log(querystring.stringify(json)) 

控制台启动服务器,运行上面的js代码,查看解析结果


5. assert模块

assert 断言,检测异常代码,当代码块中遇到assert时会进行检测,如果条件正确,不做任何处理,如若错误,直接抛出异常,不再向下执行。

const assert = require ('assert');

assert(5<3,'出错了')

console.log("123")

控制台启动服务器,运行上面的js代码,查看解析结果

  • assert.deepEqual() 深度相等判断,只比对值,不比对类型,相等与运算中的"=="
const assert = require("assert");

const boo = 55;

assert.deepEqual(boo,"55","出错了") 

控制台启动服务器,运行上面的js代码,查看解析结果

  • assert.deepStrictEqual() 深度严格相等判断,相等与运算中的"==="
const assert = require("assert");

const boo ={a:1,b:2,c:["555"]}

assert.deepStrictEqual(boo,{a:1,b:2,c:[555]},"出错了") 

方法可用于函数调用,传参时的判断,或者大型计算得出结果判断,避免因为参数错误,导致程序出错。


6. fs模块,可用于读取文件,写入文件,将资源文件拷贝至目标文件等。

fs.readFile() 读取文件

  • 第一参数为文件位置及文件名
  • 第二个参数为回调函数

读取本地data文件夹的2.text

本地的2.text文件

const fs = require("fs");

// readFile()方法有两个参数,一个是错误的err,一个是正确的data

fs.readFile("./data/2.txt",(err,data)=>{
   if(err){
    console.log("失败了")
   }else{
     console.log("成功了",data.toString())
   }
})

控制台启动服务器,运行上面的js代码,查看解析结果

下面来试试读取图片,并写入浏览器

html部分:

<!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>
    <p>我有图片</p>
    <img src="./data/timg.jpeg">
</body>
</html>

js部分:

const http = require("http");
const fs = require('fs');

let server=http.createServer((req,res)=>{
   console.log(req.url)
fs.readFile(`./${req.url}`,(err,buffer)=>{
    //处理请求,成功or失败
    if(err){
        res.writeHead(404);
        res.write('Not Found6',err);
        console.log(err)
        res.end();
    }else{
        res.write(buffer);
        res.end();
    }
})
});
server.listen(8000);

控制台启动服务器,运行上面的js代码,浏览器输入如下地址:

http://localhost:8000/img.html

查看解析结果

fs.writeFile() 写入文件

  • 第一个参数是添加的位置以及文件名
  • 第二个参数是写入的内容
  • 第三个参数是回调函数

写入本地data文件夹的1.text

const fs = require("fs");

fs.writeFile('./data/1.txt',`abc`,(err)=>{
    if(err){
        console.log("失败了")
    }else{
        console.log("成功了")
    }
})

控制台启动服务器,运行上面的js代码,查看解析结果

查看我的本地文件:

实现一个拷贝,从2.text 拷贝内容到1.text

const fs = require("fs");

( (source,target)=>{
    let result = fs.readFileSync(source,'utf8');
    fs.writeFileSync(target,result)
    
})('./data/2.txt','./data/1.txt');

控制台启动服务器,运行上面的js代码,查看两个文件


7. 流操作文件: createReadStream createWriteStream

在上面我们介绍了如何读取和写入资源,就像下面这样:

fs.readFile('www/1.html'),(err,buffer)=>{

    res.write(buffer);
});

它的工作模式是:先把文件读取到内存中,然后再扔过去,这样的操作有两个明显的缺点

  1. 占用内存
  2. 资源使用不均匀

createReadStream createWriteStream

推荐使用流操作,一边读,一边写,资源均衡

示例:

1.text:

12121212121212121
1212121
121212
121212

121212
121

stream.js:

const fs = require('fs');

// 读取流
let rs = fs.createReadStream('./1.text');

//写入流
let ws = fs.createWriteStream('./2.text');

rs.pipe(ws);

ws.on('error', (err) => {
  console.log(err);
});

ws.on('finish', () => {
  console.log('写入成功');
});

在命令行执行:

node stream.js

截屏2023-01-02 18.20.14.png

在文件夹会冒出一个2.text 的文件,文件内容同1.text,成功!

压缩

尝试读取1.text,然后进行压缩,然后输出2.text.gz 文件

const fs = require('fs');
const zlib = require('zlib');

// 读取流
let rs = fs.createReadStream('1.text');
let gz = zlib.createGzip();
//写入流
let ws = fs.createWriteStream('2.text.gz');

rs.pipe(gz).pipe(ws);

ws.on('error', (err) => {
  console.log(err);
});

ws.on('finish', () => {
  console.log('写入成功');
});

压缩成功!

WeChatd703846a43d132d95fe2dc5710169956.png


将读取内容写入服务器

const http = require('http');
const zlib = require('zlib');
const url = require('url');
const fs = require('fs');


http.createServer((req, res) => {
  let { pathname } = url.parse(req.url, true);

  let rs = fs.createReadStream('www' + pathname);

  rs.on('error', err => {

    res.writeHead(404);
    res.write("not found");
    res.end();
  })

  res.setHeader('content-encoding', 'gzip');
  let gz=zlib.createGzip()
  rs.pipe(gz).pipe(res);
}).listen('8080');

写入服务器成功,且已经执行过压缩

WeChat76f9da82ef4da275eb036bab8d902f89.png

代码优化,处理边界错误:

const http = require('http');
const zlib = require('zlib');
const url = require('url');
const fs = require('fs');


http.createServer((req, res) => {
  let { pathname } = url.parse(req.url, true);
  let filepath ='www' + pathname


  // 先检查文件状态,在执行操作
  fs.stat(filepath, (err,stat) => {
    if (err) {
      res.writeHead(404);
      res.write("not found");
      res.end();
    } else {
      let rs = fs.createReadStream(filepath);
      rs.on('error', err => { });
      res.setHeader('content-encoding', 'gzip');
      let gz=zlib.createGzip()
      rs.pipe(gz).pipe(res);
     }
   })

}).listen('8080');

此时,当输入一个不存在的地址时,也不会导致浏览器崩溃

WeChatb67d8a8eedd6728ae09f20bf59c98876.png


8. 推荐一个启动器 forever

使我们应用一直在运行,即使是崩了也会自动重启,关闭网页,关闭终端,甚至是关机也不会受影响。

下载:

npm i forever -g

启动(告诉forever 你需要启动的文件):

forever start xxx.js

查看当前启动的服务列表:

forever list

手动重启服务:

forever restart xxx.js

停止指定服务:

forever stop xxx.js

停止所有forever 开启的服务(谨慎使用):

forever stopall

三、实现一个get和一个post请求的登录 + 处理请求

1. 来实现一个get请求的登录

html部分:

<!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>
        <form action="http://localhost:8000/abc" method="get">
            用户名 <input type="tet" name="usernam"><br/>
            密码   <input type="password" name="pass"><br/>
                  <input type="submit" value="提交">
        </form>

    </body>

</html>

js部分:

const http = require("http");
const querystring = require("querystring");

http.createServer((req,res)=>{
    console.log(req.url);

    let [url,query] = req.url.split("?");
    console.log(url,query);

    let get =querystring.parse(query);
    console.log(get)

}).listen(8000)

控制台启动服务器,运行上面的js代码,查看前台页面,并将用户名密码提交:

查看控制台数据:


2. 来实现一个post请求的登录

html部分

<!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>
    <form action="http://localhost:8000/abc" method="post">
        用户名: <input type="text" name="username"><br/>
        密码:   <input type="password" name="pass" ><br/>
                <input type="submit" value="提交">
    </form>
    
</body>
</html>

js部分:

const http = require("http");

const querystring = require("querystring");

//post 因为数据比较大,数据会分好几次发送回来,接收的时候也是分段来接收,此处用数组来保存

let arr=[];
let server=http.createServer((req,res)=>{
    // 接收到数据
    req.on('data',buffer=>{
        arr.push(buffer)
    });
   //  接收完毕
    req.on('end',()=>{
    let buffer = Buffer.concat(arr);
    let post = querystring.parse(buffer.toString())
        console.log(post)
    })
});
server.listen(8000);

控制台启动服务器,运行上面的js代码,查看前台页面,并将用户名密码提交:

查看控制台返回数据:


3. 整合getpost 请求

html部分:

<!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>

        <form action="http://localhost:8000/abc" method="post">
            用户名 <input type="tet" name="usernam"><br/>
            密码   <input type="password" name="pass"><br/>
                  <input type="submit" value="提交">
        
        </form>

    </body>

</html>

js部分:

const http = require("http");
const querystring = require("querystring");

http.createServer((req,res)=>{
    // console.log(req.method)
    if(req.method==='GET'){
        // console.log(req.url)
        let [url,query] = req.url.split("?");
        let get = querystring.parse(query);
        console.log(url,get)
    }else{
        let arr=[];
        req.on("data",(buffer)=>{
            // console.log(buffer)
            arr.push(buffer)
        });
        req.on("end",()=>{
           let buffer= Buffer.concat(arr);
           let post = querystring.parse(buffer.toString());
           url= req.url
            console.log(url,post)
        })
    }
}).listen(8000)

控制台启动服务器,运行上面的js代码,查看前台页面,并将用户名密码提交:

查看控制台返回数据:

四、实现一个简单的登录&注册的前后端数据交互

现在我们整合上面所提到的方法,来实现一个前后端页面交互,包括接口调用以及页面正常交互动作。

html部分:

<!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>
        <script src="./jquery-1.7.2.js"></script>
    </head>

    <body>


            用户名 <input type="tet" id="user"><br/>
            密码   <input type="password" id="pass"><br/>
                  <input type="button" id="btn1" value="注册">
                  <input type="button" id="btn2" value="登录">
    <script>

            $(function(){
                $("#btn1").click(()=>{
                    $.ajax({
                        url:"/reg",
                        dataType:"json",
                        data:{
                            username:$("#user").val(),
                            password:$("#pass").val()
                        }
                    }).then((json)=>{
                        if(json.error){
                            alert(json.msg)
                        }else{
                            alert("注册成功")
                        }
                    },error=>{
                        alert("注册失败,请刷新重试")
                    })
                });

                $("#btn2").click(()=>{
                    $.ajax({
                        url:"/login",
                        dataType:"json",
                        data:{
                            username:$("#user").val(),
                            password:$("#pass").val()
                        }
                    }).then((json)=>{
                        if(json.error){
                            alert(json.msg)
                        }else{
                            alert("登录成功")
                        }
                    },error=>{
                        alert("登录失败,请刷新重试")
                    })
                });

            })

       </script>

    </body>

</html>

展现效果:


js部分:


const http = require('http');
const querystring =  require('querystring');
const url = require('url');
const fs = require('fs');

let users ={}
http.createServer((req,res)=>{
let path ='',get={},post={};
console.log(req.method)
if(req.method === 'GET'){
   let {pathname,query}= url.parse(req.url,true)
    path = pathname;
    get = query;
    console.log()
    complete()
}else{
    let arr =[];
    path = req.url
    req.on("data",buffer=>[
        arr.push(buffer)
    ]);
    req.on('end',()=>{
       let buffer = Buffer.concat(arr)
        post = querystring.parse(buffer.toString())
        // complete()
    })
}
function complete(){
    // console.log(path,get,post)
    // 注册
    if(path =='/reg'){
        let {username,password} = get;
        if(users[username]){
            res.write(JSON.stringify({error:1,msg:'此用户名已存在'}));
            res.end()
        }else{
            users[username] = password;
            res.write(JSON.stringify({error:0,msg:'注册成功'}));
            res.end()
        }

    }else if(path == '/login'){
        // 登录
        let {username,password} = get;
        if(users[username]){
            if(users[username] !== password){
                res.write(JSON.stringify({error:1,msg:'密码错误'}));
                res.end()
            }else{
                res.write(JSON.stringify({error:0,msg:'登录成功'}));
                res.end()
            }
           
        }else{
            res.write(JSON.stringify({error:1,msg:'此用户不存在'}));
            res.end()
        }
    }else{
      
        fs.readFile(`./${path}`,(err,buffer)=>{
            if(err){
                res.writeHead(404);
                res.write('Not Found');
                res.end()
            }else{
                res.write(buffer);
                res.end()
            }
        })
    }
}

}).listen('8000')

控制台启动服务器,运行上面的js代码,查看前台页面,并将用户名密码提交:


到此我们就把node 的一些常用模块进行了了解,只学习原生模块是远远不够的,后面我去准备一下node 的框架,敬请期待吧!