node.js Stream(流) 和 EJS 模板引擎——0822

一、node.js 中的 Stream(流)

1、什么是 Stream ?

Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。

Node.js,Stream 有四种 流类型

Readable - 可读操作。
Writable - 可写操作。
Duplex - 可读可写操作.
Transform - 操作被写入数据,然后读出结果。

所有的 Stream 对象都是 EventEmitter 的实例。常用的 事件 有:

data - 当有数据可读时触发。
end - 没有更多的数据可读时触发。
error - 在接收和写入过程中发生错误时触发。
finish - 所有数据已被写入到底层系统时触发。

2、从流中读取数据

步骤:
创建读取流——设置读取流的编码——处理流事件(data, end, error)。

流的事件是Event.Emit驱动的。

const fs = require('fs');

let Data = "";
//创建读取流
let streamRead = fs.createReadStream('./stu.txt');

//设置流的编码
streamRead.setEncoding('utf-8');

//处理流事件   --> data   end    error
streamRead.on('data', (chunk) => {
    Data += chunk;
});
streamRead.on('end', () => {
    console.log(Data);
});
streamRead.on('error', (err) => {
    console.log(err);
})

3、写入流

写入流 可以写在 读取流end()事件里边。

步骤:
创建一个可以写入的流——使用指定的编码写入数据——标记文件末尾(结束写入流)——处理流的事件(finisherror)。

const fs = require('fs');

//创建一个可以写入的流 写入到指定的文件中
let writeStream = fs.createWriteStream('./data.txt');

//使用指定的编码格式写入流
writeStream.write("写入流的数据", "utf-8", (err) => {
    if (err)
        console.log("写入失败!");
});

//标记文件末尾 --结束流
writeStream.end();

//处理流事件
writeStream.on('finish', () => {
    console.log("写入完成!");
})

查看 data.txt 文件的内容:

cat data.txt 

4、管道流

什么是管道流?
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。(会覆盖)

步骤:
创建一个可读流——创建一个可写流——管道读写操作

let readStm = fs.createReadStream('./stu.txt');
let writeStm = fs.createWriteStream('./data.txt');
//管道读写操作
//读取stu.txt文件的内容  并将内容写到data.txt文件中
readStm.pipe(writeStm);

5、链式流

什么是链式流?
链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。

比如,用管道和链式来压缩和解压文件。

let readStream = fs.createReadStream('./stu.txt');
//压缩 ./stu.txt 文件为 ./stu.txt.zip 文件
readStream.pipe(zip.createGzip()).pipe(fs.createWriteStream('./stu.txt.zip'));

二、web服务器静态文件托管

使用http模块、url模块、fs模块、path模块、自定义的extName模块创建一个静态的web服务器:

静态web服务器的目录结构:
图1
代码如下:
server.js 代码:

const http = require('http');
const fs = require('fs');
const url = require('url');
//path内置模块  可以取出路径名的后缀名称
const path = require('path');
//引入自定义模块   解析后缀名称为对应的文件格式
let extname = require('extName');
let port = 8000;
let hostname = 'localhost';
const server = http.createServer((req, res) => {
    let pathname = url.parse(req.url, true).pathname;
    if (url != '/favicon.ico') {
        if (pathname == "/") {
            pathname = "/index.html";
        }
        //这里要解析后缀名  根据后缀名取对应的文件格式 比如html对应"text/html"  用来设置请求头
        //先取出路径名的后缀名称
        let exname = path.extname(pathname);
        //使用后缀命名解析模块  解析后缀名
        //使用promise传递
        // extname(exname).then((lastname) => {
        //     res.setHeader("Content-Type", `${lastname};charset=utf8`);
        //     fs.readFile("./static" + pathname, { flag: "r+" }, (err, str) => {
        //         if (err)
        //             throw err;
        //         res.end(str)
        //     })
        // })

        //使用callback传递
        extname(exname, (lastname) => {
            res.setHeader("Content-Type", `${lastname};charset=utf8`);
            fs.readFile("./static" + pathname, { flag: "r+" }, (err, str) => {
                if (err)
                    throw err;
                res.end(str)
            })
        })
    }
});
server.listen(port, hostname, () => {
    console.log(`服务器运行在:http://${hostname}:${port}/`);
})

extName.js 代码如下:

let fs = require('fs');
//使用promise传递
// module.exports = (name) => {
//     let promise = new Promise((resolve, reject) => {
//         fs.readFile(__dirname + "/mime.json", (err, str) => {
//             if (err)
//                 throw err;
//             let namelist = JSON.parse(str.toString());
//             resolve(namelist[name]);
//         });
//     });
//     return promise;
// }

//使用callback  传递
module.exports = (name,callback) => {
    fs.readFile(__dirname + "/mime.json", (err, str) => {
        if (err)
            throw err;
        let namelist = JSON.parse(str.toString());
        callback(namelist[name]);
    });
}

关于 extName 自定义模块:
(1)mime.json 是后缀名对应文件格式的json
(2)在extName.js 文件里,暴露出一个方法(解析后缀名),该方法返回一个promise 对象,通过读取 mime.json 文件之后,根据后缀名取到对应的文件格式,resolve 出去;
(3)传递 namelist[name] 可以使用promise,也可以使用callback,或者其他异步方式。

三、事件驱动 EventEmitter

Node.js的三大特点: 单线程、非阻塞式IO(异步)、事件驱动.

处理异步请求,外边获取请求到的数据的方式:
回调函数、使用node.js内置的events模块进行数据的广播和监听(事件驱动EventEmitter)。

(1)回调函数获取异步请求到的数据:

//1.使用回调函数来获取数据
function getMime(callback){
    fs.readFile("mime.json",function (err,res){
        if(err) throw err;
        callback(res.toString());
    });
}
console.log(1);
//这样就可以在外部获取异步的数据  也可以暴露出去别的文件使用
module.exports=getMime(function (data){
    console.log(data);
});

(2)EventEmitter

什么是Node.js EventEmitter?
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。

EventEmitter 类:
events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发事件监听器功能的封装。

使用 EventEmitter:

// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();

对于每个事件,EventEmitter 支持 若干个事件监听器。
当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。

下边是一个通过EventEmitter 进行数据的广播和监听的例子:

var fs=require("fs");

var event=require("events");
//实例化事件的对象
var EventEmitter=new event.EventEmitter();
//使用events模块 获取异步的数据
//在请求完数据之后进行广播
fs.readFile("mime.json",function (err,res){
    if(err) throw err;
    EventEmitter.emit("mime",res.toString());
});

//在外部进行监听广播
EventEmitter.on("mime",function (data){
    console.log(data);
});

注意:

先监听( on) ,后分发( emit)(分发到事件队列里边)。
上边的on是先与emit执行的。

在不同的文件之间广播和监听使用都可以获取数据。

(3)EventEmitter 类的方法和属性

EventEmitter 提供了多个属性,如 onemiton 函数用于绑定事件函数,emit 属性用于触发一个事件。

下边是一些其它的属性:
方法:
addListener(event, listener) 为指定事件添加一个监听器到监听器数组的尾部。

let fun1=()=>{
	console.log("1");
}
eventemitter.addListener("collection",fun1);
eventemitter.emit("collection");

once(event, listener) 为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
removeListener(event, listener) 移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。它接受两个参数,第一个是事件名称,第二个是回调函数名称。
removeAllListeners([event]) 移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
setMaxListeners(n) 默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于提高监听器的默认限制的数量。
listeners(event) 返回指定事件的监听器数组。
emit(event, [arg1], [arg2], [...]) 按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。

类方法:
listenerCount(emitter, event) 返回指定事件的监听器数量。

eventemitter.listenerCount(eventName) 

error 事件:
EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到 异常的时候通常会触发 error 事件。

当 error 被触发时,EventEmitter 规定如果没有响 应的监听器,Node.js 会把它当作异常,退出程序并输出错误信息。

我们一般要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。例如:

var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.emit('error'); 

上边的代码 没有设置监听器,会报错。

四、web服务器静态文件托管+get、post路由传值

静态web服务器结构:
图2
index.html 代码如下:

<form action="/login" method="POST">
        账号:<input type="text" name="id">
        密码:<input type="password" name="pwd">
        <button>登录</button>
</form>

server.js 代码如下:

const http = require('http');
const url = require('url');
let fs = require('fs');
let port = 8000;
let hostname = 'localhost';
let server = http.createServer((req, res) => {
    let parse = url.parse(req.url, true);
    let path = parse.pathname;
    if (req.url != '/favicon.ico') {
        res.setHeader('Content-Type', 'text/html;charset=utf8');
        if (path == '/') {
            fs.readFile('./static/index.html', (err, str) => {
                if (err)
                    throw err;
                else {
                    res.end(str);
                }
            })
        }
        else if (path == '/login') {
            let info = "";
            if (req.method == 'GET') {
                info = parse.query.id + "---" + parse.query.pwd;
                res.end("结果:" + info);
            }
            else {
                req.on('data', (s) => {
                    info += s;
                });
                req.on('end', () => {
                    res.end("结果:" + info);
                })
            }
        }
    }
});
server.listen(port, hostname, () => {
    console.log(`服务器运行在http://${hostname}:${port}/`);
})

如果将以上代码的路由部分封装起来:
目录结构:
图3
router.js 代码如下:
暴露出一个对象,键名为路径

let fs = require('fs');
module.exports = {
    '/': (req, res) => {
        fs.readFile('./static/index.html', (err, str) => {
            if (err)
                throw err;
            else {
                res.end(str);
            }
        })
    },
    '/login': (req, res) => {
        let info = "";
        if (req.method == 'GET') {
            info = parse.query.id + "---" + parse.query.pwd;
            res.end("结果:" + info);
        }
        else {
            req.on('data', (s) => {
                info += s;
            });
            req.on('end', () => {
                res.end("结果:" + info);
            })
        }
    }
}

server.js 代码如下:
执行路径对应的函数,传参

const http = require('http');
const url = require('url');
let router = require('./router');
let port = 8000;
let hostname = 'localhost';
let server = http.createServer((req, res) => {
    let parse = url.parse(req.url, true);
    let path = parse.pathname;
    if (req.url != '/favicon.ico') {
        res.setHeader('Content-Type', 'text/html;charset=utf8');
        router[path](req, res);
    }
});
server.listen(port, hostname, () => {
    console.log(`服务器运行在http://${hostname}:${port}/`);
})

五、ejs 模板引擎

——从后台渲染数据到页面

ejs官网:https://ejs.bootcss.com/#install

需要掌握标签含义、用法、include、缓存(lru-cache);了解options参数的使用。

安装:

cnpm install --save-dev ejs

引入和使用:

let ejs = require('ejs'),
    people = ['geddy', 'neil', 'alex'],
    html = ejs.render('<%= people.join(", "); %>', {people: people});

下边是需要掌握的 ejs 基本用法:

1、标签含义

<% ‘脚本’ 标签,用于流程控制,无输出。
<%_ 删除其前面的空格符
<%= 输出数据到模板(输出是转义 HTML 标签)
<%- 输出非转义的数据到模板
<%# 注释标签,不执行、不输出内容
<%% 输出字符串 '<%'
%> 一般结束标签
-%> 删除紧随其后的换行符
_%> 将结束标签后面的空格符删除

2、用法

let template = ejs.compile(str, options);
template(data);
// => 输出渲染后的 HTML 字符串
ejs.render(str, data, options);
// => 输出渲染后的 HTML 字符串
ejs.renderFile(filename, data, options, function(err, str){
    // str => 输出渲染后的 HTML 字符串
});

完整的用法示例:

server.js 代码:

const http = require('http');
const ejs = require('ejs');
let url = require('url');
let port = 8000;
let hostname = 'localhost';
const server = http.createServer((req, res) => {
    //路径解析
    let parse = url.parse(req.url, true);
    let pathname = parse.pathname;
    if (req.url != '/favicon.ico') {
        res.setHeader('Content-Type', 'text/html;charset=utf8');
        if (pathname == '/') {
            let menu = ["首页", "导航"];
            //用法一
            // let html = ejs.render("<div><%=people%></div>", { people: "张翠花"});
            // res.end(html);

            //用法二
            ejs.renderFile(__dirname + '/view/index.ejs', { menu: menu }, (err, str) => {
                if (err)
                    throw err;
                res.end(str);
            });

            //用法三
            // let template = ejs.compile("<div><%=people%></div>");
            // let html = template({ people: "张翠花" });
            // res.end(html);
        }
    }
});
server.listen(port, hostname, () => {
    console.log(`服务器运行在:http://${hostname}:${port}/`);
})

index.html 代码:

    <%for(var i=0;i<menu.length;i++){%>
    <div><%=menu[i]%></div>
    <%}%>

3、include

通过 include 指令将相对于模板路径中的模板片段包含进来。

可以传值,服务端传给页面,页面再传给该模板片段。

如,在页面中插入模板片段 “title.ejs”:

<%-include("./module/title.ejs",{menu:menu})%>

4、缓存(lru-cache)

EJS 附带了一个基本的进程内缓存,用于缓在渲染模板过程中所生成的临时 JavaScript 函数。

使用方式:
通过 Node 的 lru-cache 库加入 LRU 缓存:
先下载lru-cache ,接下来:

let ejs = require('ejs'),
    LRU = require('lru-cache');
ejs.cache = new LRU(100); // 具有 100 条内容限制的 LRU 缓存

如果要清除 EJS 缓存,调用 ejs.clearCache 即可。如果你正在使用的是 LRU 缓存并且需要设置不同的限额,则只需要将 ejs.cache 重置为 一个新的 LRU 实例即可。

六、模拟express 封装路由模块

server.js 文件—— 模拟 express 路由模块的使用:

let http = require('http');
let app = require('./app');
let ejs = require('ejs');
let server = http.createServer(app);
server.listen(8000, () => {
    console.log('http://localhost:8000');
})

//模拟express的路由
app.get("/", (req, res) => {
    ejs.renderFile(__dirname+"/view/index.ejs", (err, str) => {
        res.send(str);
    });
})
app.get("/login", (req, res) => {
    console.log(req.query);
    res.send('登录');
})
app.post("/login",(req,res)=>{
    console.log(req.body);
    res.send("denglu")
})

app.js 文件——模拟 express 封装路由模块:

let obj = this;
obj._get = {};//存储get请求的回调函数
obj._post = {};//存储post请求的回调函数
let url = require("url")
let app = (req, res) => {
    res.send = (str) => {
        res.writeHead(200, { "Content-Type": "text/html;charset=utf8" });
        res.end(str);
    }
    let urlparse = url.parse(req.url, true);
    let pathname = urlparse.pathname;
    let method = req.method.toLowerCase();
    if (pathname != "/favicon.ico") {
        //检测该路由存在
        if (obj["_" + method][pathname]) {
            //判断路由是get还是post
            if (method == "get") {
                req.query = urlparse.query;
                obj["_" + method][pathname](req, res);
            }
            else {
                let info = "/?";
                req.on("data", (s) => {
                    info += s;
                })
                req.on("end", () => {
                    req.body = url.parse(info, true).query;
                    obj["_" + method][pathname](req, res);
                })
            }
        }
        //该路由不存在  直接end
        else {
            res.send("无该路由")
        }

    }
}
app.get = (string, callback) => {
    obj._get[string] = callback;
};
app.post = (string, callback) => {
    obj._post[string] = callback;
};
module.exports = app;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值