一 nodejs是什么
- nodejs是一个基于 Chrome V8 引擎上的 JavaScript 运行时
- Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型
简单来说:Node.js就是可以让JS脱离浏览器能够运行,而且能操作文件数据库等高级应用开发功能。
nodejs的高性能,是针对于web场景,高并发,I/O密集
- I/O密集:文件操作,网络操作,数据库
- CPU密集:压缩,解压,加密,解密等算法
二 非阻塞I/O
- 阻塞: I/O时进程休眠等待,I/O完成时进行下一步
- 非阻塞: I/O时函数立即返回,进程不等待I/O完成 (异步的感觉)
三 事件驱动
- I/O等异步操作结束后的通知(遇到I/O,不等I/O执行完直接执行下面程序,等其执行完发射一个事件,对事件找其相应的处理程序)
- 观察者模式
四 环境
4.1 模块规范 commonjs
(function(exports, require, module, _filename, _dirname){ console.log(‘this is a test’) })
- 每个文件就是一个模块,有自己的作用域
- 在模块内部module变量代表模块本身
- module.exports属性代表模块对外接口
4.2 require特性
- require运行时加载,加载一个模块的时候,其所有语句会被执行。(import编译时加载)
- module被加载的时候执行,加载后缓存,也就是不会再执行
- 一旦出现某个模块被循环加载,就只输出已经执行的部分,还未执行的部分不会输出 如b依赖a a依赖b
// main.js
const modA = require('./module_a')
const modB = require('./module_b')
console.log(modA.test)
console.log(modB.test)
// module_a.js
module.exports.test = 'A'
const modB = require('./module_b')
console.log('modA: ',modB.test)
module.exports.test = 'AA'
// module_b.js
module.exports.test = 'B'
const modA = require('./module_a')
console.log('modB:',modA.test)
module.exports.test = 'BB'
node main.js将会输出
modB: A
modA: BB
AA
BB
- 加载执行a模块, a模块的test是A,走到加载执行b模块, 这里a模块还没有执行完
- 进去b模块, b模块的test是B, 又走到a模块, 但是a模块已经执行过, 不再加载执行, 此时modA.test=“A”, 打印modB : A, 之后改变b模块的test为BB, 此时B模块走完
- 继续走a模块, console.log('modA: ',modB.test), 打印modA : BB,再到a模块的test是AA
- 再继续回到main.js继续执行第二行代码,module_b已执行不再执行了
正式项目里有可能会有多个文件循环依赖,是造成不可预知的问题,所以应该避免循环依赖
4.3 module.exports & exports
- 其实exports是module.exports的指向,即const exports = module.exports
- 如果exports不指向module.exports,也就毫无意义
eg: exports = {test: 3}
这样就改变了exports的指向,模块里不会导出test - 可以向exports添加属性 exports.test = 3, 这样ok的
也就是说exports是module.exports的一个引用,或者说是快捷方式.
实际开发中遇到的问题,一个文件里import和module.exports或者exports混用:
4.4 process
1. 参数相关
- argv 获得运行命令所有参数(数组)
- argv0 命令的第一个参数
- execArgv 文件路径之前的node命令相关的参数
- execPath node的路径
最常用的是argv,不过现在获取命令行参数都用 yargs
2. 环境相关
- env 当前电脑的环境信息
- __dirname: 获得当前执行文件所在目录的完整目录名
__filename: 获得当前执行文件的带有完整绝对路径的文件名
process.cwd():获得当前执行node命令时候的文件夹目录名
./: 文件所在目录
3. timer
- setImmediate
- nextTick 快于 setTimout和setInterval 快于 setImmediate
- nextTick将其事件放在当前队列的最后一个
- setImmediate将其事件放在下一个队列的队首
- setTimeout和setInterval介于两者之间
- 但是process.nextTick里面嵌套process.nextTick是很不好的事情,复杂的逻辑会导致下一个队列不能及时执行
- 所以一般尽量用setImmediate代替nextTick, setImmediate也是后来node出现的,用于优化
setImmediate(()=>{
console.log('setImmediate')
})
process.nextTick(() => {
console.log('nextTick')
})
setTimeout(()=>{
console.log('setTimeout')
},0)
setTimeout(()=>{
console.log('10setTimeout')
},10)
// nextTick
// setTimeout
// setImmediate
// 10setTimeout
五 基础API
- path
- fs
- events
- Buffer
- 解决异步回调地狱 promisify
使用promisify和同步,异步的区别
const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const rPath = path.join(__dirname)
function f1(){ // 分别调用 f2 f3 f4,看执行情况
f2()
console.log(111)
}
function f2(){
const files = fs.readdirSync(rPath)
console.log('--f2--', files)
console.log(222)
}
function f3(){
fs.readdir(rPath, (err, files) => {
console.log('--f3--', files)
})
console.log(333)
}
async function f4(){
const files = await promisify(fs.readdir)(rPath)
console.log('--f4--', files)
console.log(444)
}
f1()
const server1 = http.createServer((req, res) => {
const filePath = path.join(root, req.url)
fs.stat(filePath, (err, stats) => {
if(err){
res.statusCode = 404
res.end(`${filePath} is not a file or directory`)
return
}
if(stats.isFile()){
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
fs.createReadStream(filePath).pipe(res)
}else if(stats.isDirectory()){
fs.readdir(filePath, (err, files) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end(files.join(','))
})
}
})
})
const server2 = http.createServer(async (req, res) => {
const filePath = path.join(root, req.url)
try{
const stats = await promisify(fs.stat)(filePath)
if(stats.isFile()){
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
fs.createReadStream(filePath).pipe(res)
}else if(stats.isDirectory()){
const files = await promisify(fs.readdir)(filePath)
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end(files.join(','))
}
}catch(err){
res.statusCode = 404
res.setHeader('Content-Type', 'text/plain')
res.end(`${filePath} is not a file or directory`)
}
})
import { promisify } from 'util'
const getCopyPatterns = async plat => {
// 获取要复制的node包
const root = 'root/src/lib';
const mainSoFiles = await promisify(fs.readdir)(root).catch(err => {
throw chalk.red(err)
});
if (!~mainSoFiles.indexOf(plat)) {
return [];
}
const mainSoFileDir = path.join(root, plat);
const platformFiles = await promisify(fs.readdir)(mainSoFileDir).catch(err => {
throw chalk.red(err)
});
const copyPatterns = [];
platformFiles.forEach(async filename => {
const filedir = path.join(mainSoFileDir, filename);
const fsStats = await promisify(fs.stat)(filedir).catch(err => {
throw chalk.red(err)
});
if (fsStats.isFile() && /\.DS_Store$/.test(filename)) return;
copyPatterns.push({
from: filedir
});
});
return copyPatterns;
}