Node.js 详细教程总结(上)

前言:Node.js 是运行在服务器端的 JavaScript,基于 Chrome JavaScript 运行是建立的一个平台,Node.js是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎,V8引擎执行Javascript的速度非常快,性能非常好;作为一个后端程序猿,如果想要部署一些高性能的服务, Node.js 还是要了解了解的

安装

在官网上找到安装压缩包或者.msi 文件,一路 next 就行了,这两者区别就是压缩包解压之后需要配置环境变量,.msi 不需要,直接在注册表那里以及自动配置好环境变量,一条龙服务的

Node.js 的应用

步骤

  1. 引入 required 模块
  2. 创建服务器
var http = require('http'); //载入http模块
// 使用 createServer 方法创建服务器,使用 listen 绑定
http.createServer(function (request, response) {
    // 发送 HTTP 头部 
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, {'Content-Type': 'text/plain'});
    // 发送响应数据 "Hello World"
    response.end('Hello World\n');
}).listen(8888);

// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');

把这个文件保存在 node文件下中的更目录下

然后 node server.js 运行

在这里插入图片描述

在浏览器就可以访问到了 hello world

在这里插入图片描述

分析 Node.js 的 HTTP 服务器:

第一行请求(require)Node.js 自带的 http 模块,并且把它赋值给 http 变量,接下来我们调用 http 模块提供的函数: createServer

这个函数会返回 一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数, 指定这个 HTTP 服务器监听的端口号

NPM 使用介绍

随同 node.js 一起安装的包管理工具,能解决 NodeJS 代码部署上的问题

常见使用场景

  1. 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用
  2. 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用
  3. 允许用户将自己编写的包或者命令行上传到 NPM 服务器供别人使用

使用 npm 命令安装 express

Node.js REPL 交互式解释器

Node 自带交互式解释器,可以执行,读取用户输入,执行数据结构,打印输出结果,循环以上操作

启动 node 终端,在 cmd 命令键入 node
在这里插入图片描述
也可以支持循环
在这里插入图片描述

回调函数

阻塞和非阻塞案例

阻塞:创建一个 input.txt 内容如下

Node.js 回调函数教程

创建一个 main.js 文件,代码如下

var fs = require("fs");
var data = fs.readFileSync('input.txt'); //代码会在这里阻塞

console.log(data.toString());
console.log("程序执行结束!");

执行结果如下
在这里插入图片描述
非阻塞案例

main2.js 文件代码

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
    //如果读取错误,则抛出异常
    if (err) {
        return console.error(err);
    }
    //如果正确读取,则输出内容
    console.log(data.toString());
});

console.log("程序执行结束!");

输出如下

在这里插入图片描述
可以看到,代码并没有在读取的时候阻塞,而是直接往下执行了,如果读取成功打印,读取失败则抛出异常

总结:阻塞是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内

Node.js 事件循环

Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供异步的执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高

Node.js 中的每一个 api 几乎都是支持回调函数,所以基本上所有的事件机制都是用观察者模型实现

Node.js 单线程类似进入一个while(True) 的事件循环,直到没有事件观察者退出,每个异步事件都生产一个事件观察者,如果有事件发生就调用该回调函数

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

//创建事件处理程序
var connectHandler = function connected() {
    console.log('连接成功!');
    //触发 data_received
    eventEmitter.emit('data_received');
}
//绑定 connection 事件处理程序
eventEmitter.on('connection',connectHandler);
//使用匿名函数绑定 data_received 事件
eventEmitter.on('data_received',function () {
    console.log('数据连接成功!');
})
//触发 connection 事件
eventEmitter.emit('connection');
console.log('程序执行完毕!');

执行如下
在这里插入图片描述

Node.js EventEmitter

Node.js 所有的异步 IO 操作在完成时都会发送一个事件到事件队列,Node.js 里面的许多对象都会分发事件:

  • 一个 net.Server 对象会在每次有新连接时触发一个事件
  • 一个 fs.readStream 对象会在文件被打开的时候触发一个事件

所有这些产生事件的对象都是 events.EventEmitter 的实例

EventEmitter 对象如果在实例化时发生错误,会触发 error 事件,当添加新的监听器时,newListener 事件会被触发,当监听器被移除时,removeListener 会被触发

案例:1s 后会触发事件

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
// 绑定 some_event 事件
event.on('some_event',function () {
    console.log('some_event 事件触发');
})
// 1000ms 后触发事件
setTimeout(function(){
    event.emit('some_event');
}, 1000);

案例 2:EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持 若干个事件监听器。

当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递

var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('some_Event',function (arg1,arg2) {
    console.log('listen1',arg1,arg2);
});
emitter.on('some_Event',function (arg1,arg2) {
    console.log('listen2',arg1,arg2) ;
});

emitter.emit('some_Event','arg1参数','arg2参数');

案例 3:

var events = require('events');
var eventEmitter = new events.EventEmitter();

//监听器1
var listener1 = function listener1() {
    console.log('监听器 listen1 执行!');
}

//监听器2
var listener2 = function listener2() {
    console.log('监听器 listen2 执行!');
}
//绑定 connection 事件,处理函数为 listener1,listener2
eventEmitter.addListener('connection',listener1);
eventEmitter.on('connection',listener2);

var eventListeners = eventEmitter.listenerCount('connection');
console.log(eventListeners + '个监听器监听连接事件!');

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

//移除监绑定的 listener1 函数
eventEmitter.removeListener('connection',listener1);
console.log('listener1 不被监听!');

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

eventListeners = eventEmitter.listenerCount('connection');
console.log(eventListeners + '个监听器监听连接事件!');
console.log("程序执行完毕!");
$ node main.js
2 个监听器监听连接事件!
监听器 listener1 执行!
监听器 listener2 执行!
listener1 不再受监听!
监听器 listener2 执行!
1 个监听器监听连接事件!
程序执行完毕!

Error 事件

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

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

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

Node.js Buffer

JavaScript 语言自身只有字符串数据类型,没有二进制数据类型,但是在处理 TCP 流或者二进制流,必须使用到二进制数据,因此在 Node.js 中定义一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓冲区

在 Node.js 中buffer 类是随内核一起发布的核心库,Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 node.js 中处理 IO 操作移动数据时,就有可能使用 buffer ,原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存

Buffer 与字符编码

Buffer 实例一般用于表示编码字符的序列,比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码,就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换

const buf = Buffer.from('runnob','ascii');
console.log(buf.toString('hex'));
console.log(buf.toString('base64'));
//72756e6e6f62  cnVubm9i

创建 buffer 类

//创建一个长度为 10 ,且用 0 填充的 Buffer
const buf1 = Buffer.alloc(10);
//创建一个长度为10,且用 0x1 填充的 Buffer
const buf2 = Buffer.alloc(10,1);
//创建一个长度为10,但是未被初始化的 Buffer
//这个方法比调用 alloc 快,但是返回的 Buffer 实例可能包含
//旧数据,需要使用 fill 或者 writer 重写
const buf3 = Buffer.allocUnsafe(10);
//创建一个包含 [0x1,0x2,0x3]的buffer
const buf4 = Buffer.from([1,2,3]);
// 创建一个包含 utf-8 字节[0x74,0xc3,0xa9,0x73,0x74] 的buffer
const buf5 = Buffer.from('test');
//创建一个包含 Latin-1 字节[0x74,0xe9,0x73,0x74]的buffer
const buf6 = Buffer.from('test','latin1');

写入缓冲区

buf = Buffer.alloc(256);
len = buf.write('Node.js jiaocheng');
console.log("写入字节数:"+len); //17

从缓冲区中读取数据

实例

buf = Buffer.alloc(26)
for(var i = 0;i<26;++i){
    buf[i] = i +  97;
}
console.log(buf.toString('ascii'));
console.log(buf.toString('ascii',0,5));
console.log(buf.toString('utf8',0,5));
console.log(buf.toString(undefined,0,5));

输出结果为

abcdefghijklmnopqrstuvwxyz
abcde
abcde
abcde

将 Buffer 对象转换为 Json 对象

const buf = Buffer.from([0x1,0x2,0x3,0x4,0x5]);
const json = JSON.stringify(buf)

console.log(json)
//输出 {"type":"Buffer","data":[1,2,3,4,5]} 

const copy = JSON.parse(json,(key,value)=>{
    return value && value.type == 'Buffer'? 
        Buffer.from(value.data):value;
});

console.log(copy);
//输出 <Buffer 01 02 03 04 05>

缓冲区合并

实例

var buffer1 = Buffer.from('Node.js 教程');
var buffer2 = Buffer.from('I like study');

// 返回一个多个成员合并的新 buffer 对象
var buffer3 = Buffer.concat([buffer1,buffer2]);
console.log('buffer3内容:'+buffer3.toString());
//输出 Node.js 教程I like study

缓冲区的比较

var buffer1 = Buffer.from('ABC')
var buffer2 = Buffer.from('ABCD');
var result = buffer1.compare(buffer2);
if (result < 0){
    console.log(buffer1 + '在' + buffer2 + '之前');
}
else if(result > 0){
    console.log(buffer1 + '在' + buffer2 + '之后');
}
else{
    console.log(buffer1 + '在' + buffer2 + '相同');
}

拷贝缓冲区

var buf1 = Buffer.from('abcdefghijkl');
var buf2 = Buffer.from('Runnob');

//将 buf2 插入到 buf1 指定的位置上
buf2.copy(buf1,2);
console.log(buf1.toString()); // abRunnobijkl

裁剪缓冲区

//裁剪缓冲区
var buffer1 = Buffer.from('Runnob');
var buffer2 = buffer1.slice(0,2);
console.log(buffer2.toString());

Node.js Stream 流

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

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

所有的 stream 对象都是 EventEmitter 的实例,常用的事件有

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

从流中读取数据

var fs = require('fs');
var data = '';
//创建可读流
var readerStream = fs.createReadStream('input.txt');
//设置编码
readerStream.setEncoding('utf8');

//处理流事件
readerStream.on('data',function (chunk) {
    data += chunk; 
});
readerStream.on('end',function () {
    console.log(data);
});
readerStream.on('error',function (err) {
    console.log(err.stack);
});
console.log('执行完毕');

写入流

var fs = require('fs');
var data = "Node.js Stream 流教程";

//创建一个可写入的流
var writerStream = fs.createWriteStream('output.txt');
//使用 utf-8编码写入数据
writerStream.write(data,'utf8');

//标记文件末尾
writerStream.end();

writerStream.on('finish',function () {
    console.log('写入完成!');
});

writerStream.on('error',function (err) {
    console.log(err.stack);
});
console.log('程序执行完毕!');

管道流:管道提供了一个输出流到到输入流的机制,通常我们用于从一个流中获取数据传递到另外一个流中

在这里插入图片描述

实例:读取一个文件的内容,写入到另外一个文件中

var fs = require('fs');
//创建一个可读流
var readerStream = fs.createReadStream('input.txt');
//创建一个可写流
var writerStream = fs.createWriteStream('output.txt');

//管道读写操作
//读取 input.txt 文件内容,并将内容写入到 output.txt
readerStream.pipe(writerStream);
console.log('程序执行完毕!');

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

创建 compress.js 文件

var fs = require("fs");
var zlib = require('zlib');

// 压缩 input.txt 文件为 input.txt.gz
fs.createReadStream('input.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('input.txt.gz'));
  
console.log("文件压缩完成");

创建 decompress.js 文件

var fs = require('fs');
var zlib = require('zlib');
//解压
fs.createReadStream('input.txt.gz')
    .pipe(zlib.createGunzip())
    .pipe(fs.createWriteStream('input2.txt'));
console.log('文件解压完成!');

Node.js 模块系统

为了让Node.js 的文件可以互相调用,Node.js 提供了一个简单的模块系统

模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的,所以一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码,JSON 后者 可编译的 C/C++ 编译

引入模块

在 Node.js 中,引入一个模块非常简单,例如创建一个 main.js 文件引入 hello 模块

var hello = require('./hello');
hello.world();

创建 hello 模块

exports.world = function () {
    console.log('Hello World');
}

把对象封装到模块中

hello.js 文件

function Hello() {
    var name;
    this.setName = function(thyName) {
        name = thyName; 
    };
    this.sayHello = function() {
        console.log('Hello '+name);
    };
}
module.exports = Hello;

main.js 主文件

var hello = require('./hello');
hello = new Hello();
hello.setName('MaZe');
hello.sayHello();

require 加载模块的过程

在这里插入图片描述
优先级:文件模块-》原生模块-》查找文件模块,根据扩展名载入

Node.js 函数

函数名可以作为另一个函数的参数进行传递

function say(hello){
    console.log(hello);
}
function excute(someFunction,value) {
    someFunction(value);
}
excute(say,"Hello MaZe");

匿名函数

function excute(someFunction,value) {
    someFunction(value);
}
excute(function (word) {
    console.log(word)
},"hello");

函数传递是如何让 http 服务器工作的

var http = require("http");

// 写法一:
http.createServer(function (request,response) {
    response.writeHead(200,{
        "Content-Type":"text/plain"
    });
    response.write("Hello World");
    response.end();
}).listen(8887);

//写法二:
function onRequest(request,response) {
    response.writeHead(200,{"Content-Type":"text/plain"});
    response.write('Hello World');
    response.end();
};
http.createServer(onRequest).listen(8888);

Node.js 路由

我们要为路由提供请求的 url 和其它需要的 get 以及 post 参数,随后路由器根据这些数据来执行相应的代码

因此我们需要查看 http 请求,从中提取出 url 以及 get/post 参数,这一功能我们暂定作为服务器的功能

首先我们定义 onRequest ,用来找出浏览器的 url 路径

server.js 文件

var http = require("http");
var url = require("url");
const { route } = require("./root");

function start() {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");

        route(pathname);

        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }
    http.createServer(onRequest).listen(8888);
    console.log("Server has started.");
}
exports.start = start;

编写路由 root.js 文件

function route(pathname) {
    console.log("About to route a request for"+pathname);
}
exports.route = route;

这个时候把路由函数注册到服务器上

var server = require("./server");
var router = require("./root");

server.start(router.route);

随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿的温柔香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值