NODEJS

241 阅读11分钟

Nodejs的核心组成

  1. Natives modules 内置核心模块
  • 当前层内容由JS实现
  • 提供应用程序可直接调用库,例如fs, path, http等
  • JS语言无法直接操作底层硬件设施之
  1. Builtin modules 胶水层
  • c/c++编写
  1. 底层
  • V8: 执行JS代码,提供桥梁接口
  • Libuv: 事件循环、事件队列、异步IO
  • 第三方模块: zlib、http、c-cares、http parser等 事件驱动
const EventEmitter = require('events')

const myEvent = new EventEmitter()

myEvent.on('event1', () => {
  console.log('event1执行了...')
})
myEvent.on('event1', () => {
  console.log('event11111执行了...')
})
myEvent.emit('event1')

单线程

const http = require('http')
function sleepTime(time) {
  const sleep = Date.now() + time * 1000
  while (Date.now() < sleep) { }
  return
}
sleepTime(4)
const server = http.createServer((res, req) => {
  res.end('server starting...')
})
server.listen(8080, () => {
  console.log('服务启动了...')
})

nodejs实现API服务

  • ts-node
  • tsconfig.json
  • express
  • @types/express

全局对象 global

  • 全局对象可以看做是全局变量的宿主
  • 默认情况this是空对象,和global并不是一样的

全局变量

  • __filename: 返回正在执行脚本文件的绝对路径
  • __dirname: 返回正在执行脚本所在的目录(不包含当前文件名)
  • timer类函数:执行顺序与事件循环间的关系
  • process: 提供与当前进程互动的接口
  • require: 实现模块的加载
  • module、exports: 处理模块的导出

process

  1. 资源: CPU 内存
  • process.memoryUsage() 内存信息
  • process.cpuUsage() CPU信息
  1. 运行环境:运行目录、node环境、cpu架构、用户环境、系统平台
  • process.cwd() 运行目录
  • process.version node版本
  • process.versions 各种版本
  • process.arch x64
  • process.env.NODE_ENV 环境
  • process.env.PATH 系统环境变量
  • process.env.USERPROFILE C:\Users\admin 管理员目录(windows平台,Mac平台用HOME)
  • process.platform win32
  1. 运行状态:启动参数、PID、运行时间
  • process.argv [node.exe路径、当前文件路径、参数1、参数2...]
  • process.argv0 只有argv0
  • process.pid/ppid
  • process.uptime() 运行开始到结束消耗的时间
  1. 事件
process.on('exit', (code) => {
  //只能写入同步代码
  console.log('exit ' + code)
})

process.on('beforeExit', (code) => {
  //可以写异步代码
  console.log('beforeExit ' + code)
})
console.log('代码执行完了')

//执行结果
代码执行完了
beforeExit 0
exit 0

process.exit()

  1. 标准输出、输入、错误
console.log = function(data){
  process.stdout.write('----' + data + '\n')
}
console.log(1)
console.log(11)
//读出文件的内容
const fs = require('fs')

fs.createReadStream('test.txt')
  .pipe(process.stdout)
process.stdin.pipe(process.stdout)
process.stdin.on('readable', () => {
  let chunk = process.stdin.read()
  if(chunk !== null){
    process.stdout.write('data ' + chunk)
  }
})

Buffer

Buffer让JavaScript可以操作二进制

  • 无需require的一个全局变量
  • 实现Nodejs平台下的二进制数据操作
  • 不占据V8堆内存大小的内存空间
  • 内存的使用由Node来控制,由V8的GC回收
  • 一般配合Stream流使用,充当数据缓冲区
创建BUffer实例
  • alloc: 创建指定字节大小的buffer
  • allocUnsafe: 创建指定大小的buffer(不安全)
  • from: 接收数据,创建buffer
const b1 = Buffer.alloc(10)
const b2 = Buffer.allocUnsafe(13)
console.log(b1)
console.log(b2)
const b1 = Buffer.from('1')
console.log(b1)

const b2 = Buffer.from([0xe4, 0xb8, 0xad])
console.log(b2)
console.log(b2.toString())  //中

const b3 = Buffer.alloc(3)
const b4 = Buffer.from(b3)

console.log(b3)
console.log(b4)
b3[0] = 1   //修改b3不影响b4
console.log(b3)
console.log(b4)
Buffer实例方法
  • fill: 使用数据填充buffer
  • write: 向buffer中写入数据
  • toString: 从buffer中提取数据
  • slice: 截取buffer
  • indexOf:在buffer中查找数据
  • copy:拷贝buffer中的数据
fill 会重复写入
let buf = Buffer.alloc(7)
buf.fill('123', 1, 3)  // [1, 3) 填充位置
console.log(buf)
console.log(buf.toString())
write 不会重复写入
let buf = Buffer.alloc(7)
buf.write('123', 1, 4)   // 写入开始位置,写入长度
console.log(buf)
console.log(buf.toString())
toString
let buf = Buffer.alloc(7)
buf = Buffer.from('高级编程')
console.log(buf)
console.log(buf.toString('utf-8', 3, 9))  //起始位置, 结束位置
slice
let buf = Buffer.alloc(7)
buf = Buffer.from('高级编程')
let b1 = buf.slice(3, 9)  // 级编 [3, 9)
// let b1 = buf.slice(-3)  // 程
console.log(b1)
console.log(b1.toString())  //起始位置, 结束位置
indexOf
let buf = Buffer.alloc(7)
buf = Buffer.from('a高级b高级')
console.log(buf)
console.log(buf.indexOf('高', 7))  //8 检索起始位置
copy
let b1 = Buffer.alloc(6)
let b2 = Buffer.from('高级编程')
b2.copy(b1, 3, 3, 6) // b1的第3个位置开始写入 b2的[3, 6)为选择的字节
console.log(b1.toString()) // 级
console.log(b2.toString())
Buffer静态方法
let b1 = Buffer.from('高级')
let b2 = Buffer.from('编程')
let b3 = Buffer.concat([b1, b2], 9)  //限制合并后的长度
console.log(b3) // 级
console.log(b3.toString()) //高级编

let b4 = '123'
console.log(Buffer.isBuffer(b4))  //fasle
自定义buffer-split
Buffer.prototype.split = function (sep) {
  let len = Buffer.from(sep).length
  let ret = []
  let start = 0
  let offset = 0
  while ((offset = this.indexOf(sep, start)) !== -1) {
    ret.push(this.slice(start, offset))
    start = offset + len
  }
  ret.push(this.slice(start))
  return ret
}

let buf = Buffer.from('gfgfdfdgfgf')
let bufArr = buf.split('g')
console.log(bufArr)

内置模块 path

用于处理文件/目录的路径

  • .basename() 获取路径中的基础名称
  • .dirname() 获取路径中的目录名称(路径)
  • .extname() 获取路径中的扩展名称
  • .isAbsolute() 判断路径是否为绝对路径
  • .join() 拼接多个路径片段
  • .resolve() 返回绝对路径
  • .parse() 解析路径
  • .format() 序列化路径
  • .normalize() 规范化路径

basename

//basename 返回路径的最后一部分
const path = require('path')
console.log(path.basename('/a/b/c'))  //c  
console.log(path.basename('/a/b/c/')) //c 结尾处的路径分隔符会被忽略
console.log(path.basename('/a/b/c/d.js')) //d.js
console.log(path.basename('/a/b/c/d.js', '.js'))  //d
console.log(path.basename('/a/b/c/d.js', '.css'))  //d.js

dirname

//dirname 返回路径中最后一部分的上一层目录所在路径
const path = require('path')
console.log(path.dirname('/a/b/c'))   // /a/b
console.log(path.dirname('/a/b/c/'))  // a/b
console.log(path.dirname('/a/b/c/d.js'))  // /a/b/c

extname

//extname 返回响应文件的后缀名
// 存在多个点,匹配的是最后一个点到结尾的位置
const path = require('path')
console.log(path.extname('/a/b/c')) // ''
console.log(path.extname('/a/b/c/d.js')) // 'js'
console.log(path.extname('/a/b/c/d.js.css.html')) // html
console.log(path.extname('/a/b/c/d.js.')) // .

parse 解析路径

const path = require('path')
const obj = path.parse('/a/b/c/index.html')
console.log(obj)
//打印结果
{
  root: '/',
  dir: '/a/b/c',     
  base: 'index.html',
  ext: '.html',      
  name: 'index'      
}
const path = require('path')
const obj = path.parse('/a/b/c')
//const obj = path.parse('/a/b/c/')
console.log(obj)
//打印结果
{
  root: '/',
  dir: '/a/b',     
  base: 'c',
  ext: '',      
  name: 'c'      
}
const path = require('path')
const obj = path.parse('./a/b/c')
//const obj = path.parse('/a/b/c/')
console.log(obj)
//打印结果
{
  root: '',
  dir: './a/b',     
  base: 'c',
  ext: '',      
  name: 'c'      
}

format 序列化路径

const path = require('path')
const obj = path.parse('./a/b/c/')
console.log(path.format(obj))  //  ./a/b\c

isAbsolute

const path = require('path')
console.log(path.isAbsolute('foo'))   //false
console.log(path.isAbsolute('/foo'))   //true
console.log(path.isAbsolute('///foo'))  //true
console.log(path.isAbsolute('')) //false
console.log(path.isAbsolute('.'))  //false
console.log(path.isAbsolute('../bar'))  //false

join

const path = require('path')
console.log(path.join('a/b', 'c', 'index.html')) //  a/b/c/index.html
console.log(path.join('/a/b', 'c', 'index.html')) //  /a/b/c/index.html
console.log(path.join('/a/b', 'c', '../', 'index.html'))  // /a/b/index.html
console.log(path.join('/a/b', 'c', './', 'index.html'))  // /a/b/c/index.html
console.log(path.join('/a/b', 'c', '', 'index.html'))  // /a/b/c/index.html
console.log(path.join(''))  // .

normalize

const path = require('path')
console.log(path.normalize(''))  // .
console.log(path.normalize('a/b/c/d')) // a\b\c\d
console.log(path.normalize('a///b/c../d')) // a\b\c..\d
console.log(path.normalize('a//\\/b/c\\/d')) // a\b\c\d
console.log(path.normalize('a//\b/c\\/d'))  // a\c\d

resolve

const path = require('path')
console.log(path.resolve())  // D:\study\nodejs_study
console.log(path.resolve('a', 'b'))  // D:\study\nodejs_study\a\b
console.log(path.resolve('a', '/b'))  // D:\b
console.log(path.resolve('/a', 'b'))  // D:\a\b
console.log(path.resolve('/a', '/b'))  // .D:\b
console.log(path.resolve('/a', '../b'))  // .D:\b

内置核心模块 fs

提供文件系统操作的API

常见flag操作符

+ r: 表示可读
+ w:表示可写
+ s:表示同步
+ +:表示执行相反操作
+ x:表示排它操作
+ a: 表示追加操作

fd就是操作系统分配给被打开文件的标识

文件操作API

  • readFile:从执行文件中读取数据(路径不存在会报错)
  • writeFile:向指定文件中写入数据(路径不存在,会创建文件)
  • appendFile:追加的方式向指定文件中写入数据
  • copyFile:将某个文件中的数据拷贝至另一文件
  • watchFile:对指定文件进行监控
const fs = require("fs")
const path = require("path")

//readFile
fs.readFile(path.resolve('tests.txt'), 'utf-8', (err, data) => {
  if (!err) {
    console.log(data)
  }
})

//writeFile
fs.writeFile('test.txt', '123', {
  mode: 438,
  flag: 'r+', //r+:不清空,直接从头写入,  w+: 先清空,再从头开始写入
  encoding: 'utf-8'
}, (err) => {
  if (!err) {
    fs.readFile('test.txt', 'utf-8', (err, data) => {
      console.log(data)
    })
  }
})

//appendFile
fs.appendFile('test.txt', '高级编程', (err) => {
  console.log('追加成功')
})

//copyFile 操作小内存文件
fs.copyFile('test.txt', 'copy.txt', () => {
  console.log('copy成功')
})

//watchFile
fs.watchFile('test.txt', { interval: 20 }, (curr, prev) => {
  if (curr.mtime !== prev.mtime) {
    console.log('文件被修改了')
    fs.unwatchFile('test.txt')
  }
})

md转html

  • marked
  • browser-sync

文件打开与关闭

const fs = require("fs")
const path = require("path")

fs.open(path.resolve('test.txt'), 'r', (err, fd) => {
  console.log(fd)
})

fs.open('test.txt', 'r', (err, fd) => {
  console.log(fd)
  fs.close(fd, err => {
    console.log('关闭成功')
  })
})

大文件读写操作

const fs = require("fs")
const path = require("path")

//read:将磁盘里的内容读取出来放在buffer缓冲区中
let buf = Buffer.alloc(10)
fs.open(path.resolve('test.txt'), 'r', (err, rfd) => {
  console.log(rfd)
  fs.read(rfd, buf, 0, 3, 4, (err, readBytes, data) => {
    //后3个参数:buf开始执行写入起始位置,写入的长度,文件开始读取的起始位置
    console.log(readBytes, data.toString())
  })
})

//write 将缓冲区里的内容写入到磁盘文件中
buf = Buffer.from('1234567890')
fs.open('b.txt', 'w', (err, wfd) => {
  fs.write(wfd, buf, 0, 3, 0, (err, written, buffer) => {
    //后3个参数:buf开始读取起始位置,读取的长度,文件开始写入的起始位置
    console.log(written)  //写入字节数
    console.log(buffer)
    console.log(buffer.toString())
    fs.close(wfd)
  })
})

文件拷贝自定义实现

const fs = require("fs")
let buf = Buffer.alloc(10)

const BUFFER_SIZE = buf.length
let readOffset = 0
fs.open('test.txt', 'r', (err, rfd) => {
  fs.open('b.txt', 'w', (err, wfd) => {
    function next() {
      fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
        if (!readBytes) {
          fs.close(rfd, () => { })
          fs.close(wfd, () => { })
          console.log('拷贝完成')
          return
        }
        readOffset += readBytes
        fs.write(wfd, buf, 0, readBytes, (err, written) => {
          next()
        })
      })
    }
    next()
  })
})

常见目录操作API

  • access:判断文件或目录是否具有操作权限
  • stat: 获取目录及文件信息
  • mkdir:创建目录
  • rmdir:删除目录
  • readdir:读取目录中的内容
  • unlink:删除指定文件
const fs = require("fs")

//access
fs.access('test.txt', (err) => {
  if (err) {
    console.log(err)
  } else {
    console.log('有操作权限')
  }
})

//stat
fs.stat('test.txt', (err, statObj) => {
  console.log(statObj.size)
  console.log(statObj.isFile())
  console.log(statObj.isDirectory())
})

//mkdir 父级目录一定要存在 或者recursive: true
fs.mkdir('a/b/c', { recursive: true }, (err) => {
  if (!err) {
    console.log('创建成功')
  } else {
    console.log(err)
  }
})

//rmdir 默认只能删除空目录且只删除最后一级
fs.rmdir('a/b/c', { recursive: true }, (err) => {
  if (!err) {
    console.log('删除成功')
  } else {
    console.log(err)
  }
})

//readdir
fs.readdir('a/b', (err, files) => {
  console.log(files)
})

//unlink
fs.unlink('copy.txt', (err) => {
  if (!err) {
    console.log('删除成功')
  }
})

目录创建--同步

const fs = require("fs")
const path = require("path")
function makeDirSync(dirPath) {
  let items = dirPath.split(path.sep)
  for (let i = 1; i <= items.length; i++) {
    let dir = items.slice(0, i).join(path.sep)
    try {
      fs.accessSync(dir)
    } catch (err) {
      fs.mkdirSync(dir)
    }
  }
}

makeDirSync('a\\b\\c')

目录创建--异步

const fs = require("fs")
const path = require("path")
const { promisify } = require('util')
function makeDir(dirPath, cb) {
  let parts = dirPath.split('/')
  let index = 1
  function next() {
    if (index > parts.length) return cb && cb()

    let current = parts.slice(0, index++).join('/')

    fs.access(current, (err) => {
      if (err) {
        fs.mkdir(current, next)
      } else {
        next()
      }
    })
  }

  next()
}

makeDir('a/b/c', () => {
  console.log('创建成功')
})

//async方法
const access = promisify(fs.access)
const mkdir = promisify(fs.mkdir)

async function myMkdir(dirpath, cb) {
  let parts = dirpath.split('/')
  for (let index = 1; index <= parts.length; index++) {
    let current = parts.slice(0, index).join('/')
    try {
      await access(current)
    } catch (err) {
      await mkdir(current)
    }
  }
  cb && cb()
}

myMkdir('a/b/c', () => {
  console.log('创建成功')
})

内置模块VM

创建独立的沙箱环境

内置模块 Events

通过EventEmitter类实现事件统一管理 EventEmitter常见API

  • on:添加当事件被触发时调用的回调函数
  • emit:触发事件,按照注册的序同步调用每个事件监听器
  • once:添加当事件在注册之后首次被触发时调用的回调函数
  • off:移除特定的监听器
const EventEmitter = require('events')
const ev = new EventEmitter()

ev.on('事件1', () => {
  console.log('事件1执行了')
})
ev.once('事件1', () => {
  console.log('事件1执行了2222')
})

ev.emit('事件1')
ev.emit('事件1')


let cbFn = (a, b) => {
  console.log(a, b, '事件2执行了')
}
ev.on('事件2', cbFn)
ev.emit('事件2')
ev.emit('事件2', 1, 4)
ev.off('事件2', cbFn)

核心模块 stream

CommonJS

模块加载是同步完成的

模块引用、模块定义、模块标识符

module属性

  • 任意一个js文件就是一个模块,可以直接使用module属性
  • id:模块标识符,一般是一个绝对路径
  • filename:返回文件模块的绝对路径
  • loaded:布尔值,表示模块是否完成加载
  • parent:返回对象,存放调用当前模块的模块
  • children:返回当前模块调用的其他模块
  • exports:返回当前模块需要暴露的内容
  • paths: 返回数组,存放不同目录下的node_modules位置

require属性:接收标识符

  • 基本功能是读入并且执行一个模块文件
  • resolve:返回模块文件绝对路径
  • extensions:一句不同后缀名执行解析操作
  • main:返回主模块对象

模块分类及加载流畅

  • 内置模块:Node源码编译时写入到二进制文件中
  • 文件模块:代码运行时,动态加载 加载流程
  • 路径分析:依据标识符确定模块位置
  • 文件定位:确定目标模块中具体的文件及文件类型
  • 编译执行:采用对应的方式完成文件的编译执行

exports指向module.exports,所以exports不能直接赋值,否知会断了与module.exports之间的联系

浏览器中的事件环

  • 从上至下执行所有的同步代码
  • 执行过程中将遇到的宏任务与微任务添加至相应的队列
  • 同步代码执行完毕后,执行满足条件的微任务回调
  • 微任务队列执行完毕后,执行所有满足需求的宏任务回调
  • 循环事件环操作
  • 注意:每执行一个宏任务之后,就会立刻检查微任务队列
setTimeout(() => {
  console.log('s1')
  Promise.resolve().then(() => {
    console.log('p2')
  })
  Promise.resolve().then(() => {
    console.log('p3')
  })
})
Promise.resolve().then(() => {
  console.log('p1')
  setTimeout(() => {
    console.log('s2')
  })
  setTimeout(() => {
    console.log('s3')
  })
})

// p1, s1, p2, p3, s2, s3

node事件环

NODE有6个队列

  • timers:执行setTimeout与setInterval回调
  • pending callbacks:执行系统操作的回调,例如tcp udp
  • idle, prepare:只在系统内部进行使用
  • poll:执行与I/O相关的回调
  • check:执行setImmediate中的回调
  • close callbacks:执行close事件的回调

Nodejs完整事件环

  • 执行同步代码,将不同的任务添加至相应的队列
  • 所有同步代码执行后,会去执行满足条件的微任务
  • 所有微任务代码执行会会执行timer队列中满足的宏任务
  • timer中的所有宏任务执行完成后就会依次切换队列
  • 注意:在完成队列切换之前就会先清空微任务代码
  • 注意:process.nexttick的回调是微任务,且执行优先级大于Promise.then
setTimeout(() => {
  console.log('s1')
  Promise.resolve().then(() => {
    console.log('p1')
  })
  process.nextTick(() => {
    console.log('t1')
  })
})
Promise.resolve().then(() => {
  console.log('p2')
})

console.log('start')
setTimeout(() => {
  console.log('s2')
  Promise.resolve().then(() => {
    console.log('p3')
  })
  process.nextTick(() => {
    console.log('t2')
  })
})
console.log('end')
//start, end, p2, s1, t1, p1, s2, t2, p3 某一类型的队列执行完,就立刻去执行文任务?
//start, end, p2, s1, s2, t1, t2, p1, p3 宏队列切换完才会再去执行微任务?

补充

fs
readFile(path, 'utf-8',  callback(err, dataStr))
__dirname: 当前文件的路径

http
http.createServer(): 创建web服务器的实例对象
server.on('request', (req, res) => {}):为服务器实例绑定request事件,监听客户端的请求
server.listen(80, () => {}): 启动服务器
req
req.url: 
req.method: 
res
res.setHeader('Content-Type',  'text/html: Charset=utf-8')  防止中文乱码
res.end: 向客户端响应内容

CommonJS
exports === module.exports
最终共享结果,永远以module.exports为准

npm config get registry
npm config set 
registry=https://registry.npm.taobao.org/
开发依赖包 -D

npm i nrm -g
nrm ls
nrm use taobao

i5ting_toc: 把md转为html

require加载自定义模块的时候,必须加上./  ,否则会当作内置模块或第三方模块进行加载
js -> json -> node