✍️ 重学node

·  阅读 616

Node可以做什么

  • 轻量级、高性能的web服务
  • 前后端js的同构开发
  • 便捷高效的前段工程化

Node的架构和运行过程

image.png

  • 当前层内容由js实现
  • 提供应用可直接调用库,例如fs、path、http等
  • js语言无法直接操作底层硬件配置

Builtin modules 胶水层

image.png

js和具体功能实现的对照层

底层

image.png

  • V8: 执行js代码,提供桥梁接口
  • libuv: ⌚事件循环、 事件对列、异步IO
  • 第三方模块:zlib、http、c-ares

为什么是nodejs

1.nodehs 慢慢演化为一门服务端‘语言’

image.png

image.png

2. io是计算机操作过程中最缓慢的环节

2.1 Reactor模式,单线程完成多线程的工作

假设有n个人等菜,正常的多线程就是多个服务员对应多个客人进行点菜服务

而Reactor模式,是让n个人自己点菜,最后一次性由一个人负责下单

2.2 Reactor 模式下实现异步IO、事件驱动

2.3 nodejs就是通过libuv进行异步io的处理,因此可以很好的处理io密集型高并发请求

2.4 Nodejs异步io

image.png

2.4.1 io

  • 阻塞io
  • 非阻塞io
  • 重复调用io操作,判断io是否结束

期待实现无需主动判断的非阻塞io

image.png

image.png

libuv库通过事件驱动的形式来通知io结束,只需要在该关心的地方监听io结束事件,即可不主动调用即可知道io结束

2.4.1.2 Io总结

  • io是应用程序的瓶颈所在
  • 异步io提高性能无需原地等待结果返回
  • io操作术语操作系统级别,平台都有对应的实现
  • nodejs单线程配合事件驱动架构及libuv实现了异步io
  • 异步io提高了cpu的使用率

NodeJs 事件驱动架构

1.1事件驱动架构是软件开发中的通用模式,

其主要形式可以阐述为主体发布消息,其他实例接受消息

1.2Nodejs 单线程

  • 异步IO
  • 事件驱动
  • 事件循环
  • 以上三种方式共同组合形成reator模式,单线程完成多线程的事儿

1.3 NodeJs使用Js实现高效可伸缩的高性能web服务

q: 单线程如何实现高并发

  • 异步非阻塞IO配合事件回调通知

需要注意的是NodeJs主线程师单线程,而非NodeJs是单线程, 在V8中已经加入了 web workers

image.png

Nodejs 应用场景

2.1 IO密集型高并发请求

2.2 NodeJs 作为中间层

image.png

2.3 操作数据库提供api服务

const express = require('express')
const app = express()
app.listen(3000, () => {
    console.log('服务启动在3000 端口')
})
复制代码

2.4 实时聊天应用程序

通过socket.io可以轻松实现实时聊天应用程序

NodeJs 全局对象

  • 与浏览器平台的window不完全相同

  • NodeJs中每个模块都相当于被IIFE函数包裹,是独立的,因此不存在this指向全局对象

  • NodeJs全局对象上挂载着许多属性

1.1 NodeJs 中全局对象是global

  • global 的根本作用就是作为宿主
  • 全局对象可以看作是全局变量的宿主

1.2 NodeJs 常见全局变量

  • __fileName

  • 返回正在执行脚本文件的绝对路径

  • __dirName

  • 返回正在执行脚本文件所在目录

  • timer类函数

  • 执行顺序与事件循环间的关系

  • process

  • 提供与当前线程互动的接口

  • require

  • 实现模块的加载

  • module\exports

  • 处理模块的导出

1.3 process

  • 无需require即可使用(所有的全局变量,因为nodejs在运行该脚本文件的时候就已经注入了全局变量)
  • 获取进程信息
  • 执行进程操作

常见方法

  • memortusage 获取资源信息内存等

    { rss: 19070976, heapTotal: 3801088,
    heapUsed: 3018384, external: 327227, arrayBuffers: 9914 } memoryUsage

  • heapTotal 和 heapUsed 代表 V8 的内存使用情况。

  • external 代表 V8 管理的绑定到 Javascript 对象的 C++ 对象的内存使用情况。

  • rss,常驻集大小, 是为进程分配的物理内存(总分配内存的子集)的大小,包括所有的 C++ 和 JavaScript 对象与代码。

  • arrayBuffers 代表分配给 ArrayBuffer 和 SharedArrayBuffer 的内存,包括所有的 Node.js Buffer。 这也包含在 external 值中。 当 Node.js 被用作嵌入式库时,此值可能为 0,因为在这种情况下可能无法跟踪 ArrayBuffer 的分配。

  • cpuUsage

    { user: 37002, system: 24749 } cpuUsage

  • cwd 运行目录

  • version node环境

  • versions node内部uv、v8等库的版本信息集合

  • arch cpu架构

  • env.NODE_ENV 用户环境

  • env.PATH 环境变量

  • env.USERPROFILE/ env.HOME 用户根目录

  • process.platform 系统平台

  • process.argv 启动参数、pid、运行时间

  • argv0 默认传参

  • execArgv 主动传参

  • pid

  • uptime 运行时间

Path

常见api

1.1 baseName

获取路径中的基础名称

/**
 * 01 返回的就是接收路径当中的最后一部分 
 * 02 第二个参数表示扩展名,如果说没有设置则返回完整的文件名称带后缀
 * 03 第二个参数做为后缀时,如果没有在当前路径中被匹配到,那么就会忽略
 * 04 处理目录路径的时候如果说,结尾处有路径分割符,则也会被忽略掉
 */
/* console.log(path.basename(__filename))
console.log(path.basename(__filename, '.js'))
console.log(path.basename(__filename, '.css'))
console.log(path.basename('/a/b/c'))
console.log(path.basename('/a/b/c/')) */
复制代码

1.2 dirname

获取路径目录名

/**
 * 01 返回路径中最后一个部分的上一层目录所在路径
 */
/* console.log(path.dirname(__filename))
console.log(path.dirname('/a/b/c'))
console.log(path.dirname('/a/b/c/')) */
复制代码

1.3 extname

获取路径的拓展名

/**
 * 01 返回 path路径中相应文件的后缀名
 * 02 如果 path 路径当中存在多个点,它匹配的是最后一个点,到结尾的内容
 */
/* console.log(path.extname(__filename))
console.log(path.extname('/a/b'))
console.log(path.extname('/a/b/index.html.js.css'))
console.log(path.extname('/a/b/index.html.js.'))  */
复制代码

1.4 parse

解析路径

/**
 * 01 接收一个路径,返回一个对象,包含不同的信息
 * 02 root dir base ext name
 */
// const obj = path.parse('/a/b/c/index.html')
// const obj = path.parse('/a/b/c/')
/* const obj = path.parse('./a/b/c/')
console.log(obj.name) */
复制代码

1.5 format

序列化路径

/* const obj = path.parse('./a/b/c/')
console.log(path.format(obj)) */
复制代码

1.6 isAbsolute

判断当前的路径是否为绝对路径

/* console.log(path.isAbsolute('foo'))
console.log(path.isAbsolute('/foo'))
console.log(path.isAbsolute('///foo'))
console.log(path.isAbsolute(''))
console.log(path.isAbsolute('.'))
console.log(path.isAbsolute('../bar')) */
复制代码

1.7 join

拼接路径

/* console.log(path.join('a/b', 'c', 'index.html'))
console.log(path.join('/a/b', 'c', 'index.html'))
console.log(path.join('/a/b', 'c', '../', 'index.html'))
console.log(path.join('/a/b', 'c', './', 'index.html'))
console.log(path.join('/a/b', 'c', '', 'index.html'))
console.log(path.join('')) */
复制代码

1.8 normalize

规范化路径

/* console.log(path.normalize(''))
console.log(path.normalize('a/b/c/d'))
console.log(path.normalize('a///b/c../d'))
console.log(path.normalize('a//\\/b/c\\/d'))
console.log(path.normalize('a//\b/c\\/d')) */
复制代码

1.9 resolve

返回绝对路径

// console.log(path.resolve())
/**
 * resolve([from], to)
 */
// console.log(path.resolve('/a', '../b'))
console.log(path.resolve('index.html'))
复制代码

Buffer

1.1 简介

Buffer对象用于表示固定长度的字节序列。许多 Node.js API 都支持Buffers。

Buffer最常见的用处就是作为一个缓冲区,作为文件读写的缓冲

Buffer让js可以操作二进制

1.2 Buffer是什么? 在哪? 做什么?

对文件的操作其根本就是对二进制数据的操作,对二进制数据的操作可以分为

  • 流操作
  • Buffer

js语言起初服务于浏览器平台,现在在Nodejs平台下js可实现io,如何实现的io就是靠着buffer和stream。

io行为操作的本质就是二进制数据

当然stream流操作并非nodejs独创

流操作配合管道实现数据分段传输,数据的端到端传输会有生产者和消费者,生产和消费的过程往往存在等待,产生等待时数据存放在哪?

image.png

Nodejs中Buffer是一片内存空间或者也可以称之为缓冲区

1.3 Buffer总结

  • 无需require的一个全局变量
  • 实现nodejs平台下的二进制数据操作
  • 不占据v8堆内存大小的内存空间
  • 内存的使用由node来控制,由v8的gc回收
  • 一般配合stream流使用,充当数据缓冲区

2.1 Buffer api

2.1.1 fill

  • fill 和普通js的操作一样,填充
  • 三个参数,第二个参数是填充的起始位置
  • 第三个参数是填充的结束位置

2.1.2 write

  • 三个参数(value, start, value.length)

2.1.3 toString

  • toString// 默认编码格式utf-8
  • 三个参数(编码格式, start, end)

2.1.4 slice

  • 两个参数(start, length) 注意编码格式不同引起的长度问题

2.1.5 copy

  • source.copy(target, targetStart, sourceStart, sourceEnd)

2.1.6 ifBuffer

  • 判断当前数据是否为buffer

    // let buf = Buffer.alloc(6)

    // fill 和普通js的操作一样,填充 // 三个参数,第二个参数是填充的起始位置 // 第三个参数是填充的结束位置 /* buf.fill(123) console.log(buf) console.log(buf.toString()) */

    // write // 三个参数(value, start, value.length)

    /* buf.write('123') console.log(buf) console.log(buf.toString()) */

    // toString // 默认编码格式utf-8 // 三个参数(编码格式, start, end) /* buf = Buffer.from('拉钩教育') console.log(buf) console.log(buf.toString('utf-8', 3, 9)) */

    // slice /* let buf = Buffer.from('是厕所是') let b1 = buf.slice() console.log(b1.toString()) */

    // copy // source.copy(target, targetStart, sourceStart, sourceEnd) let buf = Buffer.alloc(6) let buf2 = Buffer.from('拉钩') buf2.copy(buf, 3, 3, 6)

    console.log(buf.toString()) console.log(buf2.toString())

2.1.6 熟悉api,仿写split

// 原型上添加方法
ArrayBuffer.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 
  }
  return ret 
}
复制代码

fs模块

1.1 fs是NodeJs内置核心模块,提供问价系统操作的api

image.png

1.2 权限位、标识符、 文件描述符

权限位

  • 用户对于文件所具备的操作权限,一般创建者对文件的权限为777

image.png

标识符

  • r: 可读
  • w: 可写
  • s: 同步
  • +: 执行相反操作
  • x: 排他操作

文件描述符

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

1.3 fs总结

  • fs 是Nodejs中内置核心模块
  • 代码层面上fs分为基本操作类和常用api
  • 权限位、标识符、操作符

1.4 文件操作api

  • readFile: 从指定文件中读取数据(path, encoding,callback)

  • writeFile: 向指定文件中写入数据 (path, content, {mode: 权限位, flag: 标识符, encoding})

  • appendFile: 追加的方式向指定文件中写入数据 和writeFile一样不过是push操作

  • copyFile: 将某个文件中的数据拷贝至另一文件 (target, source, callback)

  • watchFile: 对指定文件进行监控 (path, {interval: 重复调用时间}, (curr,prev))

    const fs = require('fs') const path = require('path')

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

    // writeFile /* fs.writeFile('data.txt', '123', { mode: 438, flag: 'w+', encoding: 'utf-8' }, (err) => { if (!err) { fs.readFile('data.txt', 'utf-8', (err, data) => { console.log(data) }) } }) */

    // appendFile /* fs.appendFile('data.txt', 'hello node.js',{}, (err) => { console.log('写入成功') }) */

    // copyFile /* fs.copyFile('data.txt', 'test.txt', () => { console.log('拷贝成功') }) */

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

1.5 md to html 练习

const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')

/**
 * 01 读取 md 和 css 内容
 * 02 将上述读取出来的内容替换占位符,生成一个最终需要展的 Html 字符串 
 * 03 将上述的 Html 字符写入到指定的 Html 文件中
 * 04 监听 md 文档内容的变经,然后更新 html 内容 
 * 05 使用 browser-sync 来实时显示 Html 内容
 */

let mdPath = path.join(__dirname, process.argv[2])
let cssPath = path.resolve('github.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')

fs.watchFile(mdPath, 0, (curr, prev) => {
  if (curr.mtime !== prev.mtime) {
    fs.readFile(mdPath, 'utf-8', (err, data) => {
      // 将 md--》html
      let htmlStr = marked(data)
      fs.readFile(cssPath, 'utf-8', (err, data) => {
        let retHtml = temp.replace('{{content}}', htmlStr).replace('{{style}}', data)
        // 将上述的内容写入到指定的 html 文件中,用于在浏览器里进行展示
        fs.writeFile(htmlPath, retHtml, (err) => {
          console.log('html 生成成功了')
        })
      })
    })
  }
})

browserSync.init({
  browser: '',
  server: __dirname,
  watch: true,
  index: path.basename(htmlPath)
})

const temp = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            .markdown-body {
                box-sizing: border-box;
                min-width: 200px;
                max-width: 1000px;
                margin: 0 auto;
                padding: 45px;
            }
            @media (max-width: 750px) {
                .markdown-body {
                    padding: 15px;
                }
            }
            {{style}}
        </style>
    </head>
    <body>
        <div class="markdown-body">
            {{content}}
        </div>
    </body>
    </html>
`
复制代码

1.6 文件打开和关闭

  • fs.open(path, 'r' | 'w', (err, w|r fd) => {})一般分为读写了两种
  • fs.close(path, callback)
  • fs.read(fd, buffer, offset, length , position)
  • fs.write(fd, buffer, offset, length ,position)

1.6.1 文件读写

  • 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中

  • fd 定位当前被打开的文件

  • buf 用于表示当前缓冲区

  • offset 表示当前从 buf 的哪个位置开始执行写入

  • length 表示当前次写入的长度

  • position 表示当前从文件的哪个位置开始读取

    const fs = require('fs')

    // read : 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中 let buf = Buffer.alloc(10)

    /**

    • fd 定位当前被打开的文件
    • buf 用于表示当前缓冲区
    • offset 表示当前从 buf 的哪个位置开始执行写入
    • length 表示当前次写入的长度
    • position 表示当前从文件的哪个位置开始读取

    / / fs.open('data.txt', 'r', (err, rfd) => { console.log(rfd) fs.read(rfd, buf, 1, 4, 3, (err, readBytes, data) => { console.log(readBytes) console.log(data) console.log(data.toString()) }) }) */

    // write 将缓冲区里的内容写入到磁盘文件中 buf = Buffer.from('1234567890') fs.open('b.txt', 'w', (err, wfd) => { fs.write(wfd, buf, 2, 4, 0, (err, written, buffer) => { console.log(written, '----') fs.close(wfd) }) })

1.6.2 大文件的写入测试

const fs = require('fs')

/**
 * 01 打开 a 文件,利用 read 将数据保存到 buffer 暂存起来
 * 02 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中
 */
let buf = Buffer.alloc(10)

// 01 打开指定的文件
/* fs.open('a.txt', 'r', (err, rfd) => {
  // 03 打开 b 文件,用于执行数据写入操作
  fs.open('b.txt', 'w', (err, wfd) => {
    // 02 从打开的文件中读取数据
    fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
      // 04 将 buffer 中的数据写入到 b.txt 当中
      fs.write(wfd, buf, 0, 10, 0, (err, written) => {
        console.log('写入成功')
      })
    })
  })
}) */

// 02 数据的完全拷贝
/* fs.open('a.txt', 'r', (err, rfd) => {
  fs.open('b.txt', 'a+', (err, wfd) => {
    fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
      fs.write(wfd, buf, 0, 10, 0, (err, written) => {
        fs.read(rfd, buf, 0, 5, 10, (err, readBytes) => {
          fs.write(wfd, buf, 0, 5, 10, (err, written) => {
            console.log('写入成功')
          })
        })
      })
    })
  })
}) */

const BUFFER_SIZE = buf.length
let readOffset = 0

fs.open('a.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()
  })
})
复制代码

1.7 目录操作api

  • access: 判断文件或目录是否具有操作权限(path, (err))

  • stat: 获取目录及文件信息(path, (err, statObj))

  • mkdir: 创建目录(path, {recursive: 递归创建})

  • rmdir: 删除目录(path, {recursive: 递归删除})

  • readdir: 读取文件中的内容(path, (err, file: path下的子集目录))

  • unlink: 删除指定文件(path, (err))

    const fs = require('fs')

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

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

    // 三、mkdir /* fs.mkdir('a/b/c', {recursive: true}, (err) => { if (!err) { console.log('创建成功') }else{ console.log(err) } }) */

    // 四、rmdir fs.rmdir('a', {recursive: true}, (err) => { if (!err) { console.log('删除成功') } else { console.log(err) } })

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

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

1.7.1 创建目录的同步实现

const fs = require('fs')

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

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

// 三、mkdir 
/* fs.mkdir('a/b/c', {recursive: true}, (err) => {
  if (!err) {
    console.log('创建成功')
  }else{
    console.log(err)
  }
}) */

// 四、rmdir
fs.rmdir('a', {recursive: true}, (err) => {
  if (!err) {
    console.log('删除成功')
  } else {
    console.log(err)
  }
})

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

// 六、unlink
/* fs.unlink('a/a.txt', (err) => {
  if (!err) {
    console.log('删除成功')
  }
}) */
复制代码
分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改