node
-
js的执行环境
-
主要两大块:网络,文件
-
主要做的事情: 高性能web服务器(单线程异步), 前端工程化(转义[es6->es5, less/sass->css],压缩,混淆-通过改变形参名字让代码不易阅读,打包), 爬虫,命令行工具(命令行软件)等 Electron => 可以做传统软件。把浏览器的UI功能和node的网络,文件读取,进程管理等功能组合起来制作传统软件。
-
node之所以用js适配,因为js就是异步的。node打算也做异步的I/O(异步本身底层实现是通过多线程) I/O: 跟硬盘或者网络交互时的输入输出 同步IO: 遇到异步代码比如readFile(), 代码卡在readFile()上,CPU调用读取程序读取内容再存在另外一片内存里,一边还去调用别的线程,在各线程内来来回回等回到读取程序发现全部存完了,readFile把从内存里读取的内容返回给程序继续后续处理。 异步IO: 异步操作在后台(另外线程)执行的同时主线程是不会暂停的会继续执行下去。等异步操作执行完再通知主线程执行完了。
-
层: os(硬件)-> Browser -> vm -> JS
os -> node -> vm -> JS os -> vm -> python browser提供给js的方法和node提供给js的方法是不一样的 -
如果没有给node传文件,就会REPL(交互式命令行):read,evaluate, print, loop 例如: $node
1 + 1 2 [-1,-2,-3].map(Math.abs) [1,2,3]
-
process是像console一样的全局变量,提供很多方法控制当前的进程 比如 退出: process.exit(0) 括号里是状态码,告诉外界怎么退出的。一般有标准,你以什么理由退出就返回相应的状态码 读取命令行文件接收的参数:process.argv 文件名 get-command-line-args.js 里面写代码 console.log(process.argv) 在node执行这个文件时传参数 node get-command-line-args.js foo bar baz 会读取到 后面几个参数 argv[0]是node的完整路径, argv[1]是文件的完整路径,argv[2]就是foo。。。
var num = parseInt(process.argv[2])
console.log(num + ':')
for(var i = 2 ; ; i++) {
while (num % i == 0) {
console.log(i)
num = num / i
if (num == 1) {
process.exit(0)
}
}
}
-
node中没有浏览器提供的功能比如document,alert. 全局对象浏览器是window,node中是global
-
node的调试 方法1:node --inspect-brk factor.js 15 (暂停在代码的第一行)/ node --inspect factor.js 原理:ws协议监听一个端口,打开谷歌浏览器,调试器左上角的node图标点击 写的代码被node当成模块运行 方法2:vs code的调试 方法3: npm i -g ndb ndb == node debug 用法: ndb node --insepect factor.js 15
-
require路径查找 如果是以点或者斜杠开始,相对于目前文件路径查找对应文件, 如果不以点或斜杠开始,加载内置的模块或者下载的模块 如果是下载的模块 1-如果没有找到相同名字的文件夹就会试图加载成文件, ,js, json, node 2-去当前文件夹的node_modules文件夹里找这个名字的文件夹,加载index.js 3- 如果没找到这个名字的文件夹 1)这个文件夹里有package.json就会去读取这个文件里的main字段所指向的文件 2)这个文件夹里有index.js就会把这个文件的导出的东西加载进来。 4-如果在当前文件夹的node_modules文件夹里未找到想要找的名字的文件夹就会去父级文件夹继续找
-
NPM: JS模块仓库 node package manage npm下载的时候发生的事: 这个包把自己所依赖的其它的包都声明在自己文件夹里的package.json的dependencies字段里,下载的时候会把这个字段里的所有依赖一并下载下来。
-
fs文件模块 file system:操作文件和文件夹的功能函数,本质就是把操作系统提供的函数 1)readFile 注意:如果不传第二个参数编码方式(一般传UTF-8),node自动返回原始字节Buffer对象,是类数组对象本身有length以及下标
2)wirteFile 因为是写入到硬件里,回调函数只传一个err就行。 默认以UTF-8写入
3)readdir : 文件夹里的所有文件以及文件夹的名字,以数组形式返回
4)stat:返回对象,里面包括此文件的详细信息,方法isFile,isDirectory,isSymbolicLink(类似快捷方式,它不是硬链接(两个地方指向同一个文件),它是符号连接(源文件删除它也自动被删除)),isSocket(网络),isFIFO(先进先出,TCP连接)
5)rename 重命名文件,以及移动文件
6)unlink 删除文件
7)fs里总共有四个版本的函数,同步,回调函数的异步,promise版本,还有f开头的函数。 其中f开头的函数中第一个参数fd是什么?是file directory 文件描述符,每一次open file的时候系统自动给我们返回一个文件描述符。 也就是说f开头的函数不用传路径,传文件描述符
8) chmode =》 change mode 对文件的权限改变
9)FSWatcher 文件监控 -
node里的IO相关的一般是异步函数,一般是错误优先。 如果成功就会传null,result。 如果失败就传error
-
异步函数后面加Sync(比如readFileSync)就会变成同步
-
异步函数可以封装成promise版本,像同步一样使用 例: read-file-promise.js
var fs = require('fs')
function readFilePromise(...args) {
return new Promise((resolve, reject) => {
fs.readFile(...args,(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
//用法
readFilePromise('a.js').then(data => {
}).catch(err => {
})
将一个基于回调的函数转换为promise的函数
用法:readFilePromise = promidify(fs.readFile)
function promisify(callbackBasedFunction) {
return function(...args) {
return new Promise((resolve, reject) => {
callbackBasedFunction(...args, (err, result) => {
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
}
}
将一个promise函数转换为基于回调函数的函数
function callbackify(promisedBased) {
return function(...args) {
var cb = args.pop()
promisedBased(...args).then((result)=>{
cb(null, result)
}).catch((reason)=>{
cb(reason)
})
}
}
-
以上两个转换函数node提供现成的 方法1:通过util(工具),每个函数单独转换 var util = require('util') var readFilePromise = util.promisify(fs.readFile) var readFileCallback = util.callbackify(readFilePromise) 方法2:直接通过 .promises 获取整个模块的异步函数们 fsp = require('fs').promises
-
npm i 和 npm i -g的区别 -g == --global 全局安装:不是让你在任何一个地方require的,而是以命令行工具的形式装在全局 命令行一般跟软件包名一样
node常用模块
- path:操作路径
提前要知道的:window和linux下的路径表示不一样,在window是用\反斜杠,linux用/正斜杠。 路径分隔符window是冒号:,linux是;分号。 所以path返回的有两个版本的对象,一个是window的,另一个是posix。最外层的就是目前的版本 如果在window开启node并打印path,window对象本身有两个指针一个w指针指向自己(win32:circular),另一个p指针指向p对象 如果在linux开启node并打印path,posix对象也本身有两个指针一个w指向window对象,p指针指向自己
1)basename 拿到文件名 path.basename('/foo/bar.jpg','.jpg') => bar
2) path.delimiter => 在linux 打印; , 在window打印:
3) path.dirname 拿到路径=>path.dirname('/foo/baz/bar.jpg','.jpg') => /foo/baz
4) path.extname('/foo/a.png') => .png
5) path.parse 路径分解成对象 {root: '/', dir:'',base:'',ext:'',name:''}
6) path.format 与5相反
7) isAbsolute: 以斜杠开头或者以盘开头就是绝对路径
8) join 把几个路径拼起来
9) path.normalize('foo/bat/../././') 化简=》 foo
10)path.resolve('/foo/bar/baz', '../1') => c:\foo\bar\1
path.resolve('/foo/bar/baz', '/../1') => c:\1
11)path.relative('/foo/bar/baz/baa/','/foo/bar/1/2')
从第一个路径通过什么路径可以到达第二个路径
=》 ..\..\1\2
12)path.sep 在window反斜杠,在linux正斜杠
-
util
util.type判断类型的
-
URL
URL对象:能控制url的各个部分
u = new URL('a:b@www.baidu.com:8889/foo/bar?a=b…')
URL {
href: 'a:b@www.baidu.com:8889/foo/bar?a=b…',
origin: 'www.baidu.com:8889',
protocol: 'https:',
username: 'a',
password: 'b',
host: 'www.baidu.com:8889',
hostname: 'www.baidu.com',
port: '8889',
pathname: '/foo/bar',
search: '?a=b&c=d',
searchParams: URLSearchParams { 'a' => 'b', 'c' => 'd' },
hash: '#foobar'
} url.SearchParams
url.domain相关(中国字互转unicode)
querystring模块解析query部分
- http模块
//服务端:
const http = require('http')
const server = http.createServer()
server.on('request',(request, response) => { // 背后连接tcp连接,收到正确的http报文的时候才会响应这个函数
request.socket.remoteAddress //socket是net的模块, 表示tcp连接, 这个可以获取对方的地址
request.method
request.headers
request.url
response.write('')
})
server.listen(port, () => {})
//客户端(跟XMLHttpRequest相似,可是这个不被跨域限制)发请求
const http = require('http')
var request = http.request({
hostname: "a.net",
path:'/a.html',
method: 'GET',
headers: {Accept: "text/html"}
}, function(response) {
})
http.get()//收请求
request.end()
const server = new http.Server(requestHandler) //http模块主要做的事情
class HTTPServer {
constructor(requestHandler) {
var net = require('net') //1.建立tcp连接
this.server = net.createServer(conn => { //2. 建立成功后
var head = parse()//parse data comming from conn //3. 解析收到的请求
if(isHttp(conn)) { //4. 如果是http请求就会如下反应
requestHandler(new RequestObject(conn), new ResponseObject(conn)) //5. 创造基于tcp连接的request对象和response对象, 来操作本次连接,按照报文形式给我们传入传出
} else {
conn.end()
}
})
}
listen(port, f) {
this.server.listen(port, f)
}
}
- __dirname 无论在哪个模块里都能访问到这个全局变量,当前模块所在文件夹的完整路径
- 把代码当脚本命令行使用的方法 linux里 1)echo $path 这个目录下的文件能直接执行 2)cd 其中一个路径里 3)文件放进去 (记得文件最上面加上一句告诉linux执行的时候用node执行,#!/usr/bin/env node) 4)sudo chmod 777 sfs(文件名) //修改权限 ,777可读可写可执行 5)使用,任何地方输入sfs(文件名)就直接执行那个代码了
- decodeURIComponent 路径是汉语,解析成汉语 是js的不是node的
- globals 当前模块下的 node里的setTimeout/setInterval返回一个对象,这个对象的unref方法,让该timer的未执行的状态不影响程序的结束. ref方法则相反
- os 操作系统 os.EOL 转行字符 \n \r\n
- Events 事件监听触发器 创造event,绑定事件e.on('',),触发事件emite(在浏览器是dispatchEvent),事件取消e.off
class EventEmmiter{
constructor() {
this.eventListeners = {}
}
on(type, handler) {
if(type in this.eventListeners) {
this.eventListeners[type].push(handler)
} else {
this.eventListeners[type] = handler
}
return this
}
off(type, handler) {
var listeners = this.eventListeners[type]
this.eventListeners[type] = listeners.filter(it => it != handler)
return this
}
emit(type, ...args) {
var listeners = this.eventListeners[type]
if (listeners) {
for (var i = 0 ; i < listeners.length; i++) {
var handler = listeners[i]
handler.call(this, ...args)
}
}
}
}
var emmiter = new EventEmmiter()
emmiter.on('foo',() => {})
emiter.emit('foo')
emiter.off()
流stream
-
流的模型: 搬砖,中间站一排人, 每个人旁边都有缓存区。就算中间有人走开一会,也不会断开。 模型2: 水通过管子往下面的水池子流,持续层层往下流。 通过水龙头控制流,下面快满了水龙头关,下面快枯了没谁了就开。
-
流主要解决的问题1):内存不够的问题,不管是数据的传输还是读写转换,不能一下子处理都需要一段一段处理,通过流,内存就会占用的变小。 问题2);如果有多个流(可读,可写,双工,转换)流,可串起来 file => rs => gzip => conn => conn => gunzip => ws => file
rs.pipe(gzip).pipe(ws).pipe(conn)
-
流的类型 可读流:从文件出来数据 可写流:只接收数据,最后处理这个数据(一般只负责写到硬盘里或者网络里, 到达网路的另一端后就不再管了) 双工流:可读可写流, 最典型的就是TCP连接。 进来的和出去的不一定有关系 转换流:出来的数据是进去的数据转换来的,比如zip 压缩流
-
压缩流 zlib 的 Gzip
const fs = require('fs')
const zlib = require('zlib')
var compressStream = zlib.createGzip()
var file = './3.rmb'
var rs = fs.createReadStream(file)
var ws = fs.createWriteStream('./3.gzip')
rs.on('data', data => {
if (compressStream.write(data) === false) {
rs.pause()
}
})
compressStream.on('drain', () => {
rs.resume()
})
compressStream.on('data', data => {
if(ws.write(data) === false) {
compressStream.pause()
}
})
ws.on('drain', data => {
compressStream.resume()
})
rs.on('end', () => {
compressStream.end()
})
compressStream.on('end', () => {
ws.end()
})
//以上可写成如下一句
rs.pipe(compressStream).pipe(ws)
- createReadStream,createWriteStream
var fd = fs.open(path)
//fs.read 可以一点一点读出来
fs.read(fd, buffer,offset, length, position, callback)
(从这个文件里,放入这个buf的,这个位置开始的位置上写入,读取1024个字节,这个位置开始读,(读完触发)=> {})
position >= filesize时一定要this.push(null)
fs.write(fd, chunk, 0, chunk.length, position, callback)
- 进程process
process.title
peocess.cwd() //打印当前工作目录 current working directory
== path.resolve('.')
pocess.constructor
process.chdir() //changedirectory
homw, homepath等属性
pid 进程id
ppid 谁启动这个进程
主要方法: stdout标准输出流 stderr标准错误流 stdin标准输入流 能接受别人发给这个进程的数据,来达成进程间通讯的目的
foo | bar | baz foo进程的输出变成bar输入,bar的输出变成baz的输入,baz输出到控制台
pipe到后面端口里 echo hello | md5sum.exe 算出来hello的md5被输入控制台上
echo hello | md5sum.exe | split -b 10 输出的md5的每10个字节为一个文件导出
例子:
//文件1 : 拿到可写流后加上i后输出
process-stream1.js
var i = 0
setInterval(() => {
process.stdout.write('a' + i++)
} ,1000)
//对一个进程来说可写流,出去之后对操作系统来说时可读流
//文件2: 可读流接收到后log出来
process-stream2.js
process.stdin.on('data', d => {
console.log(d.toString())
})
//文件3: 小写转大写转换
process-in-out.js
process.stdin.on('data', data => {
process.stdout.write(data.toString().toUpperCase())
})
//执行:
process-stream1.js | process-in-out.js | process-stream2.js
例子2:两个端口同步
//一个文件
const net = require('net')
const fs = require('fs')
const server = net.createServer(conn => {
conn.on('error;, () => {})
process.stdin.pipe(conn)
conn.pipe(process.stdout)
})
server.listen(999, () => {
console.log(server.address())
})
//另一个文件
const net = require('net')
net.connect(9999, '10.1.1.1').pipe(process.stdout)
npm的package.json链接
- name: 包的名字字符串
- version: 版本号三位数 "0.0.1"
- description: 字符串描述 keywords: 数组,被搜索的关键词
- homepage: 链接
- bugs: 对象里面包括url提交bug的页面,email可以把bug用邮件发
- license:
- files:
- main:包的入口文件
- bin:对象里又多个字段,{"myapp":"文件路径", "myfoo":"文件路径"} 命令行工具 ,可执行文件
- man:帮助文档
- scripts:对象 "scripts":{ "foo":"node foo.js", "bar":"ls -lha", "start": "node ./bin/www", "test": , "build": , "dev": , } 当npm run foo的时候等于执行node foo.js 也就是npm run中的run等于scrips字段里相应的内容。只有test和start可以略过run, npm start即可
- dependencies:{自己依赖的第三方代码} 通过npm install下载自己所有的依赖包。^兼容版本, 主版本号第一个数不改变就行。
express框架
用函数队列的方式来处理每一个网络请求
node的event loop:
使得Node.js可以在单线程执行非阻塞I/O操作(非阻塞: 遇到异步操作调一下就离开,把自己的异步操作卸载给操作系统)。 等系统执行完异步操作后通知Node.js 可以把回调放到poll队列去执行。
步骤:
- INIT (当Node.js开始初始event loop)
- INPUT SCRIPT (执行初始输入代码)
- 每遇到异步代码往各个事件循环的不同阶段(每个阶段都是先进先出的队列)安排函数
- timers:执行被setTimeout() 【注意如果设置0自动被改成1毫秒】 和setInterval()设定的到时间的函数.
- pending callbacks:被延迟到下一圈的I/O回调函数,poll阶段来不及执行的函数
- idle, prepare: 内部系统
- poll: 第一件是计算在这个阶段可以停顿多久以及等待I/O;
第二件是执行除了setTimeout,setInterval, setImmediate,close以外的所有回调函数。
- check: setImmediate()的回调函
- close callbacks: close的回调函数
- 额外:nextTick()的函数不管当前是什么阶段,走下一个阶段之前先执行。也就是每个阶段间隙之间执行。特殊:如果nextTick里又安排nextTick无限安排会导致程序一直卡在间隙里。其它异步函数是安排在下一轮。nextTick优先于promise.then。
- 开始进入事件循环。从timer开始执行自己阶段里被安排的到时间的函数,执行时如果又遇到异步代码继续往各个阶段安排函数。把自己阶段里的所有函数都执行完继续走下一个阶段。
- 一直循环执行,等到所有阶段都没有被安排函数就会退出node