Node.js 入门

Node.js 入门

常用库

接口信息来源于 nodejs.cn

module

在 Node.js 模块系统中,每个文件都被视为一个独立的模块。

Export Object

//test.js
exports.msg = 'Hello world';

//main.js
var test = require('./test.js');
console.log(test.msg);

Export Function

//test.js
module.exports = function (msg) { 
    console.log(msg);
};

//main.js
var msg = require('./test.js');
msg('Hello World');

Export function as a class

//test.js
module.exports = function (firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = function () { 
        return this.firstName + ' ' + this.lastName;
    }
}

//main.js
var person = require('./Person.js');
var person1 = new person('James', 'Bond');
console.log(person1.fullName());
Buffer

Buffer为Node.js操作二进制字节流的库。每单位为八位字节流

const buf1=Buffer.alloc(10)  //创建一个长度为10,零填充的Buffer

const buf2=Buffer.alloc(10,1)  //创建一个长度为10,0x01 填充的Buffer

const buf3=Buffer.from([1,2,3]) //创建一个[0x01,0x02,0x03]的Buffer

const buf4=Buffer.from('teststring')

const buf5=Buffer.from(buf4)  //创建一个buf4的拷贝

const buf6=Buffer.concat([buf1,buf2,buf3],totalLength)  //拼接Buffer,totalLength可不指定

const str1=buf4.toString('utf8', 0, 4)  //test
events

所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。 事件的命名通常是驼峰式的字符串。

eventEmitter.emit() 方法可以传任意数量的参数到监听器函数。

EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。

const EventEmitter = require('events');
const myEmitter=new EventEmitter();
myEmitter.on('event',(a,b)=>{
   console.log(`${a}+${b}=${a+b}`);
});
myEmitter.on('event',(a,b)=>{
   console.log(`${a}-${b}=${a-b}`);
});
myEmitter.emit('event',3,5);
// 3+5=8
// 3-5=-2

使用 eventEmitter.once() 可以注册最多可调用一次的监听器。 当事件被触发时,监听器会被注销,然后再调用。

const EventEmitter = require("events");
const myEmitter = new EventEmitter();
let m = 0;
myEmitter.once('event', () => {
  console.log(++m);
});
myEmitter.emit('event');
// 1
myEmitter.emit('event');
// 不触发
fs

fs 模块提供了一个 API,用于以模仿标准 POSIX 函数的方式与文件系统进行交互。

所有文件系统操作都具有同步和异步的形式。

异步的形式总是将完成回调作为其最后一个参数。 传给完成回调的参数取决于具体方法,但第一个参数始终预留用于异常。 如果操作成功完成,则第一个参数将为 nullundefined

使用异步的方法时无法保证顺序。 因此,以下的操作容易出错,因为 fs.stat() 操作可能在 fs.rename() 操作之前完成:

fs.rename('/tmp/hello', '/tmp/world', (err) => {
  if (err) throw err;
  console.log('重命名完成');
});
fs.stat('/tmp/world', (err, stats) => {
  if (err) throw err;
  console.log(`文件属性: ${JSON.stringify(stats)}`);
});

要正确地排序这些操作,则将 fs.stat() 调用移动到 fs.rename() 操作的回调中:

fs.rename('/tmp/hello', '/tmp/world', (err) => {
  if (err) throw err;
  fs.stat('/tmp/world', (err, stats) => {
    if (err) throw err;
    console.log(`文件属性: ${JSON.stringify(stats)}`);
  });
});
stream

流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。HTTP请求process.stdout都是流的实例。可以用fs.createWtireStream()等来创建文件流。

流有4种类型:可读流,可写流,可读可写流以及Transform流。

所有的流都是EventEmitter的实例。

const fs = require("fs");
const w = fs.createWriteStream("file.txt");
w.write("hello");
w.end(",world!");
//cat file.txt
//hello,world!

流与流之间可以用管道来推送数据。可以给可读流绑定多个可写流。

const fs = require('fs');
const r = fs.createReadStream('file.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);
http

一个基于流实现的http server

const http = require('http');

const server = http.createServer((req, res) => {
  // req 是一个 http.IncomingMessage 实例,它是可读流。
  // res 是一个 http.ServerResponse 实例,它是可写流。

  let body = '';
  // 接收数据为 utf8 字符串,
  // 如果没有设置字符编码,则会接收到 Buffer 对象。
  req.setEncoding('utf8');

  // 如果添加了监听器,则可读流会触发 'data' 事件。
  req.on('data', (chunk) => {
    body += chunk;
  });

  // 'end' 事件表明整个请求体已被接收。 
  req.on('end', () => {
    try {
      const data = JSON.parse(body);
      // 响应信息给用户。
      res.write(typeof data);
      res.end();
    } catch (er) {
      // json 解析失败。
      res.statusCode = 400;
      return res.end(`错误: ${er.message}`);
    }
  });
});

server.listen(1337);
// $ curl localhost:1337 -d "{}"
// object
// $ curl localhost:1337 -d "\"foo\""
// string
// $ curl localhost:1337 -d "not json"
// 错误: Unexpected token o in JSON at position 1
net

net 模块用于创建基于流的 TCP 或 IPC 的服务器与客户端。

一个监听8124端口并将输入发回给客户端的例子如下

const net = require("net");
const server = net.createServer(c => {
  console.log("客户端已连接");
  c.on("end", () => {
    console.log("客户端已断开连接");
  });
  c.write("你好\r\n");
  c.pipe(c);
});
server.on("error", err => {
  throw err;
});
server.listen(8124, () => {
  console.log("服务器已启动");
});
url

url 模块用于处理与解析 URL。

WHATWG标准的新API可直接按如下方式使用

const url=new URL('https://www.google.com/search?q=test&oq=test&sourceid=chrome')
console.log(url)
/* URL {
 * href:
 * 'https://www.google.com/search?q=test&oq=test&sourceid=chrome',
 * origin: 'https://www.google.com',
 * protocol: 'https:',
 * username: '',
 * password: '',
 * host: 'www.google.com',
 * hostname: 'www.google.com',
 * port: '',
 * pathname: '/search',
 * search: '?q=test&oq=test&sourceid=chrome',
 * searchParams:
 * URLSearchParams { 'q' => 'test', 'oq' => 'test', 'sourceid' => 'chrome' },
 * hash: '' }
*/

异步与事件循环

Node.js中大部分I/O操作为异步操作,处理I/O的操作结果等可通过回调函数来实现,但这样会导致代码可读性变差,异常难以排查等后果。因此可以考虑Promise,async等库来进行异步处理。

下面主要介绍异步的执行顺序。

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  baz()
}

foo()

// output:
// foo
// baz
// bar

按照直观上的执行顺序来说,bar应该是立即执行的,在这种理解下输出顺序应该为foo,bar,baz。但结果并不是这样。

造成这样结果的原因是由于Node.js的异步是基于消息队列实现的。

当程序执行至setTimeout时,会将其交给定时器触发,由于延时为0,因此立即触发,并将其置入消息队列中,然后继续向下执行。遇到baz。这时还没有输出bar。

当执行栈为空时,这是从消息队列中取出一条任务加入执行栈并执行,这时才会输出bar。

也就是说,回调的执行需要等执行栈为空时,才会从消息队列中将其取出并执行。

但是当setTimeout与Promise一起使用时,又会出现新的问题。


console.log("A");

setTimeout(() => {
  console.log("A - setTimeout");
}, 0);

new Promise((resolve) => {
  resolve();
})
.then(() => {
  return console.log("A - Promise 1");
})
.then(() => {
  return console.log("B - Promise 1");
});

new Promise((resolve) => {
  resolve();
})
.then(() => {
  return console.log("A - Promise 2");
})
.then(() => {
  return console.log("B - Promise 2");
})
.then(() => {
  return console.log("C - Promise 2");
});

console.log("AA");

// A
// AA 
// A - Promise 1
// A - Promise 2
// B - Promise 1
// B - Promise 2
// C - Promise 2
// A - setTimeout

setTimeoue先入队,却后输出。

造成这样的原因是由于他们入的是不同的队列,他们的优先级不同。(实际的情况可以参考阮一峰的文章 )

具体的优先级对比可参考下面的代码。

setImmediate(function() {
  console.log(1);
}, 0);
setTimeout(function() {
  console.log(2);
}, 0);
new Promise(function(resolve) {
  console.log(3);
  resolve();
  console.log(4);
}).then(function() {
  console.log(5);
});
console.log(6);
process.nextTick(function() {
  console.log(7);
});
console.log(8);
// 3
// 4
// 6
// 8
// 7
// 5
// 2
// 1
//process.nextTick > promise.then > setTimeout > setImmediate;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值