本文将会从NodeJS基础开始到企业及Web开发
提供几个实战案例的实现全过程
- 静态资源服务器
- 代码本地构建
- 单元测试
- UI测试
- headless爬虫
计划
- NodeJS核心API
- 静态资源服务器
- 项目代码构建
- 单元测试 & UI测试
- headless爬虫
- 回顾总结
必备基础前提
- 了解js和ES6的语法
- 可以简单使用命令行工具
- 了解最基本的HTTP协议
目录
- NodeJS介绍
- 调试 & 项目初始化
- 基础API
- 静态资源服务器
- 代码本地构建
- 单元测试 & 发布
- UI测试
- headless爬虫
第一章 NodeJS是什么?为什么偏爱NodeJS
NodeJS是什么
- NodeJS是一个运行在chrome的V8引擎上的JavaScript的runtime
- 注意!NodeJS并不是一门语言
- NodeJS的特性:事件驱动,非阻塞I/O模型
非阻塞I/O的概念
- 阻塞:I/O时进程休眠等待I/O完成后进行下一步
- 非阻塞:I/O时函数立即返回,进程不等待I/O完成
事件驱动
- I/O等异步操作结束后的通知
- 观察者模式
为什么偏爱NodeJS?
- 前段职责范围变大,统一开发体验
- 在处理高并发,I/O密集场景性能优势明显
CPU密集 VS I/O密集
- CPU密集:亚索、解压、加密、解密
- I/o密集:文件操作、网络操作、数据库访问
Web常见场景
- 静态资源读取:静态HTML、CSS、JS就是cpu算一下文件的对应关系并读取
- 数据库操作
- 渲染页面
高并发应对之道
- 增加机器数
- 增加每台机器的CPU数目-多核(针对运算密集)
进程
进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
多进程:启动多个进程,多个进程可以一块自行任务。
线程
- 线程:进程内的一个相对独立的、可调度的自行单元,与同属一个进程的线程共享进程资源。
- 多线程:启动一个进程,在一个进程内启动多个县城,这样多个线程可以一块自行多个任务。
NodeJS的单线程
- 单线程知识针对主进程,IO操作系统底层多线程调度
- 实际上,NodeJS只是负责单进程的听,至于操作系统怎么处理IO,NodeJS不管
- 单线程不是单进程
NodeJS的性能好的前提
- web场景:IO密集,高并发(不能是CPU密集)
NodeJS常用场景:
- Web Server
- 本地代码的构建:前段的代码异常复杂,直接书写的代码在浏览器上是不能直接工作的,要进行一些转化,这个过程叫做构建。
- 使用工具的开发:比如爬虫之类的,出于语法,效率不是最佳的。
第二章 环境 & 调试
本章的知识内容:
- CommonJS
- 引用系统内置模块&引用第三方模块
- module.exports与exports的区别及注意事项
- global变量
- process变量
- debug
在官方网站上有两个版本,意思LTS,可以用于开发环境,这是一个稳定的版本,另一个是current,是比较新的版本。安装nodejs之后npm也会自动安装。
nodejs的环境:
- CommonJS:这是NodeJS所用的模块规范,
- global:全局对象,没有bom和dom和window,取而代之的是global
- process:代表当前执行的进程,这是挂在global下面的
CommonJs
这是nodejs模块管理的规范,’
- 每个文件是一个模块,有自己的作用域
- 在模块的内部module变量代表模块本身
- module.exports属性代表模块对外接口
记住三个步骤来定义一个模块:
- 要定义一个模块就需要自己创建一个文件
- 想要在模块内部做一些输出就需要用module.exports把想输出的东西放在这个属性中
- 使用require来调用模块
require规则
1. /表示绝对路径, ./表示相对于当前文件的路径
2. 支持js\json\node扩展名,不写依次尝试
3. 不写路径这认为是build-in模块或者各级的node_modules内的第三方模块
EXAMPLE:自定义一个模块暴露出接口并加载
02_custom.js
[code]
console.log(‘This is a module’);console.log(‘This is a module’);
const testVar = 100;
function test(){ console.log(testVar);}
module.exports.testVar = testVar;module.exports.testFn = test;
[/code]
03_require.js
[code]</pre>
const mod = require(‘./02_cusmod’);
console.log(mod.testVar);
mod.testFn();
<pre>
[/code]
执行结果为
简单的说明一下执行逻辑,在第一个js文件中打印了一句话,使用函数test输出了一个产量100. 并暴露处两个接口,一个接口是常量,另一个接口是打印常量的函数。在第二个文件中使用require进行接口的调用,在调用的时候就会去执行这个js文件,因此输出了定义的“This is a module”。接下来直接打印了模块1中的常量,然后调用了了模块1中的函数,由于函数的功能是打印这个常量,因此我们可以看见常量被打印了两次
4. module被加载的时候执行,加载后缓存。也就是说只加载一次,第二次就直接用放在内存中的结果了,不会重复加载。
5. 一旦出现某个模块被循环加载,就只能输出已经执行的部分,还未执行的部分不会输出。
EXAMPLE:循环加载不会被执行
05_modA.js
[code]
module.exports.test = ‘A’;
const modB = require(‘./05_modB’);
console.log(‘modA:’,modB.test);
module.exports.test = ‘AA’;
[/code]
05_modB.js
[code]
module.exports.test = ‘B’;
const modA = require(‘./05_modA’);
console.log(‘modB:’,modA.test);
module.exports.test = ‘BB’;
[/code]
05_main.js
[code]
const modA = require(‘./05_modA’);
// This line won’t be excuted, Because modB has been loaded.
const modB = require(‘./05_modB’);
[/code]
在命令行中执行主函数后得到
之后我们把主函数的第二句注释放开,加载模块B(其实模块B已经在加载模块A的时候加载了),执行后我们发现执行结果并没有改变,证明模块B被循环加载,就只能输出已经执行的部分(在执行模块A的时候模块B已经被执行了),还未执行的部分不会输出。
引用系统内置模块 & 引用第三方模块
在应用这些模块的时候不需要假路径。接下来要引用的是fs模块,在之后的学习中会经常用到这个模块。
EXAMPLE:使用系统内置模块
[code]
const fs = require(‘fs’);const fs = require(‘fs’);
const result = fs.readFile(‘./06_fs.js’,(err,data) => { if(err){ console.log(err); }else{ console.log(data); }});
console.log(result);
[/code]
执行结果为
err是出现错误的情况下,会返回到err中,没有错误的时候会是一个空对象,我们可以同打印err的方式来知道程序是不是有错误,data就是读到的内容。出现undefined的原因需要注意需要注意!读文件是异步操作,异步操作当前是没有返回结果的,因此console.log(results)是拿不到文件内容的,则返回undefined,因此我们要使用毁掉函数来打印结果console.log(data);才可以在执行的时候就获取到执行的结果。buffer中是一些十六进制的数字,因为fs是来操作二进制流的,要读出来可以将其转换为字符串,直接data.toString()就好了。这样我们的代码就读进来了(已经加了toString的代码)。
接下来看一下应用第三方模块,这里引入chalk这个很有意思的包,这个包的作用是改变terminal中输出字体的样式,比如颜色,下滑线等等,进一步了解需要查阅API这里就不细说了。
由于我们并没有安装这个模块,因此使用npm进行安装,直接npm install chalk就安装好了,安装好了之后就会看到有了一个文件夹node_modules,第三方模块都在这里。
EXAMPLE:引入第三方模块
[code]
const chalk = require(‘chalk’);
console.log(chalk.red(‘This is red!’));
[/code]
执行结果:
这里我们重点说明一下node_modules这个文件夹的作用,这是我们使用npm生成的,我们没有指明路径,而是直接require了包的名字,这里的执行机制是先找了路径,发现没有,接下来就会自动进入node_modules文件夹里面找,这就是为什么我们只需要对第三方的包使用名字来require的原因。我们可以发现这个文件夹在安装了chalk后产生了很多的文件夹,这些文件夹是chalk的依赖,之所以这样放在外面是为了提高执行效率不乣一层一层的找了,都在第一层,这是新的npm的特征。
如果npm install xxx -g的话就会全局安装,全局安装意思是安装在了这台电脑上,在哪里都可以用了,全局安装的目录可以使用npm root -g来进行查看,建议尽量不要使用这种方法,因为会占用很多的系统资源,性能也会降低。
exports和module.exports的区别和注意事项
exports就相当于是module.exports的一个快捷方式,但是需要特别注意的一点是,由于它是快捷方式,因此不能够改变它的指向,因为模块的输出在commonJS的规范中,永远是这个对象是什么模块的输出就是什么。这是什么意思呢,来看一段代码
[code]
exports = {
a:1,
b:2,
test:100
};
[/code]
这样一来exports和module.exports就没有关系了,也不能够输出了,相反的
[code]
module.exports = {
a:1,
b:2,
test:100
};
[/code]
因此,总结exports可以理解为module.exports的快捷方式,我们可以使用它,并且和module.exports一样,但是我们不能够改变它的指向否则就会没有关系了,就跟一个普通的对象没有区别了。
global对象
在JavaScript中,全局的属性在window中,找NodeJS中全局属性在global中。
- commonJS
- Buffer、process、console
- timer
EXAMPLE:局部变量和全局变量
08_global.js
[code]
const testVar = 1000;
global.testVar2= 200;
module.exports.testVar = testVar;
[/code]
08_main.js
[code]
const mod = require(‘./08_global’);
console.log(mod.testVar);
console.log(testVar2);
[/code]
运行结果
可以看到使用global生命的全局变量在main中可以直接调用输出,而const定义的局部变量这需要使用module.exports来把接口暴露出来才能够被调用。
process的对象
1.参数相关有是个比较重要的概念,我们经常在NodeJS启动的时候来看一下启动脚相关的一些参数,argv这个对象就可以实现这个功能
10_argv.js
[code]
/*
argv
argv0
execArgv
execPath
*/
const {argv,argv0,execArgv,execPath} = process;
argv.forEach(item => { console.log(item);});
console.log(argv0);console.log(execArgv);console.log(execPath);
[/code]
运行结果
说明:
开头使用的是ES6的语法,使用的都是process的一些子对象,作为process的属性出现。后面可以跟上想传入的文件的命令,这样可以在启动文件的时候传入一些自定义的参数,这个是非常有用的。调试用的–inspect参数写在文件前面的是不会进入argv的。
argv:启动process的时候的参数,返回一个数组,因此要用forEach来输出,输出的结果是1. node所安装的路径2.当前执行文件的路径
argv0:不太常用,输出argev的第一个类似的输出,node。
execArgv:统计写在文件前的参数如–inspect
execPath:调用脚本的路径
小结,这是个参数中argv用得最多。 可以从外面自定义一些命令来传入脚本
2.环境
env 输出各种环境属性
[code]
const {env} = process;
console.log(env);
[/code]
cwd 打印当前命令执行的路径
[code]
console.log(process.cwd());
[/code]
输出结果
setImmediate: 传入一个方法,让它等一会再调用,注意,这里等的不是时间,而是等下一个时间队列,NodeJS在执行的时候会不断的检查事件队列,简单的理解就是说,作为一个异步,到了同步的东西都执行完了再执行它。所以和时间无关,只有一个参数传入一个function,
[code]
setImmediate(()=>{setImmediate(()=>{ console.log(‘setImmediate’);});
(()=>{ console.log(‘ ‘);},0)
process.nextTick(()=>{ console.log(‘nextTick’);});
[/code]
执行结果:
从执行的结果我们可以看出结论:
执行顺序是:nextTick > > setImmediate
一个根本区别是,nextTick是把自己这个function放在了当前队列的最后一个,而setImmediate是放在了下个对列的对首, 放在了它两中间。
nextTick如果里面再调用一个nextTick,可能会导致一部IO操作无法执行,所以一般使用setImmediate就可以了
debug
inspect
- 在chrome上打开chrome://inspect
- 在http://www.nodejs.org/en/docs/inspector中找到链接安装chrome的NodeJS调试工具
- 14_debug.js
[code]
function test1(){function test1(){ const a = parseInt(Math.random() * 10);
const b = parseInt(Math.random() * 10);
const c = test2(a, b);}
function test2(a, b){ if(a > b){ a += a*2; }else{ b -= a; }
return a + b;}
test1();
[/code]
4. 命令行输入
注意这里比起正常的启动使用了–inspect进入调试模式,由于不能提前打断点,因此使用了-brk来使程序在入口的时候停下来,然后再写文件名。
5. 这样在chrome刚才打开的页面中就出现了node,点击inspect进入调试环境。这是从入门原生的调试环境就可以打断点了,右上角的两个常用按钮分别是执行到下一个断点和单步调试。
另外也可以使用IDE来调试,比如VS Code,用IDE比较方便,由于用chrome调试并不是很麻烦因此这里不过多的介绍了。
第三章 最最基础的API
本节将会介绍四个基础API
- path
- Buffer
- event
- fs
学习API最好的方法是从官方文档学习http://nodejs.cn/api/
path
处理和路径有关的一切
normalize
[code]
const {normalize} = require(‘path’);
console.log(normalize(‘/usr//local/bin’));
console.log(normalize(‘/usr//local/../bin’));
[/code]
运行结果
我们可以看到,一个语句执行的时候帮我们把斜线给去掉了。
第二个由于加了..就直接会把这一级目录给略过了。
join
拼接路径,无论有没有斜线都会帮助我们拼接成一个规范的路径,在执行过程中可能会自己调用normalize,总之就是相近一切办法生成一个合法的路径。也可以使用..,会直接跳过这级目录。
[code]
const {join} = require(‘path’);
console.log(join(‘/usr’,’local’,’bin/’));
[/code]
resolve
将一个相对路径解析成绝对路径
[code]
const {resolve} = require(‘path’);
console.log(resolve(‘./’));
[/code]
basename extname dirname
[code]
const {basename, dirname, extname} = require(‘path’);
const filePath = ‘/usr/local/bin/no.txt’;
console.log(basename(filePath));
console.log(dirname(filePath));
console.log(extname(filePath));
[/code]
从执行结果可以看出
basename:打印文件名+拓展名
dirname:打印当前执行文件的路径
extname:打印拓展名
parse format
parse:把文件名解析
format:parse的逆操作
[code]
const {parse, format} = require(‘path’);
const filePath = ‘/usr/local/node_modules/n/package.json’;
const ret = parse(filePath);
console.log(ret);
console.log(format(ret));
[/code]
sep delimiter win32 posix
[code]
const {const { sep, delimiter, win32, posix} = require(‘path’);
console.log(‘sep:’,sep);console.log(‘win sep:’,win32.sep);
console.log(‘PATH:’, process.env.PATH);
console.log(‘delimiter:’, delimiter);console.log(‘win delimiter:’, win32.delimiter);
[/code]
几个比较
[code]
const path = require(‘path’);
const mod = require(‘./02_cusmod’);
console.log(mod.testVar);
console.log(‘__dirname: ‘,__dirname);
console.log(‘process.cwd()’,process.cwd());
console.log(‘./ ‘, path.resolve(‘./’));\
[/code]
小结:
__dirname,__filename:总是返回文件的绝对路径
process.csw():总是返回执行node命令所在的文件夹
./:在require方法中总是相对当前文件所在的文件夹
在其他地方和process.cwd()一样,相对node启动文件夹
Buffer
- 用于处理二进制数据流
- 实例类似整数数组,但不是,因为它的大小固定,不能随便修改长度、追加内容
- C++代码在V8堆外分配物理内存
注意:js中是没有读取和操作二进制数据流的机制的
Buffer使用的频率相当搞,不需要require就可以直接使用,比较常用的是alloc和from
Buffer.alloc:创建一块空间
Buffer.from:实例化数组或字符串
[code]
console.log(Buffer.alloc(10));
console.log(Buffer.alloc(20));
console.log(Buffer.alloc(5,1));
console.log(Buffer.allocUnsafe(5,1));
console.log(Buffer.from([1,2,3]));
console.log(Buffer.from(‘test’));
console.log(Buffer.from(‘test’,’base64′));
[/code]
创建一块长度为10的空间,默认是用0来填充
创建一块长度为20的空间
创建长度为5的空间,里面填充1
allocUnsafe必须先重写内容再使用,建议平时不要用,只是速度比较快
from,给了一个数组或者字符串,把它实例化(默认utf8)
实例化字符串,默认使用了utf8的编码
实例化字符串,指定使用了base64的编码
Buffer.byteLength:返回一个字符串实际占用了几个字节(一个中文占的是三个字节)
Buffer.isBuffer();看一下一个对象是不是一个Buffer对象
Buffer.concat();拼接Buffer
[code]
console.log(Buffer.byteLength(‘test’));
console.log(Buffer.isBuffer({}));
console.log(Buffer.isBuffer(Buffer.from([1, 2, 3])));
const buf1 = Buffer.from(‘This ‘);
const buf2 = Buffer.from(‘is ‘);
const buf3 = Buffer.from(‘a ‘);
const buf4 = Buffer.from(‘test ‘);
const buf5 = Buffer.from(‘!’);
const buf = Buffer.concat([buf1, buf2, buf3, buf4, buf5]);
console.log(buf.toString());
[/code]
运行结果
buffer.length():打印buffer的长度,注意打印的是buffer总的长度,和里面放了多少东西没有关系
buffer.toString():用与转换字符串的,默认使用utf8,但是可以通过传入参数来改变字符
buffer.fill():用于填充一些其他的值
buffer.equals():两个buffer的内容是否相等,只比较内容,不比较内存中的位置
buffer.indexOf():查找字符在字符串中的位置
[code]
const buf = Buffer.from(‘This is a test!’);
console.log(buf.length);
const buf2 = Buffer.allocUnsafe(10);
buf2[0] = 2;
console.log(buf2.length);
console.log(‘==========’)
console.log(buf.toString(‘base64’))
console.log(‘==========’)
const buf3 =Buffer.allocUnsafe(10);
console.log(buf3);
console.log(buf3.fill(10, 2, 6));
console.log(‘==========’)
const buf4 = Buffer.from(‘test’);
const buf5 = Buffer.from(‘test’);
const buf6 = Buffer.from(‘test!’);
console.log(buf4.equals(buf5));
console.log(buf4.equals(buf6));
console.log(‘==========’)
console.log(buf4.indexOf(‘es’));
console.log(buf4.indexOf(‘esa’));
[/code]
buffer.copy():
StringDecoder
这两个可以用来处理中文字符,由于中文字符占用的是三个字节,因此不能够直接的输出,在使用stringDecader的时候需要先引入这个模块,并将其自定义,自定义的方法就是将其实例化,在实例化的过程中传入一个参数,这个参数是需要用到的编码,比如说utf8,它的工作原理大概是,stringdecoder并不会意识到自己在处理一些宽字节字符,而是意识到表示不了了,就先存在它的缓存里面,存在下次进行拼接,可以打印的时候才会打印出来。
[code]
const StringDecoder = require(‘string_decoder’).StringDecoder;const StringDecoder = require(‘string_decoder’).StringDecoder;const decoder = new StringDecoder(‘utf8’);
const buf = Buffer.from(‘中文字符串’);
for(let i = 0; i < buf.length; i +=5){ const b = Buffer.allocUnsafe(5); buf.copy(b, 0, i);
console.log(decoder.write(b));}
[/code]
Events(事件)
NodeJs的两个特性是事件驱动、异步IO,大部分NodeJs的API都采用的是异步事件驱动架构,当主进程遇到了一个IO请求的时候,把IO交给了底层,然后IO完成之后通知主进程。那么是如何通知主进程的呢?就是通过事件来告诉主进程的。对于一些事件(触发器)会周期性的触发命名事件来调用函数对象(监听器)。
所有能够触发事件的对象都是EventEmitter类的实例,这些对象开放了一个eventEmitter.on()函数,也就是说要使用事件就必须要继承这个类。另外这个函数允许将一个或多个函数绑定到会被对象触发的命名事件上。
对了,当EventEmitter对象触发一个事件的时候,所有绑定在该事件上的函数都会被同步的调用。监听器的返回值会被丢弃。
EXAMPLE:绑定多个事件,但只响应一次。
[code]
const EventEmitter =require(‘events’);
class CustomEvent extends EventEmitter{
}
const ce = new CustomEvent();
ce.once(‘test’,()=>{
console.log(‘test event’);
});
setInterval(() => {
ce.emit(‘test’);
},500);
[/code]
通过使用once,可以看到程序只执行了一次。
EXAMPLE:移除事件
[code]
const EventEmitter = require(‘events’);
class CustomEvent extends EventEmitter{
}
function fn1 (){
console.log(‘fn1’);
}
function fn2 (){
console.log(‘fn2’);
}
const ce = new CustomEvent();
ce.on(‘test’, fn1);
ce.on(‘test’, fn2);
setInterval(() => {
ce.emit(‘tets’);
},500);
(() => {
ce.removeListener(‘test’,fn2);
},1500);
[/code]
fs(文件系统)
文件IO是由简单的封装POSIX函数提供,通过require(‘fs’)来使用该模块,所有方法都有异步和同步的形式,
异步方法的最后一个参数都是一个灰调函数,传给回调函数的参数取决于具体的方法,但是要特别注意的是,回调函数的第一个参数都会保留给异常,如果操作成功完成,那么第一个参数会是null或者undefined。
当使用同步方法的时候,任何异常都会被立即抛出。可以使用try/catch来处理异常,或者让异常向上冒泡。
readFile & readFileSync读文件
注意,读出来的文件是16进制的,要设置编码utf8或者用toString才能够正常的读出来
EXAMPLE: 读文件
[code]
const fs = require(‘fs’);
fs.readFile(‘./32_readfile.js’,’utf8′, (err, data) => {
if(err) throw err;
console.log(data);
});
const data = fs.readFileSync(‘./02_cusmod.js’,’utf8′);
console.log(data);
[/code]
可以看到,即使把同步方法写在异步方法后面,也是先执行同步的操作,只有等同步操作完成了之后,异步的才可能被执行到。
另一点需要注意的是,文件读出来默认的是16进制的数据,因为使用Buffer的方式,转换可以使用toString()还有一个小技巧就是可以加上编码形式,如代码中的’utf8’。
同步的函数就是异步后面加上sync即可。由于NodeJS主要处理的是高并发的请求,因此同步的方式会造成阻塞,违背了NodeJS的优点,因此一般情况下不考虑同步的方法。
writeFile 写文件
有两种形式,第一种是直接写字符串,第二种是写Buffer
[code]
const fs =require(‘fs’);
fs.writeFile(‘./text’,’This is a test’,{
encoding: ‘utf8’
}, err => {
if(err) throw err;
console.log(‘done!’);
})
// 使用Buffer对象来写
const content = Buffer.from(‘this is a test.’);
fs.writeFile(‘./text1’,content, err => {
if(err) throw err;
console.log(‘done!’);
})
[/code]
第一个参数是要写的文件名,如果没有就会新近啊,./前面提到过是表示当前的文件夹,
stat 跟文件信息有关
[code]
const fs = require(‘fs’);
fs.stat(‘./34_stat.js’,(err,stats) => {
// 使用error判断文件是否存在的方法(不throw err)
if(err){
console.log(‘文件不存在’);
return;
};
// 是文件吗?
console.log(stats.isFile());
// 是目录吗?
console.log(stats.isDirectory());
// 打印所有的状态
console.log(stats);
});
[/code]
rename 重命名
[code]
const fs = require(‘fs’);
fs.rename(‘./text’,’test.txt’,err => {
if(err) throw err;
console.log(‘done!’)
});
[/code]
第一个参数是待更改的文件名,第二个参数是要改成的文件名。
unlink移除
[code]
const fs = require(‘fs’);
fs.unlink(‘./test.txt’,err =>{
});
[/code]
readdir 读文件夹
[code]
const fs = require(‘fs’);
fs.readdir(‘../’, (err,files) => {
if(err) throw err;
console.log(files);
})
[/code]
可以看到文件夹中的内容就被读出来了,这里需要注意的是,’../’两个点读的是上一级文件夹的内容,读当前文件夹是’..’,当然也可以用path来读。
mkdir创建一个文件夹
[code]
const fs = require(‘fs’);
fs.mkdir(‘test’,err => {
});
[/code]
rmdir删除一个文件夹
[code]
const fs = require(‘fs’);
fs.rmdir(‘test’,err => {
});
[/code]
watch监视
[code]
const fs = require(‘fs’);
fs.watch(‘./’,{
recursive: true
}, (eventType,filename) =>{
console.log(eventType, filename);
});
[/code]
watch可以用于监视文件夹,这个命令的参数recursive是是否迭代的意思(是否监视文件夹下子文件的变动),Example在文件40_watch中加了一个空格会出现changge,重命名了文件名为40的文件会出现改变。这个命令非常重要
以上就是fs中常用API了,接下来介绍几个比较有难度的。
readstream
stream是流的意思,可以理解成有方向的数据。有方向是指从一个设备流向了另外一个设备(在linux中所有的设备被抽象成了文件),即从一个文件流向了另外一个文件,流的的东西是数据。所以,stream必须有两个条件。
- 有方向
- 是数据
数据传输有两种方式,打个比方:
- 每接好一桶水就给出去,按照桶的方式给。
- 安自来水管,流出一点就给出去
显然stream是第二种,这种方式在很多场景下好处是很明显的。比如说电脑看电影,最开始电脑只有512Mb的时候,却也看得了1GB的电影,理论上来说电影要想读取的话得要整个放在电脑的内存里面,可是内存不够,因此stream就可以解决这个问题,一点点放在电脑上一点点消费。
EXAMPLE:输出到控制台
[code]
const fs = require(‘fs’);
const rs = fs.createReadStream(‘./41_readstream.js’);
// 输出到控制台
rs.pipe(process.stdout);
[/code]
pipe:指明了输出方向,这里还调用了前面提到过的process.stdout。
createReadStream:指明了输出什么数据。
mark!:stdin可以从控制台读回数据。
wirtestream 写操作
注意理论上只能够接受buffer,但是也可以接受string,能接受string是因为可以使用buffer.from或者同string来进行相互的转换。数字转换层String有很多方法,最简单的就是在数字后面加上一个空的字符。如num+’ ‘就可以了。
[code]
const fs = require(‘fs’);
const ws = fs.createWriteStream(‘./test.txt’);
const tid = setInterval(() => {
const num = parseInt(Math.random()*10);
console.log(num);
if(num<7){ ws.write(num + ”); } else { clearInterval(tid); ws.end(); } }, 200); ws.on(‘finish’,() => {
console.log(‘done!’);
});
[/code]
输出结果:
程序的逻辑是,生成一个随机数字,如果这个数字大于7就结束,小于7就继续生成随机数字,并把生成的随机数字写入到文件里面。可以看到数字就被写入到文件和控制台中了(控制台只是用来监视程序的,实际开发直接输入文件中就可以了),由于这里的程序结构,所有我们是拿不到第三个数字8的。
promisify解决回调地狱的问题
由于所有的操作都是异步的,目前的例子都很简单,都是一个异步的操作,但是对于复杂的操作场景,一个异步操作里面会包含多个异步操作,并且在这些多个异步的操作里面又包含着多个异步操作,这样就行程了回调地狱。
比如说这样的代码
[code]
() => {
() => {
() => {}
}
}
[/code]
这样的代码就没有办法读了,在以前解决这个问题的方法非常的有限,现在promis成为了一个规范,我们可以像是写同步一样来写异步。
[code]
const fs = require(‘fs’);
const promisify = require(‘util’).promisify;
const read = promisify(fs.readFile);
// read(‘./43_promisify.js’).then(data => {
// console.log(data.toString());
// }).catch(ex => {
// console.log(ex);
// });
async function test(){
try{
const content = await read(‘./243_promisify.js’);
console.log(content.toString());
}catch(ex){
console.log(ex);
}
}
test();
[/code]
到这里NodeJS的基础API就介绍完毕了,接下来将进入实战!