Node.js基础
Node.js 介绍
是什么
- 简单的说 Node.js 就是运行在服务端的 JavaScript。
- Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。
- Node.js 脱离了浏览器,可以操作网络、文件等
- Node.js = ECMAScript + 各种基于 ES 的模块(net、http、fs)
用途
- 服务端开发(通过网络提供各种数据)
- 工具开发:vue-cli / create-react-app
- 桌面端(electorn, nw.js):vsCode
安装Node.js
- Node.js官网下载稳定版本,左边为稳定版本,右边为非稳定版本
- 安装完Node.js会自动安装NPM(Node Package Manager) - 包管理工具
- 通过指令 node -v 来查看是否安装完成和查看node版本号;npm -v 来查看npm版本
Node.js 创建第一个应用
步骤一、引入 required 模块
使用 require 指令来载入 http 模块,并将实例化的 HTTP 赋值给变量 http
var http = require("http");
步骤二、创建服务器
使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。
var http = require('http');
http.createServer(function(request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
// 发送响应数据 "第一个应用"
response.end('第一个应用\n');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');
使用 node 命令执行以上的代码:
node server.js
Server running at http://127.0.0.1:8888/
打开浏览器访问 http://127.0.0.1:8888/
Node.js模块系统
node没有全局作用域。在Node.js中,通过require方法加载和执行多个JavaScript脚本文件。文件与文件之间由于是模块作用域,即使加载执行多个文件,可以完全避免变量命名冲突污染。但是某些情况下,模块与模块是需要进行通信的,可通过require方法得加载文件模块导出的接口对象。即:
- 模块作用域
- 通过require方法,加载文件模块和执行里面的代码
- 通过require方法,得加载文件模块导出的接口对象exports
引入模块
在 Node.js 中,引入一个模块非常简单,如下我们创建一个 main.js 文件并引入 hello 模块
var hello = require('./hello');
hello.world();
以上实例中,代码 require(’./hello’) 引入了当前目录下的 hello.js 文件(./ 为当前目录,node.js 默认后缀为 js)
Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。
接下来创建 hello.js 文件
exports.world = function() {
console.log('Hello 这是hello模块');
}
如果只是想把一个对象封装到模块中
代码格式如下:
module.exports = function() {
// ...
}
例如:
//hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
//main.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('yy');
hello.sayHello();
模块接口的唯一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。
Node.js 中自带了一个叫做 http 的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。
这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象
exports 和 module.exports 的使用
如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似class,包含了很多属性和方法),就用 module.exports。
不建议同时使用 exports 和 module.exports。
如果先使用 exports 对外暴露属性或方法,再使用 module.exports 暴露对象,会使得 exports 上暴露的属性或者方法失效。原因在于,exports 仅仅是 module.exports 的一个引用
内置模块
- 为了实现一些文件操作,网络系统等操作,这些都是原本 JavaScript 不能实现的,由 Node.js 提供,我们也可以称他们为内置模块或者是官方模块,我们不需要进行下载,也不需要自定义,他已经集成在 Node.js 内,我们也可以理解为这些模块是随着我们安装 Node.js 的时候一并安装进来的
- Node.js 内置模块 - Buffer,C/C++Addons,Child Processes,Cluster,Console,Crypto,Debugger,DNS,Domain,Errors,Events,File System,Globals,HTTP,HTTPS,Modules,Net,OS,Path,Process,P unycode,Query Strings,Readline,REPL,Stream,String De coder,Timers,TLS/SSL,TTY,UDP/Datagram,URL, Utilities,V8,VM,ZLIB等
path 模块
- __dirname - 指向被执行 js 文件的绝对路径
- __firename - 指向被执行 js 文件的绝对路径 + js文件名
- ./ - 你执行 Node.js 命令的路径,即工作路径
规范化路径
path.normalize 不同系统的路径有可能不同,例如,Unix系统是 /,Windows系统是 \ 该方法可规范化路径
连接路径
path.join 该方法path使用特定于平台的分隔符作为分隔符将所有给定的段连接在一起,然后对结果路径进行规范化
path.join('..', 'fs', '1', '1.txt'); // windows下:..\fs\1\1.txt
拼接路径段为绝对路径
path.resolve( [from…], to ) 与 path.join 不同,前者只是简单的将路径段进行连接,而 path.resolve 会对路径段进行解析,给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径
path.resolve('/foo/bar', './baz') // returns '/foo/bar/baz'
path.resolve('/foo/bar', 'baz') // returns '/foo/bar/baz'
path.resolve('/foo/bar', '/baz') // returns '/baz'
path.resolve('/foo/bar', '../baz') // returns '/foo/baz'
path.resolve('home','/foo/bar', '../baz') // returns '/foo/baz'
path.resolve('home','./foo/bar', '../baz') // returns '/home/foo/baz'
path.resolve('home','foo/bar', '../baz') // returns '/home/foo/baz'
path.resolve('home', 'foo', 'build','aaaa','aadada','../../..', 'asset') //return '/home/foo/asset'
判断参数 path 是否是绝对路径
path.isAbsolute 返回布尔值
path.isAbsolute(__dirname); // true
将绝对路径转为相对路径
path.relative(from, to) 基于当前工作目录,返回从 from 到 to 的相对路径
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); // ../../impl/bbb
返回路径最后一部分/去除后缀
path.basename(path[, ext]) 此方法区分大小写,包括扩展名
path.basename('/foo/bar/baz/asdf/quux.html'); // quux.html
path.basename('/foo/bar/baz/asdf/quux.html', '.html'); // quux
path.basename('/foo/bar/baz/asdf/quux.HTML', '.html'); // quux.HTML
返回路径后缀名
- path.parse
返回路径字符串的对象 - path.format(pathObject)
从对象中返回路径字符串,和 path.parse 相反 - 对象属性:
root - 根路径
dir - 路径
base - 文件名及后缀
name - 文件名
ext - 文件后缀
path.parse('/home/user/dir/file.txt'); // { root: '/', dir: '/home/user/dir', base: 'file.txt', ext: '.txt', name: 'file' }
path.format({ root: '/', dir: '/home/user/dir', base: 'file.txt', ext: '.txt', name: 'file' }); // /home/user/dir/file.txt
File system(fs) 模块
fs是文件操作模块,跟数据库操作有些类似,大的方向为 - 增、删、改、查
区别:
- 文件操作
- 目录操作(文件夹/容器)
文件操作
- 文件写入
- fs.writeFile(file, data[, options], callback)
异步模式下写入文件的语法格式 writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。
- fs.writeFile(file, data[, options], callback)
const fs = require('fs'); // 文件操作
fs.writeFile('1.txt', '我是内容', err => {
if (err) return console.log(err);
console.log('写入成功');
});
fs.writeFile('1.txt', '我是内容', { flag: 'w' }, err => { });
- 文件读取
fs.read(fd, buffer, offset, length, position, callback) 异步模式下读取文件的语法格式
不写编码格式的情况下,我们可以通过 .toString() 的方法转化成中文
(接收一个参数 - 编码格式,如不填写参数,则将使用 utf-8 作为默认值,需确保内容为字符串)
fs.readFile('1.txt', 'utf-8', (err, data) => {
if (err) return console.log(err);
console.log(data);
});
fs.readFile('1.txt', (err, data) => { });
- 文件操作的同步与异步
所有文件操作都是有同步和异步之分,同步方法会加上 “Sync”,同步操作会造成代码阻塞,视情况采用不同的方法 - 文件修改(文件名)
fs.rename(oldPath, newPath, callback) 文件内容修改我们一般是先读取文件内容,再按需修改,重新写入,所以 fs 修改的是文件名 - 文件删除
fs.unlink(path, callback) 删除文件的语法格式 - 文件复制
fs.copyFile
在以前版本的 Node.js 是没有复制方法的,我们需要通过 读取文件 -> 写入文件 进行复制
参数:- 需要复制的文件路径/名称
- 指定的文件路径/名称
- 函数回调
fs.copyFile('1.txt', '2.txt', err => {
if (err) return console.log(err);
console.log('复制成功');
});
目录操作
- 创建目录
fs.mkdir(path[, options], callback) 创建目录的语法格式
fs.mkdir('1', err => {
if (err) return console.log(err);
console.log('创建成功');
});
- 读取目录
fs.readdir(path, callback) 读取目录的语法格式
fs.readdir('1', (err, data) => {
if (err) return console.log(err);
console.log(data);
});
- 目录修改(目录名)
fs.rename 使用与 文件修改 一致 - 目录删除
fs.rmdir(path, callback) 删除目录的语法格式
删除目录只能删除空目录/文件夹,否则会报错
删除非空文件夹需要 - 先把目录内的文件/文件夹删除 -> 删除空目录
function removeDir(_path) {
const data = fs.readdirSync(_path);
data.forEach(item => {
// 判断是否是文件 文件: 直接删除 目录:递归
// 注意:item是名称,需要组装完整路径: path.join(_path, item)
const url = path.join(_path, item);
if (fs.statSync(url).isFile()) {
// 文件: 直接删除
fs.unlinkSync(url);
} else {
// 目录:递归
removeDir(url)
}
});
// 删除空目录
fs.rmdirSync(_path);
}
removeDir('1');
文件与目录操作通用方法
- fs.rename 修改名称
- fs.existsSync 判断文件或者目录是否存在
- fs.stat 获取文件或者目录的详细信息
http 模块
http模块被用于服务器开发,简单来说,就是监听网络(端口),当有客户端请求了,那么就返回对应的数据
- http.createServer 接收一个回调函数,回调函数有两个传参
- request - http.ClientRequest 类 储存当前请求的客户端信息和方法,客户端访问的地址(url)与后端的文件不是一对一的关系,他们只是以中虚拟映射的关系,这个关系是我们后端程序根据实际情况返回的
- response - http.ServerResponse 类
提供了服务器响应相关的信息和方法- response.write(chunk[, encoding][, callback]) 浏览器会响应并把内容打在页面上,我们可以 html 等各种资源(数据)储存在外部文件中,然后通过 node 去读取,编译成浏览器可读的形式传给浏览器
- response.end([data[, encoding]][, callback]) 此方法向服务器发送信号,指示所有响应头和主体已发送。该服务器应认为此消息已完成。response.end()必须在每个响应上调用方法,否则页面会一直卡着
- response.setHeader() 设置头部信息
- response.writeHeader(statusCode[, statusMessage][, headers]) 发送写入头信息(包括状态码),response.writeHeader 必须在 response.setHeader 后面,response.setHeader 将合并传递给 response.writeHeader 给定的优先级
- server.listen(‘端口号’,‘callback’)
在当前电脑上监听一个指定的端口
require的加载规则
即根据模块标识来加载即:require('模块标识符')
1.自己写的模块
路径形式的模块:1./ 当前目录,不可省略 , 2../ 上一级目录,不可省略 3.js 后缀名可以省略
var b = require('./foo.js')
var b = require('./foo')
2.核心模块
核心模块的本质也是文件,已经被编译到了二进制文件中(下载后,编译在node.exe),我们只需要按照名字来加载就可以了
var http = require('http')
var fs = require('fs')
3.第三方模块
凡是第三方模块都必须通过 npm 来下载
使用的时候就可以通过 require('包名') 的方式来进行加载才可以使用
如: var template = require('art-template')
整个加载过程中:
先找到当前文件所处目录中的 node_modules 目录
node_modules/art-template
node_modules/art-template/
node_modules/art-template/package.json
node_modules/art-template/package.json 文件中的 main 属性
main 属性中就记录了 art-template 的入口模块
如果 package.json文件不存在或者main指定的入口模块是也没有,自动找该目录下的 index.js,index.js 是作为一个默认备选项
如果以上所有任何一个条件都不成立,进入上一级目录找 node_modules
按照这个规则依次往上找,直到磁盘根目录还找不到,最后报错:Can not find moudle xxx
一个项目有且仅有一个 node_modules 而且是存放到项目的根目录
- 优先从缓存加载:再次加载某个模块,不会执行里面的代码,但可以从缓存中拿到其中的接口对象,这样可以避免重复加载,提高模块加载效率
Node.js EventEmitter
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。
Node.js 里面的许多对象都会分发事件:一个 net.Server 对象会在每次有新连接时触发一个事件, 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。
events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
//EventEmitter 对象如果在实例化时发生错误,会触发 error 事件。当添加新的监听器时,newListener 事件会触发,当监听器被移除时,removeListener 事件被触发。
//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.addListener(event, listener) 为指定事件添加一个监听器到监听器数组的尾部
// 2.on(event, listener) 为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数。
// 3.once(event, listener) 为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
// 4.removeListener(event, listener) 移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。它接受两个参数,第一个是事件名称,第二个是回调函数名称
// 5.removeAllListeners([event]) 移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
// 6.setMaxListeners(n) 默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于改变监听器的默认限制的数量。
// 7.listeners(event) 返回指定事件的监听器数组。
// 8.emit(event, [arg1], [arg2], [...]) 按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。
// ---类方法 获取事件的监听器数量 events.emitter.listenerCount(eventName);
// ---事件 newListener removeListener 从指定监听器数组中删除一个监听器。需要注意的是,此操作将会改变处于被删监听器之后的那些监听器的索引。
Stream(流)
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。
Node.js,Stream 有四种流类型:
- Readable - 可读操作。
- Writable - 可写操作。
- Duplex - 可读可写操作.
- Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
- data - 当有数据可读时触发。
- end - 没有更多的数据可读时触发。
- error - 在接收和写入过程中发生错误时触发。
- finish - 所有数据已被写入到底层系统时触发。
var data = '';
var fs = require('fs');
// 创建可读流
var readStream = fs.createReadStream('input.txt');
// 设置编码为 utf8。
readStream.setEncoding('UTF8');
// 处理流事件 --> data, end, and error
readStream.on('data', function(chunk) {
data += chunk;
});
readStream.on('end', function() {
console.log(data + '流 文件读取');
});
readStream.on('error', function(err) {
console.log(err.stack);
});
console.log('流 文件读取完毕');
var outputData = '流 写入文件-----';
var writeStream = fs.createWriteStream('output.txt');
writeStream.write(outputData, 'UTF8');
writeStream.end();
writeStream.on('finish', function() {
console.log('写入完成');
});
writeStream.on('error', function(err) {
console.log(err.stack);
});
console.log('流 文件写入完毕');
管道流 管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
// 管道读写操作
// 创建一个可读流
var fs = require('fs');
var readerStream = fs.createReadStream('input.txt');
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
var writerStream = fs.createWriteStream('pipeStream.txt');
readerStream.pipe(writerStream);
链式流 链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。
var fs = require('fs');
var zlib = require('zlib');
fs.createReadStream('input.txt').pipe(zlib.createGzip()).pipe(fs.createWriteStream('input.txt.gz'));
console.log('文件压缩完成');
fs.createReadStream('input.txt.gz').pipe(zlib.createGunzip()).pipe(fs.createWriteStream('input.txt'));
console.log('文件解压完成');