底层:
v8:1. 主要负责js代码的执行 2. 提供桥梁接口。
libuv:事件循环,事件队列,异步io
第三方功能模块:zlib,http,c-areas等。
nodejs是为了实现高性能的web服务器,后来经过长时间的发展nodejs就变成了一门服务端语言。这样就jsavascript在浏览器场景工作之外的场景。
数据的读写操作,总归是有消耗的。
并发服务端解决思路:1.多线程,2.多进程。
reactor模式:单线程完成多线程工作,并且是非阻塞。
多线程的消耗:1. 状态保存,2.时间消耗 3.状态锁
reactor模式下实现异步io,事件驱动。
nodejs更适用io密集型高并发请求。
nodejs三层架构:
发布者广播消息,其他的订阅者接收之前订阅的消息,之后在处理响应的处理。
const EvnentEmitter = require('events')
const myEvent = new EvnentEmitter()
myEvent.on('事件1', () => {
console.log('事件1执行了')
})
myEvent.emit('事件1')
nodejs核心:1.异步io,2.事件循环,3.事件驱动
nodejs单线程如何实现高并发?
答:在nodejs的底层是:异步io + 事件循环 + 事件驱动的架构去通过回调通知的方式实现非阻塞的io调用和并发。具体的表现是在代码中出现多个请求的时候,是无需阻塞的。会由上到下的执行,等待libuv库完成工作之后,再按顺序通知响应的事件回调触发执行。这样的话,单线程就完成了多线程的工作。这里的单线程指的是:主线程是单线程,并不说nodejs只能是单线程。
nodejs中的代码都是由v8执行的。
const http = require('http')
const server = http.createServer((req, res) => {
// res.end:向客户端返回内容
res.end('server starting....')
})
// 模拟代码执行消耗的时间
function sleepTime(timer) {
const sleep= Date.now() + time * 1000
while(Date.now() < sleep) {}
return
}
// 开启本地服务。
server.linten(8080, () => {
console.log('服务启动了')
})
需求:希望有一个服务,可以依据请求的接口内容返回相应的数据。
app.js
import express from 'express'
import list from './list.json'
const app = express()
app.get('/', (req, res) => {
res.json(list)
})
app.listener(8080, () => {
console.log('服务正常开启')
})
list.json
[
{
name: 'liz',
age: 1,
},
{
name: 'lizz',
age: 2,
}
]
nodejs中常见的全局对象:
1.__filename
:返回正在执行脚本文件的绝对路径
2.__dirname
:返回正在执行脚本所在目录。(绝对目录)
3.timer类函数,执行顺序和事件循环间的关系(setTimeout,setInterval,setImmediate,nextTicl)
6. process:提供与当前进程互动的接口。(获取当前进程的数据)
7. require:实现模块的加载
8. module,exports:处理模块的导出。
9. 在nodejs中默认情况下this是空对象,和global并不是一样的。
console.log(global) // 看看都有啥
console.log(__filename, __dirname)
console.log(this==global) // false
资源:cpu,内存
const fs = require('fs')
console.log(processs.memoryUsage)
console.log(processs.cpuUsage)
// 运行环境:运行目录,node环境,cpu架构,开发环境状态,系统平台是max,linux,windows
console.log(process.cwd()) // 获取当前的运行目录
console.log(process.version) // node当前的版本
console.log(process.versions) // 得到更多的版本信息,
console.log(process.arch) // cpu架构 x64
console.log(process.env.NODE_ENV) // 开发/生产环境状态
console.log(process.env.PATH) // 当前本机配置的环境变量
console.log(process.env.USERPROFILE)
console.log(process.platform)
// 运行状态:启动参数,PID,运行时间
console.log(process.argv) // 启动参数 是一个数组:默认有2个值:1:nodejs启动程序目录 2:文件绝对路径 启动的参数默在数组的尾部
console.log(process.argv0) // 获取数组的第一个
console.log(process.execArgv)
console.log(process.pid) // ppid
console.log(process.uptime)
事件基本上都是基于:发布订阅模式。
// 事件
// 当前的脚本文件执行完成之后执行
process.on('exit', (code) => {
console.log('代码执行完了')
})
process.on('beforeExit', () => {
console.log('before exit' + code)
})
console.log('代码执行完了')
// 标准的输入,输出,错误
console.log = function(data) {
process.stdout.write('--' + data + '\n')
}
const fs = require('fs')
fs.createREadStream('test.txt')
.pip(process.stdout)
process.stdin.pipe(process.stdout)
process.stdin.setEncoding('utf-8')
process.stdon('readable', () => {
let chunk = process.stdin.read()
if(chunk !==null) {
process.stdout.write('data' + chunk)
}
})
用于处理文件/目录的路径
const path = require('path')
console.log(__filename) // D:\Desktop\Node 高级编程\code\06-path.js
// 1.获取路径中的基础名称
// 1. 返回的就是接收路径中的最后一部分 2.第二个参数表示扩展名,如果说没有设置则返回完整的文件名称带后缀。 3.第二个参数作为后缀,如果没有在当前路径中呗匹配到,那么就会忽略 4. 处理目录路径的时候,结尾处有路径分割符,则会被忽略掉。
console.log(path.basename(__filename)) // 06-path.js
console.log(path.basename(__filename, 'js')) // 06-path
console.log(path.basename(__filename, '.css')) // 06-path.js
console.log(path.basename('/a/b/c')) // c
console.log(path.basename('/a/b/c/')) // c
// 2.获取路径目录名(路径)
// 1.返回路径中最后一个部分的上一层目录所在路径。
console.log(path.dirname(__filename)) // D:\Desktop\Node 高级编程\code
console.log(path.dirname('/a/b/c')) // /a/b
console.log(path.dirname('/a/b/c/')) // /a/b
// 3.获取路径的扩展名
// 1.返回path路径中相应文件的后缀名 2.如果path路径当中存在多个点,匹配的是最后一个点到结尾的内容。
console.log(path.extname(__filename)) // .js
console.log(path.extname('/a/b')) // ''
console.log(path.extname('/a/b/index.html.js.css')) // .css
console.log(path.extname('/a/b/index.html.js.')) // .
// 4.解析路径
// 1.接收一个路径,返回一个对象,包含不懂的信息
// root,dir,base,ext,name
// 处理文件路径
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 obj = path.parse('/a/b/c') // { root: '/' , dir: '/a/b', base: 'c', ext: '', name: 'c' }
const obj = path.parse('/a/b/c/') // { root: '/' , dir: '/a/b', base: 'c', ext: '', name: 'c' }
const obj = path.parse('./a/b/c/') // { root: '' , dir: './a/b', base: 'c', ext: '', name: 'c' }
// 5. 序列化路径
const obj = path.parse('./a/b/c')
console.log(path.format(onj)) / ./a/b\c
// 6. 判断当前的路径是否为绝对路径
console.log(path.isAbsolute('foo')) // false
console.log(path.isAbsolute('/foo')) // true
console.log(path.isAbsolute('///foo')) // true
console.log(path.isAbsolute('')) // false
// 7.拼接路径
console.log(path.join('a/b', 'c', 'index.html')) // a\b\c\index.html
Buffer是nodejs的内置类
const b1 = Buffer.alloc(10)
const b2 = Buffer.allocUnsafe(10)
console.log(b1, b2)
// from
const b1 = Buffer.from('1', 'utf-8')
console.log(b1)
const b1 = Buffer.from([1,2,3, '中'], 'utf-8')
console.log(b1) //
const b2 = Buffer.from('中')
console.log(b2) //
console.log(b2.toString()) // 中
// slice
buf = Buffer.from('拉钩教育')
let b1 = buf.slice(-3)
consooe.log(b1)
consooe.log(b1.toString())
// indexOf
buf = Buffer.from('liz爱前端,爱上拉钩教育,爱大家,我爱所有')
console.log(buf.indexOf('爱', 4)) // 15
let b1 = Buffer.from('拉钩')
let b2 = Buffer.from('拉教育')
let b = Buffer.concat([b1, b2])
consoe.log(b) //
consoe.log(b.toString()) // 拉钩教育
ArrayBuffer.prototype.split = function (sep) {
let len = Buffer.from(sep).length
let ret = [] // 最终返回的结果
let offset = 0
let start = 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 = 'zce吃馒头,吃面条,我吃所有的好吃的'
let bufArr = buf.split('吃')
console.log(bufArr) // []
FS是内置核心模块,提供文件系统操作的api
readFile:从指定文件中读取数据
writeFile:向指定文件中写入数据
appendFile:追加的方式向指定文件中写入数据
copyFile: 将某个文件中的数据拷贝至另一个文件
watchFile: 对指定文件进行监控
const fs = require('fs')
const path = require('path')
// 读取文件中的数据
fs.readFile(path.resolve('data.txt'), 'utf-8', (err, data) => {
if (!err) {
console.log(data)
}
})
// 向文件中写入数据
// 设置权限:{ mode: '438', flag: 'r+' }
fs.writeFile('data.txt', 'hello node js', { mode: '438', flag: 'r+', encoding: 'utf-8' }, (err, data) => {
if(!err) {
fs.readFile('data.txt', 'utf-8', (err, data) => {
consoel.log(data)
})
}
})
appendFile
fs.appendFile('data.txt' , '拉钩教育', (err) => {
console.log('写入成功')
})
copyFile
fs.copyFile('data.txt', 'test.txt', () => {
console.log('拷贝成功')
})
watchFile
fs.watchFile('data.txt', { interval: 20 }, (curr, prev) => {
console.log(curr, prev)
if(curr.mtime !== prev.mtime) {
console.log('文件被修改了')
// 取消对文件的监控
fs.unwatchFile('data.txt')
}
})
const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')
// 1.读取md和css内容。2.将上述读取出来的内容替换占位符,生成一个最终需要展示的html字符串。 3.将上述的html字符写入到指定的html文件中。 4.监听md文档内容的变化,然后更新html内容。 5.使用browser-sync来实时显示html内容
// 拿到md文件的路径
let mdPath = path.join(__dirname, process.argv[2])
let cssPath = path.resolve('gihub.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')
// 数据读取
fs.readFile(mdPath, 'utf-8', (err, data) => {
let htmlStr = marked(data)
fs.readFile(cssPath, 'utf-8', (err, data) => {
let retHtml = teml.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 = ``
index.md
### 我是婊气
const fs = require('fs')
const path = require('path')
// fd:文件操作符
// open:打开文件
fs.open(path.resolve('data.txt'), 'r', (err, fd) => {
console.log(fd)
})
close
fs.close('data.txt', 'r', (err, fd) => {
console.log(fd)
fs.close(fd, err=> {
consoel.log('关闭成功')
})
})
大文件读写操作,由于内存限制问题,不要直接使用fs.readFile 和 fs.writeFile。
必须使用fs.read 和 fs.write 来对文件进行读写操作。
const fs = require('fs')
// read:所谓的读操作就是将数据从磁盘文件中写入到buffer中。
let buf = Buffer.alloc(10)
fs.open('data.txt', 'r', (err, rfd) => {
console.log(rfd)
fs.read(rfd, buf, 0, 3, 0, (err, readBytes, data) => {
console.log(readBytes, data, data.toString())
})
})
// write将缓冲区里的内容写到磁盘文件中。
buf = Buffer.from('1234567890')
fs.open('b.txt', 'w', (err, wfd) => {
fs.write(wfd, buf, 0, 3, 0, (err, written, buffer) => {
console.log(err, written, buffer.toString())
})
})
let buf = Buffer.alloc(10)
// 1.打开指定的文件
fs.open('a.txt', 'r', (err, rfd) => {
// 3.打开b文件,用于执行数据写入操作
fs.open('b.txt', 'w', (err, wfd) => {
// 2.从打开的文件中读取数据
fs.read(rfd, buf, 0, 10, 0 , (err, readBytes, buffer) => {
// 4.将buffer中的数据写入到b.txt文件中
fs.write(wfd, buf, 0, 10, 0, (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, rfd) => {
function next() {
fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
if (!readBytes){
// 如果条件成立,说明读写操作已经完成。
fs.close(rfd, () => {})
fs.close(wfd, () => {})
consoel.log('拷贝完成')
return
}
readOffset += readBytes
fs.write(wfd, buf, 0, 10, 0, (err, written) => {
next()
})
})
}
next()
})
})
const fs = require('fs')
// access
fs.access('a.txt', (err) => {
if (err) console.log(err)
console.log('有操作权限')
})
const fs = require('fs')
// 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('创建成功')
console.log(err)
})
rmdir
fs.rmdir('a/b/c', { recursive: true } ,(err) => {
if(!err) console.log('删除成功')
console.log(err)
})
const fs = require('fs')
const path = require('path')
// 1.
前端开发为什么需要模块化?
模块:就是小而精且利于维护的代码片段。
利用:函数,对象,自执行函数实现分块
commonjs是语言层面的规范:
commonjs规范:
1.模块引用:require
2.模块定义:exports导出
3.模块标识:模块ID,为了找到对应的模块,
nodejs与commonjs:
任意一个文件就是一个模块,具有独立作用域。
使用require导入其他模块。
将模块ID传入实现目标某块定位。
module属性:
任意的js文件就是一个模块,可以直接使用module属性。
id:返回模块标识符,一般是一个绝对路径
fillename:返回文件模块的绝对路径。文件名称
loaded:布尔值,表示模块式否加载完成
parent:返回的是对象,存放调用当前模块的模块
children:数据,存放当前模块调用的其他模块。
exports:返回当前模块需要暴露的内容。
paths:数组,存放不同目录下的node_modules位置。
module.exports与exports有什么区别?
在规范中,只规定了module.exports执行数据的导出操作,单个exports实际上它是nodejs自己为了方便操作给每个模块都提供了一个变量,它只是指向了module.exports所对应的内存地址,因此我们可以直接通过exports导出相应的内容,但是不能直接给exports赋值,因为这样做等于切断了module.exports与exports之间的联系,这样exports就变成了局部的变量,无法向外部提供数据。
require:属性
基本功能就是读入并且执行一个模块文件。
resolve:返回模块文件绝对路径
extensions:依据不同后缀名执行解析操作
main:返回主模块对象,也就是入口文件。
1.commonjs起初只是为了弥补javascript语言模块化缺陷。想让js代码不仅运行在浏览器端。
2.commonjs是语言层面上的规范,当前主要用于nodejs。
3.commonjs规定模块化分为引入,定义,标识符三个部分
4.module在任意模块中可以直接使用包含模块信息
5.require接收标识符 ,加载目标模块
6.module.exports与exports都能导出模块数据
7.commonjs是同步加载数据
同步加载:
-node中模块 加载流程
分为三步
1:路径分析:依据标识符确定模块位置。
2.文件定位:确定目标模块中具体的文件及文件类型。
3.编译执行:采用对应的方式完成文件的编译执行。
路径分析
:其实就是一句不同的格式就行路径的查找,标识符分为:路径和非路径标识符。
在查找模块的时候查找策略是什么?在当前模块中可以直接通过module.paths获取到路径数组,在这个数组中存放的就是在不同的路径下查找的目录,而这些路径刚好就是从当前文件的父级开始,知道当前项目所在的盘符根目录,在将来加载模块的时候会遍历所有的目录,如果没有找到目标文件,就抛出错误。
文件定位
:在导入文件的时候,可能没有写扩展名,那么这个时候的查找规则就是先找.js文件,再找.json文件,最后找.node文件,如果都没找到那么node就认为当前拿到的是一个目录,会把这个目录当作一个包处理,而这个过程同样遵循js规范,首先会在当前文件查找package.json,使用json.parse解析文件,取出main属性值,如果main的值没有扩展名,则遵循.js,.json,.node的查找规则,如果还是没找到,或者是没有package.json文件,则默认找index文件,index.js,index.json,index.node,如果没找到,就抛出错误。
编译执行
: 首先创建对象,然后按路径载入,完成编译执行。对.js和.json文件编译执行是不一样的。
对.json文件将读取到的内容通过json.parse解析。对.js文件,1.使用fs模块同步读取目标文件内容 2.对目标文件的内容进行语法报错,生成可执行的js函数。3. 调用函数时传入exports,module,require等属性值。
VM:创建独立运行的沙箱环境。
// 模拟文件加载流程
/*
1.路径分析
2.缓存优化
3.文件定位
4.编译执行
*/
const fs = require('fs')
const path = require('path')
const vm = require('vm')
function Module(id) {
this.id = id
this.exports = {}
}
Module.__extensions = {
'.js'(module) {
// 读取
let content = fs.readFileSync(module.id, 'utf-8')
// 包装
content = Module.wrapper[0] + content + Module.wrapper[1]
// vm
let compileFn = vm.runInThisContext(content)
// 准备参数的值
let exports = module.exports
let dirname = path.__dirname(module.id)
let filename = module.id
compileFn.call(exports, exports, myRequire, module, filename, dirname)
},
'.json'(module) {
let content = JSON.parse(fs.readFileSync(module.id, 'utf-8'))
module.exports = content
},
}
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname){',
'})'
]
Module._resolveFilename = function (filename) {
// 利用path将filename转为绝对路径
let absPath = path.resolve(__dirname, filename)
// console.log('absPath', absPath)
// 判断当前路径对应的内容是否存在
if(fs.existsSync(absPath)) {
// 如果条件成立,则说明absPath对应的内容是存在的。
return absPath
} else {
// 文件定位
let suffix = Object.keys(Module.__extensions)
console.log(suffix)
for(let i = 0; i < suffix.length; i++) {
let newPath = absPath + suffix[i]
if (fs.existsSync(newPath)) {
return newPath
}
}
}
throw new Error(`${filename} is not exists`)
}
Module._cache = {}
Module.prototype.load = function () {
let extname = path.extname(this.id)
console.log(extname)
Module.__extensions[extname](this)
}
function myRequire(filename) {
// 1 绝对路径
let mPath = Module._resolveFilename(filename)
// 2 缓存优先
let catchModule = Module._cache[mPath]
if (catchModule) return catchModule.exports
// 3 创建空对象加载目标模块
let module = new Module(mPath)
// 4 缓存已加载过的模块
Module._cache[mPath] = module
// 5 执行编译(编译执行)
module.load()
// 6 返回数据
return module.exports
}
// 测试数据
let obj = myRequire('./v')
console.log(obj)
通过EventEmitter 类实现事件统一管理。
on
const EventEmitter = require('events')
const ev = new EventEmitter()
// on:注册时间
ev.on('自定义事件名称', () => {
console.log('事件1执行了')
})
ev.on('自定义事件名称', () => {
console.log('事件1执行了----2')
})
// emit:触发事件
ev.emit('自定义事件名称') // 事件1执行了 事件1执行了----2
ev.emit('自定义事件名称') // 事件1执行了 事件1执行了----2
once:事件只只执行一次,执行完成之后删除,以后仔调用就不执行了。
ev.once('自定义事件1', () => {
console.log('事件1执行了')
})
ev.once('自定义事件1', () => {
console.log('事件1执行了-----2')
})
ev.emit('自定义事件名称') // 事件1执行了 事件1执行了----2
ev.emit('自定义事件名称') // 不执行
off:删除事件监听
let cnFn = (...args) => {
console.log('事件1执行了', args) // [1,2,3]
}
ev.on('自定义事件名称', cnFn)
ev.emit('自定义事件名称') // 事件1执行了
ev.off('自定义事件名称')
ev.emit('自定义事件名称', 1, 2, 3) // 事件没执行
1.缓存对垒,存放订阅者信息
2.具有增加,删除订阅的能力
3.状态改变时通知所有订阅者执行监听
1.发布订阅者存在一个调度中心
2.状态发生改变时,发布订阅无需主动通知,由调度中心通知之前的订阅信息
模拟发布订阅的实现
class PubSub{
constructor(){
this._events = {}
}
// 注册事件
// event:事件类型,
// 事件类型对应的回调函数
sub(event, callback) {
// 之前订阅过相同类型的事件
if (this._events[event]) {
this._events[event].push(callback)
} else {
// 之前没有订阅过这个类型的事件
this._events[event] = [callback]
}
}
// 发布
pub(event, ...args) {
const items = this._events[event]
if (items.length) {
items.forEach((fn) => {
fn(...args)
})
}
}
}
// 测试
let ps = new PubSub()
ps.sub('事件1', () => {console.log('事件1执行le')})
ps.pub('事件1')
setTimeout(() => {
console.log('s1')
})
Promise.resolve().then(() => {
console.log('p1')
})
console.log('start')
process.nextTick(() => {
console.log('tick')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
//
start
end
tick
p1
s1
setImmediate
nextTick的优先级高于promise,没有为什么,就是这样定义的。
如果有2个setTimeout任务,则先执行第一个,第一个也是按照:同步任务,微任务的顺序,微任务放到微任务队列,先不执行,然后执行执行第2个setTimeout,同理,执行完同步任务,执行微任务,微任务放到微任务队列,先不执行。 2个setTimeout执行完成之后,去微任务队列去执行微任务。
如果是新版本的node,则是第一个setTimeout里面所有的代码执行完成之后,再执行第2个。
1.任务队列数不同。(浏览器只有2个任务队列,nodejs中有:微任务 + 6个事件队列)
2.nodejs微任务执行时机不同。(相同点:都是同步代码执行完成,执行微任务。不同点:浏览器平台下每当一个宏任务执行完毕后就清空微任务 3.nodejs平台在事件队列切换时会去清空微任务。)
3.微任务优先级不同。(1.浏览器事件中,微任务存放在事件队列,先进先出。2.nodejs中process.nextTick优先于process.nextTick)
setTimeout(() => {
console.log('s1')
})
/*
setTimeout(() => {
console.log('s1')
}, 0)
*/
setImmediate(() => {
console.log('setImmediate')
})
存在的问题:有时s1先输出,有时setImmediate先输出。
为什么会产这种问题呢?最根本的setTimeout没有加时间。没有写,默认为0,这个0不管是在浏览器平台下还是node环境下都不稳定,因为有时会产生延迟。如果产生延迟则在代码中反而先执行的是setImmediate。如果没有产生延迟,则先执行setTimeout。
应用程序中为什么使用流来处理数据?
流处理数据的优势:
1.时间效率:流的分段处理可以同时操作多个数据chunk
2.空间效率:同一时间流无需占据大内存空间
3.使用方便:流配合管理,扩展程序变得简单。
nodejs中内置了stream,它实现了流操作对象。
nodejs中流的分类:
1.Reabable: 可读流
2.Writeable:可写流
3.Duplex:双工流,既可以读也可以写。
4.Tranform:转换流,可读可写,还能实现数据转换
const fs = require('fs')
// 创建一个可读流,生产数据
let rs = fs.createReadStream('1.txt')
// 修改字符编码,便于后续使用
rs.seEncoding('utf-8')
// 创建一个可写流,消费数据
let ws = fs.createWriteStream('1.txt')
// 监听事件调用方法完成具体消费
rs.on('data', (chunk) => {
// 执行数据写入
ws.write(chunk)
})
双工流
let {Duplex} = require('stream')
class MyDuplex extends Duplex {
constructor(source) {
super()
this.source = source
}
_read() {
let data = this.source.shift() || null
this.push(data)
}
_write(chunk, en, nexr) {
process.stdout.write(chunk)
process.nextTick(next)
}
}
// 测试
let source = ['a', 'b', 'c']
let myDuplex = new MyDuplex(source)
myDuplex.on('data', (chunk) => {
console.log(chunk.toString())
})
转换流
let {Transform} = require('stream')
class MyTransform extends Transform {
constructor(){}
_transform(chunk, en, cb) {
this.push(chunk.toString().toUpperCase())
cb(null)
}
}
// 测试
let t = new MyTransform()
t.write('a')
t.on('data', (chunk) => {
console.log(chunk.toString())
})
const fs = require('fs')
const rs = fs.createReadStream('test.txt',{
flags: 'r',
encoding: null.
fd: null,
mode: 438,
autoClose: true
start:0, // 开始读取位置
end: 3, // 结束位置
highWaterMark: 2,
})
rs.on('data', (chunk) => {
console.log(chunk.toString())
rs.pause() // 暂停读取
setTimeout(() => {
rs.resume() // 恢复读取
}, 1000)
})
rs.on('readable', () => {
// let data = rs.read()
// console.log(data)
let data = null
while((daat= rs.read)!==null) {
console.log(data.toString())
}
})
const fs = require('fs')
const rs = fs.createReadStream('test.txt',{
flags: 'r',
encoding: null.
fd: null,
mode: 438,
autoClose: true
start:0, // 开始读取位置
end: 3, // 结束位置
highWaterMark: 2,
})
rs.on('open', () =>{
console.log('文件打开了')
})
rs.on('data', (chunk) =>{
console.log(chunk.toString())
})
// end执行之后,一般是:数据写入操作完成。
rs.on('end', (chunk) =>{
// 一般在end事件中。处理数据
console.log('当数据被清空之后执行')
})
rs.on('error', (err) =>{
console.log('错误了' + err)
})
rs.on('close', () =>{
console.log('文件关闭了')
})
执行时机:
open
data
end
close
如果报错:
err
const fs = require('fs')
let ws = fs.createWriteStream('test.txt', {
highWaterMark: 3
})
// 数据写入
ws.write('1')
const fs = require('fs')
let ws = fs.createWriteStream('test.txt', {
highWaterMark: 3
})
// 数据写入
let flag = ws.write('1')
console.log(flag) // true
flag = ws.write('2')
console.log(flag) // true
flag = ws.write('3')
console.log(flag) // false:如果flag为false,并不是说明当前数据不能执行写入。
数据读写存在的问题:内存溢出,gc频繁调用,其他进程变慢。
const fs = require('fs')
const EventEmitter = require('events')
class MyFileReadStream extends EventEmitter{
constructor(path, options = {}) {
super()
this.path = path
this.flags = options.flags || 'r'
this.mode = options.mode || 438 // rw
this.autoClose = options.autoClose || true
this.start = options.start || 0
this.end = options.end
this.highWaterMark = options.highWaterMark || 64 * 1024
this.readOffset = 0
this.open()
this.on('newListener', (type) => {
if (type === 'data') {
this.read()
}
console.log(type)
})
}
// 打开文件
open() {
// 原声open方法打开指定位置的文件
fs.open(this.path, this.flags, this.mode, (err, fd) => {
if (err) {
this.emit('error', err)
}
this.fd = fd
this.emit('open', fd)
})
}
read() {
if (typeof this.fd !== 'number') {
return this.emit('open', this.read)
}
let buf = Buffer.alloc(this.highWaterMark)
let howMuchToRead
if (this.end) {
howMuchToRead = Math.min(this.end = this.readOffset, this.highWaterMark)
} else {
howMuchToRead = this.highWaterMark
}
fs.read(this.fd, buf, 0, howMuchToRead, this.readOffset, (err, readBytes) => {
if (readBytes) {
this.readOffset += this.readBytes
this.emit('data', buf.slice(0, readBytes))
this.read()
} else {
this.emit('end')
}
})
console.log(this.fd)
}
close() {
fs.close(this.fd, () => {
this.emit('close')
})
}
}
// 测试
let rs = new MyFileReadStream('test.txt', {
end: 7,
highWaterMark: 3,
})
rs.on('open', (fd) => {
console.log('open',fd)
})
rs.on('error', (err) => {
console.log('error', err)
})
rs.on('fn', () => {})
rs.on('end', () => {
console.log('end')
})
为什么不采用数组存储数组?
数组的缺点:
1.数组存储数据的长度有上限
2.数据在增加,删除其他元素的时候,需要移动其他元素的位置
3.在js中数组被实现成了一个对象。在使用效率上比较低。
链表:是一系列节点的结合。
每个节点都具有指向下一个节点的属性。
单向链表
// size:维护当前节点的个数
链表的功能
1. 增加节点
2.删除节点
3.修改节点
4.查询
5.清空链表
class Node{
cunstructor(element, next) {
this.element = element
this.next = next
}
}
class LinkedList{
constructor(head, size) {
this.head = null
this.size = 0
}
// 根据index,获取当前节点node
_getNode(index) {
if(index < 0 || index > this.szie) {throw new Error('越界了')}
let currNode = this.head
for (let i = 0; i < index; i++) {
currNode = currNode.next
}
return currNode
},
add(index, element) {
if(arguments.length === 1) {
element = index
index = this.size
}
if(index < 0 || index > this.szie) {throw new Error('越界了')}
if(index ===0) {
this.head = this._getNode(index-1)
prevNode.next = new Node(element, prevNode.next)
}
this.size++
}
remove(index) {
if(index===0) {
let head = this.head
this.head = head.next
} else {
let prevNode = this._getNode(index-1)
prevNode.next = prevNode.next.next
}
this.size--
}
set(index, element) {
let node = this._getNode(index)
node.element = element
}
get(index) {
return this._getNode(index)
}
clear() {
this.head = null
this.size = 0
}
}
// 测试
const l1 = new LinkedList()
l1.add(index, 'node1')
l1.add(node1)
console.log(l1)
class Queue{
consrructor() {
this.linkedList = new LinkedList()
}
enQueue(data) {
this.linkedList.add(data)
}
}
pipe最终实现的还是一个拷贝的操作
rs.pipe(ws):比以前的先读后写要方便。
const fs = require('fs')
const rs = fs.createReadStream('./9.txt'm {
highWaterMark :4
})
const fs = fs.createWriteStream('./9.txt'm {
highWaterMark: 1
})
rs.pipe(ws)
通信必要条件
1.交换机通讯
2.路由器通讯
http:应用层协议,
tcp,udp:传输层协议
OSI 7层模型:
应用层:用户与网络的接口
表示层:数据加密,转换,压缩。
会话层:控制网络连接建立与终止
传输层:控制数据传输可靠性。
网络层:确定目标网络
数据链路层:确定目标主机。
物理层:各种物理设备标准。
数据从a到b,先封装再解封。
在两台主机通信之前,需要先握手,建立数据通信的通道。
通信事件:
listening事件:调用server.listen方法之后触发。
connection事件:新的连接建立时触发。
close事件:当server关闭时触发。
error事件:当错误出现时触发。
data事件:当接收到数据时触发该事件
write方法:在socket上发送数据,默认是utf8编码
end:结束可读端。
server.js
const net = require('net')
//创建服务端实例
const server = net.createServer()
const PORT = 1234
const HOST = 'locaLhost'
server.listen(PORT, HOST)
server.on('listening', () => {
console.log('服务端已经开启')
})
// 接收消息 回写消息
server.on('connection', (socket) => {
// 接收消息
socket.on('data', (chunk) => {
const msg = chunk.toString()
console.log(msg)
})
// 回写消息
socket.write(Buffer.from('您好' + msg))
})
server.on('close', () => {
console.log('服务端已经关闭')
})
server.on('error', (err) => {
if(err.code ==='EADDRINUSE') {
console.log('地址正在被使用')
}else {
console.log(err)
}
})
client.js
const net = require('net')
const client = net.createConnection({
port: 1234,
host: '127.0.0.1'
})
client.on('connect', () => {
console.log('拉钩教育')
})
client.on('data', (chunk) => {
console.log(chunk.toString())
})
client.on('error', (err) => {
console.log(err)
})
client.on('close', () => {
console.log('客户端断开连接')
})
req:前端的请求头信息req.headers
res:后端返回的结果
server.js
const http = require('http')
const url = require('url')
const server = http.createServer((req, res) => {
console.log("请求进来了")
// 请求路径额获取
let [pathname, query] = url.parse(req.url, true)
console.log(query)
// 请求方式
console.log(req.method)
// 版本号
console.log(req.httpVersion)
// 请求头
console.log(req.headers)
// 获取请求体中的数据
let arr = []
req.on('data', (data) => {
arr.push(data)
})
req.on('end', () => {
console.log(Buffer.concat(arr).toString())
})
})
server.listen(1234, () => {
console.log("server is start。。。")
})
const http = require('http')
const server = http.createServer((req, res) => {
console.log("请求进来了")
// res.write('ok')
// res.end()
// 设置头部信息
res.statusCode = 200
res.setHeader('content-type', 'text/html;chartset=utf-8')
res.end('ok')
})
server.listen(1234, () => {
console.log("server is start。。。")
})
主要解决跨域问题。
浏览器直接向自己创建的代理的客户端去发送请求,这里的代理客户端又充当着浏览器直接访问的服务端,服务端和服务端之间的通信是不存在跨域的。
server,js
const http = require('http')
const url = require('url')
const server = http.createServer((req, res) => {
console.log("请求进来了")
let {pathname, query} = url.parse(req.url)
// post
let arr = []
req.on('data', (data) => {
arr.push(data)
})
req.on('end', (data) => {
// console.log(req.headers['content-type'])
let obj = Buffer.concat(arr).toString()
if (req.headers['content-type'] === 'application/json') {
// console.log(req.headers.parsms)
let a = JSON.parse(obj)
res.end(JSON.stringify(a))
} else if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
let ret = querystring.parse(obj)
res.end(JSON.parse(ret))
}
console.log(Buffer.concat(arr).toString())
})
})
server.listen(1234, () => {
console.log("server is start。。。")
})
client.js
const http = require('http')
/*
http.get({
host: 'localhost',
port: '1234',
path: '/?a=1'
}, (res) => {
})
*/
// 配置信息
let options = {
host: 'localhost',
port: '1234',
path: '/?a=1',
method: 'post',
headers: { 'content-type': 'application/jsom' }
}
let req = http.request(options, (res) => {
})
req.end({name: 'liz'})
解决跨域有很多种
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const server = http.createServer((req, res) => {
let {pathname, query} = url.parse(req.url)
let absPath = path.join(__dirname, pathname)
// 目标资源的状态处理
fs.stat(absPath, (error, statObj) => {
if(err) {
res.statusCode = 404
res.end('not fount')
return
}
if (statObj.isFile()) {
fs.readFile(absPath, (err, data) => {
res.setHeaders('Content-type', 'tetx/html;chart=utf-8')
res.end(data)
})
} else {
fs.readFile(path.join(absPath, 'index.htnl'), (err,data) => {
res.setHeaders('Content-type', 'tetx/html;chart=utf-8')
res.end(data)
})
}
})
res.end('okok')
})
server.listen(1234, () => {
console.log('sererv is starting')
})
静态服务工具
npm init -y生成package.json
npm install commander
npm install mine
package.json
"bin": {
"lgserver": "bin/www.js"
}
bin/www.js
#! /user/bin/env node
const {program} = require('commander')
console.log('开始执行了')
program.option('-p --port', 'set server port')
// 配置信息
let options = {
'-p --port ' : {
'description': 'init server port',
'example': 'lgserve -p 3306',
},
'-d --directory ' : {
'description': 'init server directory',
'example': 'lgserve -d c',
},
}
functio formatConfig(configs, cb) {
Object.entrise(configs).forEach(([key, val]) => {
cb(key, val)
})
}
formatConfig(options, (cmd, val) => {
program.option(cmd, val.description)
})
program.on('--help', () => {
formatConfig(options, (cmd, val) => {
program.option(val.example)
})
})
program.name('lgserver')
let version = require('../package.json').version
program.version = version
let cmdConfig = program.parse(process.argv)
console.log('cmdConfig', cmdConfig)
let Server = require('../main.js')
new Server(cmdConfig).start()
main.js
const http = require('http')
const url = require('url')
const path = require('path')
const mine = require('mine')
function mergeConfig(config) {
return {
port: 1234,
directory: process.cwd()
...config,
}
}
class Server{
constructor(config){
this.config =mergeConfig(config)
}
start() {
let server = http.createServer(this.serveHandle.bind(this))
server.listener(this.config.port, () => {
console.log('服务端已经运行了')
})
}
serveHandle(req, res) {
let {pathname} = url.parse(req.url)
pathname = decodeURIComponent(pathname)
let abspath = path.join(this.config.directory, pathname)
try{
let statObj = await fs.stat(abspath)
if (statObj.isFile()) {
this.FileHandle(req, res, abspath)
} else {
let dirs = await fs.readdir(abspath)
let renderFile = promisify(ejs.renderFile)
let ret = await renderFile(path.resolve(__dirname, 'template.html'), {arr: dirs})
res.end(ret)
}
}catch(err){
this.errorHandle(req, res, err)
}
console.log('有请求进来了')
}
errorHandle(req, res, abspath) {
res.statusCode = 404
res.setHaeder('Content-type', 'text/html;chart=utf-8')
res.end('not fond')
}
FileHandle(req, res, abspath) {
res.statusCode = 200
res.setHaeder('Content-type', mine.getType(abspath) + ';chart;utf-8')
createReadStream(abspath).pipe(res)
}
}
module.exports = Server
验证:lgServer -p 3307 -d: c
步骤3. npm link
步骤4. lgserve