【Node】Node.js 入门知识点总结(一)

一、Node.js 回调函数

  • Node.js 异步编程的直接体现就是回调函数
  • Node 所有 API 都支持回调函数。
  • 回调函数一般作为函数的最后一个参数出现。

例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

二、Node.js 事件循环

  • Node.js 是单进程单线程应用程序。
  • Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。
  • Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数
  • Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
  • Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类绑定和监听事件,如下实例:
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();


// 绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);


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

三、Node.js EventEmitter

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

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

EventEmitter 类

  • events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发事件监听器功能的封装。
  • 当添加新的监听器时,newListener 事件会触发,当监听器被移除时,removeListener 事件被触发。
  • 对于每个事件,EventEmitter 支持 若干个事件监听器。当事件触发时,注册到这个事件的事件监听器被依次调用事件参数作为回调函数参数传递
  • EventEmitter 提供了多个属性,如 on 和 emiton 函数用于绑定事件函数,emit 属性用于触发一个事件。
  • error事件:EventEmitter 定义了一个特殊的事件 error。我们一般要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。例如:emitter.emit('error'); 
  • 继承EventEmitter:包括fs、net、 http在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它原因有两点:首先,具有某个实体功能的对象实现事件符合语义, 事件的监听和发生应该是一个对象的方法。其次 ,JavaScript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。

四、Node.js Buffer(缓冲区)

  • JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像TCP流文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer类,该类用来创建一个专门存放二进制数据的缓存区
  • 一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存
  • Node.js 目前支持的字符编码包括:ascii、utf8、utf16le(ucs2)、base64、latin1(binary)、hex。
const buf = Buffer.from('runoob', 'ascii');

// 输出 72756e6f6f62
console.log(buf.toString('hex'));

// 输出 cnVub29i 
console.log(buf.toString('base64'));
描述方法
创建 Buffer 类

常见的有 Buffer.alloc(size[, fill[, encoding]])、Buffer.from(array)、Buffer.from(buffer) 、Buffer.from(string[, encoding])。更多详情见菜鸟教程【Buffer】

写入缓冲区buf.write(string[, offset[, length]][, encoding])。返回值:返回实际写入的大小。如果 buffer 空间不足, 则只会写入部分字符串。
从缓冲区读取数据buf.toString([encoding[, start[, end]]])。返回值:解码缓冲区数据并使用指定的编码返回字符串。

将 Buffer 转换为 JSON 对象

buf.toJSON()。当字符串化一个 Buffer 实例时,JSON.stringify() 会隐式地调用该 toJSON()。

缓冲区合并

Buffer.concat(list[, totalLength])。返回值:返回一个多个成员合并的新 Buffer 对象。

缓冲区比较

buf.compare(otherBuffer)。返回值:返回一个数字,表示 buf 在 otherBuffer 之前(<0),之后(>0)或相同(=0)。

拷贝缓冲区

buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])。没有返回值。

缓冲区裁剪

buf.slice([start[, end]])。返回值:返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪切。

缓冲区长度

buf.length。返回值:返回 Buffer 对象所占据的内存长度。
其他方法buf[index]、buf.equals(otherBuffer)、buf.fill(value[, offset][, end])

五、Node.js Stream(流)

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

2. Stream 有四种流类型:Readable - 可读操作、Writable - 可写操作、Duplex - 可读可写操作、Transform - 操作被写入数据,然后读出结果。

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

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

4. 从流中读取数据

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

// 创建可读流
var readerStream = fs.createReadStream('input.txt');

// 设置编码为 utf8。
readerStream.setEncoding('UTF8');

// 处理流事件 --> data, end, and error
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("程序执行完毕");

5.  写入流

var fs = require("fs");
var data = '菜鸟教程官网地址:www.runoob.com';

// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('output.txt');

// 使用 utf8 编码写入数据
writerStream.write(data,'UTF8');

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

// 处理流事件 --> data, end, finish, and error
writerStream.on('finish', function() {
    console.log("写入完成。");
});

writerStream.on('error', function(err){
   console.log(err.stack);
});

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

6. 管道流

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

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("程序执行完毕");

7. 链式流

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

接下来我们就是用管道和链式来压缩和解压文件。

 创建 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');

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

六、Node.js 模块系统

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

2. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。

3. Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

创建 hello.js 文件,代码如下:

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

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

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

有时候我们只是想把一个对象封装到模块中(module),例如:

//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('BYVoid'); 
hello.sayHello(); 

注:exports 和 module.exports 的使用

如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似class,包含了很多属性和方法),就用 module.exports

4. Node.js 的 require 方法中的文件查找策略如下:

Node.js 中存在 4 类模块(原生模块和3种文件模块),原生模块优先级更高。

七、Node.js 函数

在JavaScript中,一个函数可以作为另一个函数的参数。Node.js中函数的使用与Javascript类似。

1)先定义,再传递

function say(word) {
  console.log(word);
}

function execute(someFunction, value) {
  someFunction(value);
}

execute(say, "Hello");
var http = require("http");

function onRequest(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}

http.createServer(onRequest).listen(8888);

2)匿名函数

function execute(someFunction, value) {
  someFunction(value);
}

execute(function(word){ console.log(word) }, "Hello");
var http = require("http");

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

八、Node.js 路由

我们要为路由提供请求的 URL 和其他需要的 GET 及 POST 参数,随后路由需要根据这些数据来执行相应的代码。

我们需要的所有数据都会包含在 request 对象中,该对象作为 onRequest() 回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的 Node.JS 模块,它们分别是 url 和 querystring 模块

server.js 文件代码如下:

var http = require("http");
var url = require("url");
 
function start(route) {
  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;

 router.js 文件代码如下:

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

同时,我们会相应扩展 index.js,使得路由函数可以被注入到服务器中:

var server = require("./server");
var router = require("./router");
 
server.start(router.route);

九、Node.js 全局对象

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

1. 全局对象与全局变量

global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义,满足以下条件的变量是全局变量:

  • 在最外层定义的变量(在 Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文);
  • 全局对象的属性;
  • 隐式定义的变量(未定义直接赋值的变量)。

注意: 最好不要使用 var 定义变量以避免引入全局变量,因为全局变量会污染命名空间,提高代码的耦合风险。

2. 全局变量

__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。

__dirname 表示当前执行脚本所在的目录

3. 全局函数:setTimeout(cb, ms)、clearTimeout( t ) 、setInterval(cb, ms)、clearInterval(t)。

4. console对象

console 用于提供控制台标准输出,它是由 Internet Explorer 的 JScript 引擎提供的调试工具,后来逐渐成为浏览器的实施标准。

常见的console对象方法:console.log()、console.info()、console.error()、console.warn()、console.dir()、console.time()、console.timeEnd()、console.trace()、console.assert()。

console.info("程序开始执行:");

var counter = 10;
console.log("计数: %d", counter);

console.time("获取数据");
//
// 执行一些代码
// 
console.timeEnd('获取数据');

console.info("程序执行完毕。")
$ node main.js
程序开始执行:
计数: 10
获取数据: 0ms
程序执行完毕

5. process

process 是一个全局变量,即 global 对象的属性。

它用于描述当前 Node.js 进程状态的对象,提供了一个与操作系统的简单接口。

常用的process 对象成员方法:exit、beforeExit、uncaughtException、Signal 事件。

process.on('exit', function(code) {

  // 以下代码永远不会执行
  setTimeout(function() {
    console.log("该代码不会执行");
  }, 0);
  
  console.log('退出码为:', code);
});
console.log("程序执行结束");
$ node main.js
程序执行结束
退出码为: 0

process 属性

// 输出到终端
process.stdout.write("Hello World!" + "\n");

// 通过参数读取
process.argv.forEach(function(val, index, array) {
   console.log(index + ': ' + val);
});

// 获取执行路径
console.log(process.execPath);


// 平台信息
console.log(process.platform);
$ node main.js
Hello World!
0: node
1: /web/www/node/main.js
/usr/local/node/0.10.36/bin/node
darwin

process 方法

// 输出当前目录
console.log('当前目录: ' + process.cwd());

// 输出当前版本
console.log('当前版本: ' + process.version);

// 输出内存使用情况
console.log(process.memoryUsage());
$ node main.js
当前目录: /web/com/runoob/nodejs
当前版本: v0.10.36
{ rss: 12541952, heapTotal: 4083456, heapUsed: 2157056 }

 更多process的属性和方法见菜鸟教程:https://www.runoob.com/nodejs/nodejs-global-object.html

十、Node.js 常用工具

util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能过于精简的不足。

util.inherits(constructor, superConstructor) 是一个实现对象间原型继承的函数。特别注意,仅仅继承了原型。

util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换为字符串的方法,通常用于调试和错误输出

  • 它至少接受一个参数 object,即要转换的对象。
  • showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息。
  • depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多少。如果不指定depth,默认会递归2层,指定为 null 表示将不限递归层数完整遍历对象。
  • 如果 colors 值为 true,输出格式将会以 ANSI 颜色编码,通常用于在终端显示更漂亮的效果。
  • 特别要指出的是,util.inspect 并不会简单地直接把对象转换为字符串,即使该对象定义了 toString 方法也不会调用。

util.isArray(object):如果给定的参数 "object" 是一个数组(如:[]、new Array())返回true,否则返回false。

util.isRegExp(object):如果给定的参数 "object" 是一个正则表达式(如:/some regexp/、new RegExp("another regexp"))返回true,否则返回false。

util.isDate(object):如果给定的参数 "object" 是一个日期(如:new Date())返回true,否则返回false(如:Date()不行)。

util.isError(object):如果给定的参数 "object" 是一个错误对象(如:new Error()、new TypeError())返回true,否则返回false。

十一、Node.js 文件系统

描述方法
导入文件系统模块(fs)

var fs = require("fs")

读取文件内容异步的 fs.readFile(file, callback) 和同步的 fs.readFileSync(file, callback)
打开文件(异步模式)

fs.open(path, flags[, mode], callback)

其中,flags - 文件打开的行为,常见的有r/r+/rs/rs+/w/wx/w+/wx+/a/ax/a+/ax+。

mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。

callback - 回调函数,带有两个参数如:callback(err, fd)。

获取文件信息(异步模式)

fs.stat(path, callback)

其中,callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象。

stats类中的方法有:stats.isFile()/stats.isDirectory()/stats.isBlockDevice()/

stats.isCharacterDevice()/stats.isSymbolicLink()/stats.isFIFO()/vstats.isSocket()

写入文件(异步模式)

fs.writeFile(file, data[, options], callback)

其中,options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666,flag 为 'w'。

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

追加文件内容(异步模式)fs.appendFile(filename, data[, options], callback)
读取文件(异步模式)

fs.read(fd, buffer, offset, length, position, callback)

其中,fd - 通过 fs.open() 方法返回的文件描述符

buffer - 数据写入的缓冲区。

offset - 缓冲区写入的写入偏移量。

length - 要从文件中读取的字节数。

position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。

callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。

关闭文件(异步模式)

fs.close(fd, callback)

截取文件(异步模式)

fs.ftruncate(fd, len, callback)

其中,len - 文件内容截取的长度。

删除文件fs.unlink(path, callback)
  
创建目录

fs.mkdir(path[, options], callback)

其中,options 参数可以是:

recursive - 是否以递归的方式创建目录,默认为 false。

mode - 设置目录权限,默认为 0777。

读取目录

fs.readdir(path, callback)

其中,callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。

删除目录fs.rmdir(path, callback)

十二、Node.js GET/POST请求

1. 获取 GET 请求内容:

var http = require('http');
var url = require('url');
var util = require('util');
 
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
 
    // 解析 url 参数
    var params = url.parse(req.url, true).query;
    res.write("网站名:" + params.name);
    res.write("\n");
    res.write("网站 URL:" + params.url);
    res.end();
 
}).listen(3000);

2. 获取 POST 请求内容

var http = require('http');
var querystring = require('querystring');
 
var postHTML = 
  '<html><head><meta charset="utf-8"><title>菜鸟教程 Node.js 实例</title></head>' +
  '<body>' +
  '<form method="post">' +
  '网站名: <input name="name"><br>' +
  '网站 URL: <input name="url"><br>' +
  '<input type="submit">' +
  '</form>' +
  '</body></html>';
 
http.createServer(function (req, res) {
  var body = "";
  req.on('data', function (chunk) {
    body += chunk;
  });
  req.on('end', function () {
    // 解析参数
    body = querystring.parse(body); //在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
    // 设置响应头部信息及编码
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});
 
    if(body.name && body.url) { // 输出提交的数据
        res.write("网站名:" + body.name);
        res.write("<br>");
        res.write("网站 URL:" + body.url);
    } else {  // 输出表单
        res.write(postHTML);
    }
    res.end();
  });
}).listen(3000);

END

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值