后端与运维

Node.js基础

写在前边

本文大纲

  • 回调机制
  • 事件驱动机制
  • 模块化
  • 路由(get、post)

1、Node.js回调机制

1.1、什么是回调

  • 函数调用方式有三种:同步调用异步调用回调
  • 回调是一种双向调用模式
  • 回调可以通过回调函数来实现

1.2、阻塞与非阻塞

  • 阻塞非阻塞关注的是程序在等待调用结果消息返回值)时的状态

  • 阻塞:回调函数的调用方不执行完不准继续执行后续程序。(通俗释义。。。)

  • 非阻塞:回调函数调用方执行过程中,后续程序继续可执行。(通俗释义。。。)

  • 贴代码,如下:

    //data.txt
    hello block code, haha!
    
    // block.js 阻塞调用示例代码文件
    //阻塞代码
    let fs = require('fs');
    
    var data = fs.readFileSync('data.txt');
    
    // console.log(data);
    //<Buffer 68 65 6c 6c 6f 20 62 6c 6f 63 6b 20 63 6f 64 65 ef bc 8c 20 68 61 68 61 ef bc 81>
    console.log(data.toString());
    
    // -> 'hello block code, haha!'
    
    //non-blocking 非阻塞调用示例代码
    //非阻塞代码
    let fs = require('fs');
    
    fs.readFile('data.txt', (err, data) => {
        //容错
        if(err) {
            return console.error(err);
        }
        //输出文件内容
        console.log(data.toString());
    });
    //先执行下面的log内容,后输出回调中的文件内容;
    console.log('程序执行完毕!');
    
    // 1 -> 程序执行完毕!
    // 2 -> hello block code, haha!
    

2、Node.js事件驱动机制

2.1、事件驱动I/O模型

非阻塞的I/O模型:

img

(上图为事件处理流程)

Node.js运行机制:

  • node是单进程单线程,不能同时并发去完成更多的事情,只能通过事件回调来实现并发的效果。也正因为node不是多线程,所以不需要同时处理很多额外的工作。单进程、单线程的node性能比较高
  • node的API都是异步执行的,都作为一个独立的线程在运行,node利用这种异步函数调用机制来实现并发处理
  • node中几乎所有事件机制都是使用设计模式中的观察者模式来实现的。
  • 上图中的Events是事件队列。

2.2、事件与事件绑定

事件处理代码流程

  1. 引入events对象,创景eventEmitter对象
  2. 绑定时间处理程序
  3. 触发事件
//引入Events模块并创建eventsEmitter对象
let events = require('events');
let eventEmitter = new events.EventEmitter();

//绑定事件处理函数
let connectHandler = () => {
    console.log('connectHandler 被调用');
}
eventEmitter.on('connection', connectHandler);  // 进行事件绑定

//触发事件
eventEmitter.emit('connection');

console.log('程序执行完毕……');

// -> connectHandler 被调用
// -> 程序执行完毕……

3、Node.js模块化

3.1、模块化的概念和意义

  1. 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统
  2. 模块是Node.js应用程序的基本组成部分
  3. 文件模块一一对应的。一个Node.js文件就是一个模块
  4. 这个文件可能是Javascript代码JSON或者**编译过的C/C++**扩展
  5. Node.js中存在4类模块原生模块3种文件模块

3.2、Node.js的模块加载流程

img

(模块加载流程图)

  • 文件缓存区、原始模块缓存区:可以防止某一个模块反复的加载到内存中,这样可以节省内存提高模块的加载速度
  • 上图可以了解到:Node.js模块可以从 文件模块缓存区原始模块缓存区原始模块文件模块加载

3.3、贴代码,模块化示例;

//say.js 文件模块
class Say {
    constructor(something) {
        this.doWhat = something || '';
    }

    hello(name) {
        console.log('hello ' + name + ', ' + this.doWhat);
    }
}

//导出Say模块
module.exports = Say;
// main.js
//引入Say文件模块
let Say = require('./say');

let say = new Say('我们去看场电影,怎么样?');
say.hello('小张');
// -> 'hello 小张, 我们去看场电影,怎么样?'

4、Node.js路由

4.1、贴代码:nodejs程序中的router示例

//server.js

//引入node原生模块 http、url
let http = require('http');
let url = require('url');

//web服务启动函数
function start(router) {
    function requestServer(req, res) {
        //解析请求地址;
        let pathname = url.parse(req.url).pathname;
        console.log('请求来自于' + pathname + '路由地址');
		
        //设置响应头;
        res.writeHead(200, { "Content-Type": "text/html;charset=utf8" });
        
        //设置路由
        router(pathname, res);
        // res.write('Hello World');
        // res.end();
    }
	
    //创建web服务
    http.createServer(requestServer).listen(8888);
	
    console.log('server is starting ...');
}

//导出该函数
exports.start = start;
//router.js
//路由设置函数
function route (pathname, res) {
    if(pathname == '/') {
        res.write('访问根路由');
        res.end();
    } else if(pathname == '/home') {
        res.write('访问/home路由,<a href="https://www.baidu.com" target="_blank">去百度</a>');
        res.end();
    } else {
        res.end('404');
    }
}

//导出该函数
exports.route = route;
//index.js
// 引入node 文件模块 server、router
let server = require('./server');
let router = require('./router');

//启动web服务
server.start(router.route);

4.2、get请求处理

//node 处理get 请求

let http = require('http');
let url = require('url');
let util = require('util');

http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf8'});
    //简单输出
    // res.end(util.inspect(url.parse(req.url, true)));
    //复杂输出
    let params = url.parse(req.url, true).query;
    res.write('查询姓名:' + params.name);
    res.write('\n');
    res.write('查询年龄:' + params.age);
    res.end();    
}).listen(8886);

console.log('server is starting……');

4.3、post请求处理

  • 模拟前端服务

    <!-- 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>统计个人信息</title>
    </head>
    <body>
        <!-- <form> -->
            姓名:
            <input type="text" name="name" id="name" placeholder="请填写真实姓名"><br>
            年龄:
            <input type="text" name="age" id="age" placeholder="请填写真实年龄"><br>
            
            <input type="submit" id="btn"> <br>
        <!-- </form> -->
    
            后端返回数据: <span id="data"></span>
        <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
        <script src="./index.js"></script>
    </body>
    </html>
    
    
    //index.js
    
    $('#btn').click(function (e) {
        e.stopPropagation();
        $.ajax({
            method: 'post',
            url: 'http://localhost:8886/add-user',
            data: {
                name: $('#name').val(),
                age: $('#age').val()
            },
            success: function(res) {
                $('#data').html(res);
            },
            fail: function(res) {
                $('#data').html('error:' + res);
            }
        });
    });
    
  • 模拟后端node服务

    //postReq.js
    //node 处理post 请求
    
    let http = require('http');
    let url = require('url');
    let querystring = require('querystring');
    let util = require('util');
    
    http.createServer((req, res) => {
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf8', 'Access-Control-Allow-Origin': '*'});
        
        let pathname = url.parse(req.url).pathname;
        let postData = '';
        if(pathname == '/add-user') {
            req.on('data', (chunk) => {
                postData += chunk;
            })
            req.on('end', () => {
                postData = querystring.parse(postData);
                // res.write(util.inspect(postData));
                // res.write('<br>');
                if (postData.name && postData.age) {
                    res.write('接口返回姓名:' + postData.name + '; ');
                    // res.write('<br>');
                    res.write('接口返回年龄:' + postData.age);
                } else {
                    res.write('404');
                }
                res.end();
            })
        } else {
            res.end('request address error');
        }
    
        // res.end('hello');
    }).listen(8886);
    
    console.log('server is starting……');
    
  • 启动前后端服务,并在前端添加表单,经过node的web服务处理后 返回给前端显示。

参考文档