本文内容:

Node.js一个最简单的入门学习笔记,可能大部分参考了Node.JS中文网

简单地Web服务器

const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('你好世界\n')
})

server.listen(port, hostname, () => {
console.log(`服务器运行在 http://${hostname}:${port}/`)
})

这里引入了http模块

设定了本地监听的地址和监听端口号为127.0.0.13000

同时使用http.createServer来创建服务,这里的http.createServer是非阻塞异步函数,采用了回调的方式来进行异步处理请求和发送响应包

其中(req, res) => {...}采用了箭头函数来创建

其中设置了响应包的statusCode以及Header,气质最后的消息体设置了你好世界

同时server.listen在监听本地之后,就会触发回调函数来打印出我们监听的地址和端口号

image-20200819194752774

image-20200819194922098

程序退出

const express = require('express')

const app = express()

app.get('/', (req, res) => {
res.send('你好')
})

const server = app.listen(3000, () => console.log('服务器已就绪'))

对于这个Web服务器,正常情况下是永远不会正常退出的(即除了你在外面Ctrl+C

如果说想在程序的其他地方关闭server,如果采用process.exit()退出,则会退出整个程序,可能正在等待或者运行的程序都会退出

但这种采用信号量SIGTERM来进行进程处理

const express = require('express')

const app = express()

app.get('/', (req, res) => {
res.send('你好')
})

const server = app.listen(3000, () => console.log('服务器已就绪'))

process.on('SIGTERM', () => {
server.close(() => {
console.log('进程已终止')
})
})
  • SIGKILL 是告诉进程要立即终止的信号,理想情况下,其行为类似于 process.exit()

  • SIGTERM 是告诉进程要正常终止的信号。它是从进程管理者(如 upstartsupervisord)等发出的信号

如果说程序内部的其他函数想要结束进程是,只需要调用

process.kill(process.pid, 'SIGTERM')

外部程序需要得知运行的Node的进程pid即可发送信号关闭

Node.js环境变量

可以通过

process.env

来获取和修改

image-20200819202058322

Node.js命令行参数

process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`)
})

image-20200819202456001

可以看到通过process.argv得到了命令行参数,通过forEach函数以及回调函数来列举出来

不过这里同样打印出了我们的nodejs文件路径

这里可以通过后面加上slice(2)指出从第三个参数开始

process.argv.slice(2).forEach((val, index) => {
console.log(`${index}: ${val}`)
})

image-20200819202713949

大概先这样,对于复杂的name=a2u13以及--name=a2u13需要第三方库解析即可,这里先不赘述

exports暴露接口

const car = {
brand: 'Ford',
model: 'Fiesta'
}

const bus = {
saily: 'Azure',
mosi: 'Monly'
}

exports.car = car
exports.bus = bus
const obj = require('./sky')

console.log(obj.car)

打印结果如下:

{ brand: 'Ford', model: 'Fiesta' }

如果使用module.exports来暴露

const car = {
brand: 'Ford',
model: 'Fiesta'
}

module.exports = car
const car = require('./sky')

console.log(car)
{ brand: 'Ford', model: 'Fiesta' }

不同的是,module.exports只暴露了一个对象作为接口

npm运行任务

npm run <task-name>

package.json文件中写入即可

{
"scripts": {
"watch": "webpack --watch --progress --colors --config webpack.conf.js",
"dev": "webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
},
}
npm run watch
npm run dev
npm run prod

package.json属性

http://nodejs.cn/learn/the-package-json-guide

这里不复制粘贴了,有需求可以自己查

Node.js事件循环

调用堆栈

Javascript几乎所有的I/O操作比如文件读取、网络请求都是非阻塞的,如果发现被阻塞了,不好意思出异常了

事件调用堆栈是后进先出的(LIFO),即后边来的事件优先处理

事件循环不断地检查调用堆栈,以查看是否需要运行任何函数。

当执行时,它会将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
console.log('foo')
bar()
baz()
}

foo()

简单地例子,按照LIFO的处理顺序,函数执行的打印应该如下:

foo
bar
baz

感觉是不是顺序执行?

图片来自于node.js中文网

http://nodejs.cn/learn/the-nodejs-event-loop

从第一张堆栈图来看,node在每次处理一个函数时,会依次加载内部函数到堆顶,然后如果内部函数同样具有结构体,则同理压入栈低,如果执行的是一个包装好的函数(console.log),则执行完直接出栈

消息队列

const baz = () => console.log('baz')

const foo = () => {
console.log('foo')
setTimeout(() => console.log('bar'), 0)
baz()
}

foo()

这里bar本来是个单独函数,但我感觉这么写可以体现出我们回调的特性,就这么写了

执行上面的程序,打印结果如下:

foo
baz
bar

为什么这里设置了sertTimeout0,还是会将回调推迟到最后呢?

通过上图执行流程可知,node在遇到setTimeout之后,会将setTimeout出栈,但是他所调用的回调函数,却被放入了消息队列

在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。 类似 onLoad 这样的 DOM 事件也如此。

事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。

消息队列的等待执行状态实际上也不是由setTimeout所维持,是由浏览器提供支持(这里我有个疑问,对于node而言,是谁在维持这个状态呢?)

总而言之

处理优先级:调用堆栈>消息队列

作业队列

const baz = () => console.log('baz')

const foo = () => {
console.log('foo')
setTimeout(() => console.log('bar'), 0)
new Promise((resolve, reject) =>
resolve('应该在 baz 之后、bar 之前')
).then(resolve => console.log(resolve))
baz()
}

foo()

这个玩意我可以理解为我要插队了

执行结果为

foo
baz
应该在 baz 之后、bar 之前
bar

这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。

在当前函数结束之前 resolvePromise会在当前函数之后被立即执行。

调用优先级:调用堆栈>作业队列>消息队列

process.nextTick

这个玩意我可以理解为:

他在处理完当前事件循环流程后,就要执行

告诉V8要尽快处理这个任务,而不是把他插入队列当中

image-20200820110958412

优先级:调用堆栈>process.nextTick>作业队列>消息队列

setImmediate

image-20200820111327883

image-20200820111622939

作为 setImmediate()参数传入的任何函数都是在事件循环的下一个迭代中执行的回调。

setImmediate()setTimeout(() => {}, 0)process.nextTick() 有何不同?

  • process.nextTick() 的函数会在事件循环的当前迭代中(当前操作结束之后)被执行。 这意味着它会始终在 setTimeoutsetImmediate 之前执行。

  • 延迟 0 毫秒的 setTimeout() 回调与 setImmediate() 非常相似。 执行顺序取决于各种因素,但是它们都会在事件循环的下一个迭代中运行。

可以看到process.nextTick很牛逼,抢先执行,而setImmediatesetTimeout就很low,不过给setTimeout加点延迟,效果就不一样了

JavaScript Promise

Promise是用来处理异步代码的一个感觉很NB的东西

当 promise 被调用后,它会以 处理中状态 开始。 这意味着调用的函数会继续执行,而 promise仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。

被创建的 promise 最终会以被解决状态被拒绝状态结束,并在完成时调用相应的回调函数(传给 thencatch)。

可能直接写概念不好理解,我这里按照例子写了一些个人的看法理解

const fs = require('fs')

const getFile = (fileName) => {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) {
reject(err) // 调用 `reject` 会导致 promise 失败,无论是否传入错误作为参数,
return // 且不再进行下去。
}
resolve(data)
})
})
}

getFile('/etc/hosts')
.then(data => console.log(data.toString()))
.catch(err => console.error(err))

重点在于reject、resolve、then、catch

image-20200820153903301

可以看到是通过resolve传入在外部通过then来处理

如果我们传入的文件不存在呢?

image-20200820153951790

则通过reject传递进入了catch处理

剩下的感觉很牛逼,到时候专门开个专题来写一下Promise的有关知识

async/await

异步这一块有时间再写一下他的实现原理

Node.js 事件触发器

感觉就是定下个事件任务,然后直接触发

const EventEmitter = require('events')
const eventEmitter = new EventEmitter()

eventEmitter.on('start', number => {
console.log(`开始 ${number}`)
})

eventEmitter.emit('start', 114514)

image-20200820162459079

Node.js读写文件

读文件:

异步:

const fs = require('fs')

fs.readFile('/Users/joe/test.txt', 'utf8' , (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})

同步:

const fs = require('fs')

try {
const data = fs.readFileSync('/Users/joe/test.txt', 'utf8')
console.log(data)
} catch (err) {
console.error(err)
}

写文件:

异步:

const fs = require('fs')

const content = '一些内容'

fs.writeFile('/Users/joe/test.txt', content, { flag: 'a+' }, err => {
if (err) {
console.error(err)
return
}
//文件写入成功。
})
  • r+ 打开文件用于读写。
  • w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
  • a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
  • a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。

同步:

const fs = require('fs')

const content = '一些内容'

try {
const data = fs.writeFileSync('/Users/joe/test.txt', content)
//文件写入成功。
} catch (err) {
console.error(err)
}

模块方法

异常处理

Node.js抛出异常不是抛出一个字符串常量,而是抛出一个错误对象

throw new Error('Ran out of coffee')

class NotEnoughCoffeeError extends Error {
//...
}
throw new NotEnoughCoffeeError()

处理异常则和Python我感觉差不多

try {
//lines of code
} catch (e) {}

对于没有捕获的异常,则需要采用process来进行监听捕捉

process.on('uncaughtException', err => {
console.error('There was an uncaught error', err)
process.exit(1) //mandatory (as per the Node.js docs)
})

对于异步的错误,比如Promise

doSomething1()
.then(doSomething2)
.then(doSomething3)
.catch(err => console.error(err))

doSomething1()
.then(() => {
return doSomething2().catch(err => {
//handle error
throw err //break the chain!
})
})
.then(() => {
return doSomething2().catch(err => {
//handle error
throw err //break the chain!
})
})
.catch(err => console.error(err))

下面的例子通过打破链条而实现了每个then的捕捉(这样方便得到错误的位置?

对于asyncawait

async function someFunction() {
try {
await someOtherFunction()
} catch (err) {
console.error(err.message)
}
}

其他

至于Buffer以及Stream等模块,我感觉这方面内容可能有些复杂

就不打算这么略写了,等遇到实际应用场景则来看看