Node.js 入门指南:掌握基本操作

发表时间: 2022-03-16 14:46
  1. Node.js特点(记住三句话)
  • 事件驱动
  • 非阻塞IO模型(异步)
  • 轻量和高效

2.导入与导出

ode.js中模块的分类:

  • 核心模块(已经封装好的内置模块);
  • 自己定义的模块;
  • 第三方的模块(npm下载下来的);
  1. require

require 函数用来在一个模块中引入另外一个模块。传入一个模块名,返回一个模块导出对象。用法: let cc = require("模块名") ,其中模块名可以用绝对路径也可以用相对路径,模块的后缀名.js可以省略。例如:

let cc1 = require('./main.js')let cc2 = require('home/src/main.js')let cc3 = require('./main')

require()函数用两个作用:

  • 执行导入的模块中的代码;
  • 返回导入模块中的接口对象;
  1. exports

exports对象用来导出当前模块的公共方法或属性,别的模块通过require函数使用当前模块时得到的就是当前模块的 exports 对象。用法: exports.name ,name为导出的对象名。例子:

exports.add = function () { let i = 0 console.log(++i)}导出一个add方法供其他模块使用
  1. module.exports

module.exports 用来导出一个默认对象,没有指定对象名,常见于修改模块的原始导出对象。比如原本模块导出的是一个对象,我们可以通过module.exports修改为导出一个函数。如下:

module.exports = function () { console.log('hello world!')}

总结

  1. Node中每个模块都有一个module对象,module对象中的有一个exports属性为一个接口对象,我们需要把模块之间公共的方法或属性挂载在这个接口对象中,方便其他的模块使用这些公共的方法或属性。
  2. Node中每个模块的最后,都会return: module.exports
  3. Node中每个模块都会把module.exports指向的对象赋值给一个变量exports,也就是说:exports = module.exports
  4. module.exports = XXX,表示当前模块导出一个单一成员,结果就是XXX。
  5. 如果需要导出多个成员时必须使用exports.add = XXX; exports.foo = XXX;或者使用module.exports.add = XXX; module.export.foo = XXX;

3.加载第三方包

Node.js中使用CommonJs模块化机制,通过npm下载的第三方包,我们在项目中引入第三方包都是:let xx = require('第三方包名'),究竟require方法加载第三方包的原理机制是什么,今天我们来探讨下。

require('第三方包名')优先在加载该包的模块的同级目录node_modules中查找第三方包。

let template = require('art-template') //加载第三方包

找到该第三方包中的package.json文件,并且找到里面的main属性对应的入口模块,该入口模块即为加载的第三方模块。

如果在要加载的第三方包中没有找到package.json文件或者是package.json文件中没有main属性,则默认加载第三方包中的index.js文件。

如果在加载第三方模块的文件的同级目录没有找到node_modules文件夹,或者以上所有情况都没有找到,则会向上一级父级目录下查找node_modules文件夹,查找规则如上一致。

如果一直找到该模块的磁盘根路径都没有找到,则会报错:can not find module xxx

4.npm命令

npm英文全称:node package manager,npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。我们平时开发项目都是需要使用npm下载依赖,常见的npm命令总结如下:

  1. npm -v:查看npm版本。
  2. npm init:初始化后会出现一个package.json配置文件。可以在后面加上-y ,快速跳过问答式界面。
  3. npm install:会根据项目中的package.json文件自动下载项目所需的全部依赖。
  4. npm install 包名 --save-dev(npm install 包名 -D):安装的包只用于开发环境,不用于生产环境,会出现在package.json文件中的devDependencies属性中。
  5. npm install 包名 --save(npm install 包名 -S):安装的包需要发布到生产环境的,会出现在package.json文件中的dependencies属性中。
  6. npm list:查看当前目录下已安装的node包。
  7. npm list -g:查看全局已经安装过的node包。
  8. npm --help:查看npm帮助命令。
  9. npm update 包名:更新指定包。
  10. npm uninstall 包名:卸载指定包。
  11. npm config list:查看配置信息。
  12. npm 指定命令 --help:查看指定���令的帮助。
  13. npm info 指定包名:查看远程npm上指定包的所有版本信息。
  14. npm config set registry https://registry.npm.taobao.org:修改包下载源,此例修改为了淘宝镜像。
  15. npm root:查看当前包的安装路径。
  16. npm root -g:查看全局的包的安装路径。
  17. npm ls 包名:查看本地安装的指定包及版本信息,没有显示empty。
  18. npm ls 包名 -g:查看全局安装的指定包及版本信息,没有显示empty。

5.文件读取

导入 文件 模块

var fs = require('fs')

读取

同步:

var content = fs.readFileSync('hello.txt',{flag:'r',encoding:"utf-8"})

异步(默认):

fs.readFile("hello.txt",{flag:'r',encoding:"utf-8"},function(err,data){   if(err){       console.log(err)  }else{       console.log(data)  }})

flag:读取模式

encoding:编码格式

读取文件 promise 封装

function fsRead(path){   return new Promise(function(resolve,reject){       fs.readFile(path,{flag:'r',encoding:"utf-8"},function(err,data){           if(err){               //console.log(err)               //失败执行的内容               reject(err)          }else{               //console.log(data)               //成功执行的内容               resolve(data)          }      })  })}
//.then 调用var w1 = fsRead('hello.txt')w1.then(function(res){   console.log(res)})
// async 异步函数async function ReadList(){   var file2 = await fsRead('hello.txt');   console.log("1:",file2.toString()+".txt")   console.log("1:","hello2.txt")   console.log(file2.length)   var file3 = await fsRead(file2.trim()+".txt");   console.log(file3)   var file3Content = await fsRead("hello3.txt".trim())   console.log(file3Content)}ReadList()

7.文件写入

导入 文件 模块

var fs = require('fs')

写入

格式:write=>w read=>r append =>a

异步:

fs.writeFile('test.txt',"今晚吃啥\n",{flag:"a",encoding:"utf-8"},function(err){  if(err){      console.log("写入内容出错")  }else{      console.log("写入内容成功")  }})

封装写入函数

function writefs(path,content){   return new Promise(function(resolve,reject){       fs.writeFile(path,content,{flag:"a",encoding:"utf-8"},function(err){           if(err){           //console.log("写入内容出错")               reject(err)          }else{               resolve(err)           //console.log("写入内容成功")          }      })  })}

调用封装函数

async function writeList(){   await writefs('lc.html',"<h1>1今天吃烧烤</h1>");   await writefs('lc.html',"<h1>2今天吃烧烤</h1>");   await writefs('lc.html',"<h1>3今天吃烧烤</h1>");   await writefs('lc.html',"<h1>4今天吃烧烤</h1>");}writeList()

8.文件删除

let fs = require("fs")

fs.unlink('lc.txt',function(){

    console.log("成功删除!")
})

9.buffer缓冲区

buffer (缓存区)

1、数组不能进行二进制数据的操作2、js数组不像java、python等语言效率高3、buffer内存空间开辟出固定大小的内存

将字符串转成buffer对象

var str = "helloworld"let buf = Buffer.from(str)console.log(buf.toString())

开辟一个空的buffer(缓存区)(alloc函数)

let buf1 = Buffer.alloc(10)

console.log(buf1)

allocUnsafe(之前的一些内容)(效率高)

10.文件目录

导入 文件 模块

var fs = require('fs')

目录操作

读取目录

fs.readdir(path,callback)

异步操作读取

fs.readdir('../03fs',function(err,files){   if(err){       //console.log(err)  }else{       //console.log(files)       files.forEach(async function(filename,i){           let content = await fsRead('../03fs/'+filename)           await fsWrite(txtPath,content);      })  }   })

调用的封装函数

let fs = require('fs')function fsRead(path){   return new Promise(function(resolve,reject){       fs.readFile(path,{flag:'r',encoding:"utf-8"},function(err,data){           if(err){               //console.log(err)               //失败执行的内容               reject(err)          }else{               //console.log(data)               //成功执行的内容               resolve(data)          }           //console.log(456)      })  })}function fsWrite(path,content){   return new Promise(function(resolve,reject){       fs.writeFile(path,content,{flag:"a",encoding:"utf-8"},function(err){           if(err){               //console.log("写入内容出错")               reject(err)          }else{               resolve(err)               //console.log("写入内容成功")          }      })  })}module.exports = {fsRead,fsWrite}

删除目录

let fs = require('fs')fs.rmdir('abc',function(){   console.log("删除成功")})

输入输出

导入readline

let readline = require('readline');

实例化接口对象(process对象,stdout/in输入输出)

var r1 = readline.createInterface({   output:process.stdout,   input:process.stdin})

question方法 提问

r1.question("你的名字是?", function(answer){   console.log("我的名字是" + answer)   //添加close 程序结束   ri.close()})

close 事件监听

r1.on("close", function(){   //结束程序   process.exit(0)})

实践

let readline = require('readline');let {fsWrite} = require("./lcfs");//导入readline包//实例化接口对象var r1 = readline.createInterface({   output:process.stdout,   input:process.stdin})function lcQuestion(title){   return new Promise(function(resolve,reject){       r1.question(title,function(answer){           resolve(answer)      })  })}async function createPackage(){   let name = await lcQuestion("您的包名叫什么?");   let description = await lcQuestion("您的包如何描述?");   let main = await lcQuestion("您的包主程序入口文件是什么?")   let author = await lcQuestion("您的包的作者是谁?")   let content = `{       "name": "${name}",       "version": "1.0.0",       "description": "${description}",       "main": "${main}",       "scripts": {         "test": "echo Error: no test specified && exit 1"       },       "keywords": [         "'LAOCHEN'"       ],       "author": "${author}",       "license": "ISC",       "dependencies": {}     }`   await fsWrite('package.json',content)   //最终写完内容,关闭输入进程   r1.close()}//调用函数createPackage()//结束程序r1.on('close',function(){   process.exit(0)})

11.文件流

导入 文件 模块

var fs = require('fs')

写入流

  1. 创建写入流

语法:fs.createWriteStream(文件路径,【可选的配置操作】)

let ws = fs.createWriteStream("hello.txt",{flags:"w",encoding:"utf-8"});

2. 监听文件打开事件(open)

ws.on('open',function(){   console.log("文件打开")})

3. 监听准备事件(ready)

ws.on("ready",function(){   console.log("文件写入已准备状态")})

4. 监听文件关闭事件(close)

ws.on("close",function(){   console.log("文件写入完成,关闭")})

5. 文件写入完成

ws.end(function(){console.log("文件写入关闭")})

let ws = fs.createWriteStream("hello.txt",{flags:"w",encoding:"utf-8"});

实践

let ws =  fs.createWriteStream("hello.txt",{flags:"w",encoding:"utf-8"});//文件流式写入ws.write("helloworld1!",function(err){   if(err){       console.log(err)  }else{       console.log("内容1流入完成")  }});ws.write("helloworld2!",function(err){   if(err){       console.log(err)  }else{       console.log("内容2流入完成")  }});//文件写入完成ws.end(function(){console.log("文件写入关闭")})


读取流

1. 创建读取流

fs.createReadStream(路径,【可选的配置项】)

文档

let rs = fs.createReadStream('hello.txt',{flags:'r',encoding:"utf-8"})

音乐

let rs = fs.createReadStream('snake.mp4',{flags:'r'})

读取时写入

let ws = fs.createWriteStream('a.txt',{flags:"w",encoding:"utf-8"})

//打开rs.on('open',function(){   console.log("读取的文件已打开")})//关闭rs.on("close",function(){   ws.end()   console.log("读取流结束")})//每一批数据流入完成//提取文件内容rs.on('data',function(chunk){   console.log("单批数据流入:"+chunk.length)   console.log(chunk)   ws.write(chunk,()=>{console.log("单批输入流入完成")})})

管道流(读取的同时写入)

createReadStream.pipe(createWriteStream)

let rs = fs.createReadStream('snake.mp4',{flags:'r'})let ws = fs.createWriteStream('b.mp4',{flags:"w"})rs.on('open',function(){   console.log("读取的文件已打开")})rs.on("close",function(){   console.log("读取流结束")})rs.pipe(ws)

链式流

链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。

接下来我们就是用管道和链式来压缩和解压文件。

创建 compress.js 文件, 代码如下:

var fs = require("fs");var zlib = require('zlib');// 压缩 input.txt 文件为 input.txt.gzfs.createReadStream('input.txt').pipe(zlib.createGzip()).pipe(fs.createWriteStream('input.txt.gz')); console.log("文件压缩完成。");

代码执行结果如下:

$ node compress.js文件压缩完成。

执行完以上操作后,我们可以看到当前目录下生成了 input.txt 的压缩文件 input.txt.gz。

接下来,让我们来解压该文件,创建 decompress.js 文件,代码如下:

var fs = require("fs");var zlib = require('zlib');// 解压 input.txt.gz 文件为 input.txtfs.createReadStream('input.txt.gz').pipe(zlib.createGunzip()).pipe(fs.createWriteStream('input.txt')); console.log("文件解压完成。");

12.node事件

Node.js 事件循环

Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高。

Node.js 几乎每一个 API 都是支持回调函数的。

Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。

Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.

开启进程开启线程初始化数据,window/document/location...whild(true){      初始化事件列表   根据事件修改数据   根据数据去渲染页面         if(count=0){       运行js代码       btn.onclick = function(){           document.body.style.background = "skyblue"           console.log(123)      }       console.log(456)       count++  }}

没有使用events包 仅使用JavaScript事件监听进行事件驱动

(原理)

let fs = require("fs");fs.readFile("hello.txt",{flag:"r",encoding:"utf-8"},function(err,data){   if(err){       console.log(err)  }else{       console.log(data)       lcEvent.emit('helloSuccess',data)       //1数据库查看所有的用详细信息       //2统计年龄比例       //3查看所有用户学校的详细信息  }})let lcEvent = {   event:{       //fileSuccess:[fn,fn,fn]  },   on:function(eventName,eventFn){       if(this.event[eventName]){           this.event[eventName].push(eventFn)      }else{           this.event[eventName] = []           this.event[eventName].push(eventFn)      }  },   emit:function(eventName,eventMsg){       if(this.event[eventName]){           this.event[eventName].forEach(itemFn => {               itemFn(eventMsg)          });      }  }}lcEvent.on('helloSuccess',function(eventMsg){   console.log("1数据库查看所有的用户详细信息")})lcEvent.on('helloSuccess',function(eventMsg){   console.log("2统计用户年龄比例")})lcEvent.on('helloSuccess',function(eventMsg){   console.log("3查看所有用户学校的详细信息")})

事件驱动程序

Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。

当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。

这个模型非常高效可扩展性非常强,因为 webserver 一直接受请求而不等待任何读写操作。(这也称之为非阻塞式IO或者事件驱动IO)

在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();

以下程序绑定事件处理程序:

// 绑定事件及事件的处理程序eventEmitter.on('eventName', eventHandler);

我们可以通过程序触发事件:

// 触发事件eventEmitter.emit('eventName');

实例1

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();// 创建事件处理程序var connectHandler = function connected() {  console.log('连接成功。');   // 触发 data_received 事件  eventEmitter.emit('data_received');}// 绑定 connection 事件处理程序eventEmitter.on('connection', connectHandler);// 使用匿名函数绑定 data_received 事件eventEmitter.on('data_received', function(){  console.log('数据接收成功。');});// 触发 connection 事件eventEmitter.emit('connection');console.log("程序执行完毕。");

接下来让我们执行以上代码:

$ node main.js连接成功。数据接收成功。程序执行完毕。

实例2

//导包let events = require("events");let fs = require("fs")//创建对象let ee = new events.EventEmitter();//事件监听ee.on("helloSuccess",function(eventMsg){   console.log("1吃夜宵")   console.log(eventMsg)})ee.on("helloSuccess",function(){   console.log("2唱K")})ee.on("helloSuccess",function(){   console.log("3打王者")})ee.on("helloSuccess",function(){   console.log("4打dota")})//function lcReadFile(path){   return new Promise(function(resolve,reject){       fs.readFile(path,{encoding:"utf-8"},function(err,data){           if(err){               //console.log(err)               reject(err)          }else{               //console.log(data)               resolve(data)               //ee.emit("helloSuccess",data)          }      })  })}//than触发lcReadFile('hello.txt').then(function(data){   ee.emit("helloSuccess",data)})//ES6新特性触发async function test(){   let data = await lcReadFile('hello.txt')   ee.emit("helloSuccess",data)}test()

Node 应用程序是如何工作的?

在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数接收错误对象作为第一个参数。

接下来让我们来重新看下前面的实例,创建一个 input.txt ,文件内容如下:

官网地址:www.sxt.com

创建 main.js 文件,代码如下:

var fs = require("fs");fs.readFile('input.txt', function (err, data) {  if (err){     console.log(err.stack);     return;  }  console.log(data.toString());});console.log("程序执行完毕");

以上程序中 fs.readFile() 是异步函数用于读取文件。如果在读取文件过程中发生错误,错误 err 对象就会输出错误信息。

如果没发生错误,readFile 跳过 err 对象的输出,文件内容就通过回调函数输出。

执行以上代码,执行结果如下:

程序执行完毕官网地址:www.sxt.com

接下来我们删除 input.txt 文件,执行结果如下所示:

程序执行完毕Error: ENOENT, open 'input.txt'

因为文件 input.txt 不存在,所以输出了错误信息。

Node.js EventEmitter

Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。所有这些产生事件的对象都是 events.EventEmitter 的实例。


EventEmitter 类

events 模块只提供了一个对象:events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。

你可以通过require("events");来访问该模块。

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();

EventEmitter 对象如果在实例化时发生错误,会触发 error 事件。当添加新的监听器时,newListener 事件会触发,当监听器被移除时,removeListener 事件被触发。

下面我们用一个简单的例子说明 EventEmitter 的用法:

//event.js 文件var EventEmitter = require('events').EventEmitter;var event = new EventEmitter();event.on('some_event', function() {   console.log('some_event 事件触发');});setTimeout(function() {   event.emit('some_event');}, 1000);

执行结果如下:

运行这段代码,1 秒后控制台输出了 'some_event 事件触发'。其原理是 event 对象注册了事件 some_event 的一个监听器,然后我们通过 setTimeout 在 1000 毫秒以后向 event 对象发送事件 some_event,此时会调用some_event 的监听器。

$ node event.jssome_event 事件触发

EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持 若干个事件监听器。

当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。

让我们以下面的例子解释这个过程:

//event.js 文件var events = require('events');var emitter = new events.EventEmitter();emitter.on('someEvent', function(arg1, arg2) {   console.log('listener1', arg1, arg2);});emitter.on('someEvent', function(arg1, arg2) {   console.log('listener2', arg1, arg2);});emitter.emit('someEvent', 'arg1 参数', 'arg2 参数');

执行以上代码,运行的结果如下:

$ node event.jslistener1 arg1 参数 arg2 参数listener2 arg1 参数 arg2 参数

以上例子中,emitter 为事件 someEvent 注册了两个事件监听器,然后触发了 someEvent 事件。

运行结果中可以看到两个事件监听器回调函数被先后调用。这就是EventEmitter最简单的用法。

EventEmitter 提供了多个属性,如 onemiton 函数用于绑定事件函数,emit 属性用于触发一个事件。接下来我们来具体看下 EventEmitter 的属性介绍。