nodejs的一些核心概念

来源:互联网 发布:h3c网络工程师证书知乎 编辑:程序博客网 时间:2024/06/05 05:39

本文参考这里

1. 事件循环

这里写图片描述

java的同步执行模式:

System.out.println("Step: 1");System.out.println("Step: 2");Thread.sleep(1000);System.out.println("Step: 3");

顺序执行1.2.3,并在输出3之前停顿1s。
对异步执行的node来说:

console.log('Step: 1')setTimeout(function () {  console.log('Step: 3')}, 1000)console.log('Step: 2')

不会停顿1s,输出顺序也不会只1,3,2.
setTimeout函数将其回调函数在1s之后放入事件队列上,供时间循环遍历。所以从时间队列遍历的顺序上看,执行顺序就是1,2,3.

2. 全局对象global

  • global.process:保存了一些process、system和环境相关的一些信息。
  • global.__filename:当前执行脚本的文件名和路径。
  • global.__dirname: 当前执行脚本的绝对路径。
  • global.module: 当前模块导出的对象。
  • global.require():导出模块的方法。

3. process对象

process对象保存了一些当前进程的一些信息:
* process.pid:进程id
* process.versions:node\v8和其他组建的版本号
* process.arch:系统架构
* process.argv:命令行接口参数
* process.env:环境变量
* process.uptime():获取uptime
* process.memoryUsage():获取占用内存
* process.cwd():获取当前工作目录
* process.exit():结束当前进程
* process.on():添加监听事件

4. 时间触发器

node中的时间触发器这么用:

var events = require('events');var emitter = new events.EventEmitter();emitter.on('knock', function() {  console.log('Who\'s there?')})emitter.on('knock', function() {  console.log('Go away!')})emitter.emit('knock')

编写一个使用EventEmitter的job模块:

// job.jsvar util = require('util')var Job = function Job() {  var job = this   // ...  job.process = function() {    // ...    job.emit('done', { completedOn: new Date() })  }}util.inherits(Job, require('events').EventEmitter)module.exports = Job

使用job模块:

// weekly.jsvar Job = require('./job.js')var job = new Job()job.on('done', function(details){  console.log('Job was completed at', details.completedOn)  job.removeAllListeners()})job.process()

5. Streams流式读写

用node处理大数据时候会出现些问题。首先,速度降低,buffer限制在1Gb。再说了,如果有些读写资源一直持续,并不停止,node怎么工作?
流式读写可以解决这些问题。
先看一下标准的buffer读写机制:

这里写图片描述

在进一步读取或者output之前,我们必须先等待buffer加载完所有的数据。相比之下,使用流式读取机制是这样的:

这里写图片描述

node中有4中流:
* Readable:可读
* Writable:可写
* Duplex:双工,可读可写
* Transform:转换数据

流式读写隐藏在node的各个地方:
* HTTP requests and responses
* Standard input/output
* File reads and writes

6. 可读流例子

标准输入流process.stdin就是一个可读流。

process.stdin.resume()process.stdin.setEncoding('utf8')process.stdin.on('data', function (chunk) {  console.log('chunk: ', chunk)})process.stdin.on('end', function () {  console.log('--- END ---')})

可读流有一个read()方法用来同步读取数据块。通常使用while(null != (chunk = readble.read())来循环读取数据:

var readable = getReadableStreamSomehow()readable.on('readable', () => {  var chunk  while (null !== (chunk = readable.read())) {    console.log('got %d bytes of data', chunk.length)  }})

7. 可写流例子

使用write()方法往可写流中写入数据,process.stdout就是一个可写流:

process.stdout.write('hello world');

8. 管道

下面的例子使用pipe()方法将一个文件读取后压缩,再写入压缩文件中:

var readable = getReadableStreamSomehow()readable.on('readable', () => {  var chunk  while (null !== (chunk = readable.read())) {    console.log('got %d bytes of data', chunk.length)  }})

9. http流

http的请求和相应既是可读可写的流,也是event emitter。可以给请求添加data事件:

const http = require('http')var server = http.createServer( (req, res) => {  var body = ''  req.setEncoding('utf8')  req.on('data', (chunk) => {    body += chunk  })  req.on('end', () => {      var data = JSON.parse(body)    res.write(typeof data)    res.end()  })})server.listen(1337)

如果server端需要返回大数据,可以使用流式返回:

app.get('/non-stream', function(req, res) {  var file = fs.readFile(largeImagePath, function(error, data){    res.end(data)  })})app.get('/stream', function(req, res) {  var stream = fs.createReadStream(largeImagePath)  stream.pipe(res)})

10. buffers

浏览器端的javascript没有用来处理二进制数据的数据类型。但是node中有个buffer的数据类型可以用来处理二进制数据。
创建二进制数据类型可以这样:
* new Buffer(size)
* new Buffer(array)
* new Buffer(buffer)
* new Buffer(str[, encoding])
二进制数据令人看起来很费解,通常使用toString()方法转换成人读格式。

let buf = new Buffer(26)for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97 // 97 is ASCII a}console.log(buf) // <Buffer 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a>buf.toString('utf8') // outputs: abcdefghijklmnopqrstuvwxyzbuf.toString('ascii') // outputs: abcdefghijklmnopqrstuvwxyzbuf.toString('ascii', 0, 5) // outputs: abcdebuf.toString('utf8', 0, 5) // outputs: abcdebuf.toString(undefined, 0, 5) // encoding defaults to 'utf8', outputs abcde

fs模块读出的数据类型也是buffer类型的:

fs.readFile('/etc/passwd', function (err, data) {  if (err) return console.error(err)  console.log(data)});

11. clusters

javascript单线程利用不了多核cpu,但是node可以使用cluster模块充分利用多核cpu。
使用cluster模块的套路通常是这样的:

// cluster.jsvar cluster = require('cluster')if (cluster.isMaster) {  for (var i = 0; i < numCPUs; i++) {    cluster.fork()  }} else if (cluster.isWorker) {  // your server code})

12. pm2工具

pm2工具可以充分利用多核,并且不用像上面使用cluster模块那样写那么恶心的代码。
下面写一个express server,使用pm2工具可以在不改变代码的情况下利用多核cpu。

var express = require('express')var port = 3000global.stats = {}console.log('worker (%s) is now listening to http://localhost:%s', process.pid, port)var app = express()app.get('*', function(req, res) {  if (!global.stats[process.pid]) global.stats[process.pid] = 1  else global.stats[process.pid] += 1;  var l ='cluser '    + process.pid    + ' responded \n';  console.log(l, global.stats)  res.status(200).send(l)})app.listen(port)

使用pm2工具启动server:

pm2 start server.js -i 0

13. spawn vs fork vs exec

cluster模块使用fork()方法穿件新的node server实例,其实node中有3中方法穿件新的外部进程:spawn(), fork(), exec()。3种方法均来自于child_process模块。他们的不同之处总结起来如下:
* require('child_process').spawn():用来处理大数据,支持流式,支持任何命令,不会创建新的V8实例。
* require('child_process').fork():创建一个新的V8实例,包含多个workers线程,仅仅支持node命令。
* require('child_process').exec():使用buffer,所以不适合处理大数据,通过异步方式在回调函数中一次性处理数据。支持所有命令。
下面看一下使用spawn来执行node program.js的例子:

var fs = require('fs')var process = require('child_process')var p = process.spawn('node', 'program.js')p.stdout.on('data', function(data)) {  console.log('stdout: ' + data)})

fork()的语法与spawn()类似,fork()进程执行的都是nodejs程序,所以fork()参数中不需要加node命令:

var fs = require('fs')var process = require('child_process')var p = process.fork('program.js')p.stdout.on('data', function(data)) {  console.log('stdout: ' + data)})

exec()不支持data事件,只能回调:

var fs = require('fs')var process = require('child_process')var p = process.exec('node program.js', function (error, stdout, stderr) {  if (error) console.log(error.code)})

14. 异步错误处理

说到错误处理,nodejs和其他大部分语言都有try/catch语句。同步编程时候,使用try/catch很爽:

try {  throw new Error('Fail!')} catch (e) {  console.log('Custom Error: ' + e.message)}

node的异步机制导致错误发生时的上下文丢失,从而无法捕捉error。所以使用node编写程序时候,回调函数的第一个参数往往是error参数,用来记录发生的错误,每次执行回调时候,开发者需要先检查一下error,并将error冒泡(将error传递给下一个回掉函数):

  if (error) return callback(error)  //  or  if (error) return console.error(error)  ```  使用domain模块。将异常代码放入domain模块的run方法中,异常抛出后,程序不会崩溃,并且还能得到友好的错误提示。```javascript// domain-async.js:var d = require('domain').create()d.on('error', function(e) {   console.log('Custom Error: ' + e)})d.run(function() {  setTimeout(function () {    throw new Error('Failed!')  }, Math.round(Math.random()*100))});
0 0
原创粉丝点击