一、简介
- Node.js是一个能够在服务器端运行JavaScript的开放源代码、跨平台JavaScript运行环境。
- Node采用Google开发的V8引擎运行js代码,使用事件驱动、非阻塞和异步I/O模型(I:input、O:output)等技术来提高性能,可优化应用程序的传输量和规模。
- Node大部分基本模块都用JavaScript编写。在Node出现之前,JS通常作为客户端程序设计语言使用,以JS写出的程序常在用户的浏览器上运行。
- 目前,Node已被IBM、Microsoft、Yahoo!、Walmart、Groupon、SAP、LinkedIn、Rakuten、PayPal、Voxer和GoDaddy等企业采用。
- 但是Node与其他语言最大的不同之处是:PHP等语言是阻塞的,而Node是非阻塞的。
Node是对ES标准的一种实现,Node也是JS引擎。
通过Node可以使JS代码在服务端执行。
Node的服务器是单线程的,但是在后台拥有一个I/O线程池。
Node处理速度快,适合页面渲染。
二、Node的用途
1、Web服务API,比如rest
2、实时多人游戏
3、后端的Web服务,例如跨域、服务器端的请求
4、基于Web的应用
5、多客户端的通信,如即时通信
三、使用Node执行js文件
1、使用Win+R打开命令行窗口
2、输入cd+目录名,进入文件所在目录
3、node + 空格 + 所需执行的js文件名
快捷方式:在所需执行文件的文件夹上方输入cmd,在执行第三步操作。
四、模块化介绍
1、ECMAScirpt标准的缺陷
- 没有模块系统(引入jquery属于模块化,但不属于模块化系统,ES6有模块化)
- 标准库较少
- 没有标准接口
- 缺乏管理系统
2、模块化
- 如果程序设计的规模达到了一定程度,则必须对其进行模块化。
- 模块化可以有多种形式,但至少应该提供能够将代码分割为多个源文件的机制。
- CommonJS的模块功能可以帮我们解决该问题。、
- 在Node中,一个js文件就是一个模块。
- 在Node中,每一个js文件中的js代码都是独立运行在一个函数中的,而不是全局作用域,所以一个模块中的变量和函数在其他模块中无法访问。
CommonJS规范
- CommonJS规范的提出,主要是为了弥补当前JavaScript没有标准的缺陷。
- CommonJS规范为JS指定了一个美好的愿景,希望JS能够在任何地方运行。
- CommonJS对模块的定义十分简单:模块引用、模块定义、模块标识。
3、模块引用
在Node中,通过require()函数来引入外部的模块。
require()中可以传递一个文件的路径作为参数,node将会自动根据该路径引入外部模块。
这里路径如果使用相对路径,必须以.或者..开头。
require("./02.moudle.js");
使用require()引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块。
可以通过exports来向外暴露变量和方法
只需要将需要暴露给外部的变量或方法设置为exports的属性即可。
<!--01.module.js--!>
console.log("我是一个模块,我是01.module");
exports.x = "我是01.module.js中的x";
exports.y = "我是01.module.js中的y";
exports.fn = function() {
};
<!--02.module.js--!>
var md = require("./01.module.js");
console.log(md.x);
console.log(md.y);
console.log(md.fn);
引用案例:定义一个模块match,在该模块中提供两种方法:
add(a,b);求两个数的和,mul(a,b);求两个数的积
//02.module.js
var math = require("./03.math");
console.log(math.add(123, 456)); //579
//03.math.js
exports.add = function(a, b) {
return a + b;
}
exports.mul = function(a, b) {
return a * b;
}
4、模块定义
- 在运行环境中,提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一的导出的出口。
- 在模块中还存在一个module对象,它代表模块自身,而exports是module的属性。
- 在Node中一个文件就是一个模块。
在Node中,模块分为三类:
① 内建模块:底层由C++编写的模块
② 核心模块:由Node引擎提供的模块
核心模块的标识就是模块的名字
示例:var fs = require("fs");
③ 文件模块:由用户自己创建的模块
文件模块的标识就是文件的路径
(绝对路径,一般来说是相对路径。相对 路径使用.或者..开头)
5、模块标识
- 模块标识其实就是模块的名字,也就是传递给require()方法的参数,它必须是符合驼峰命名法的字符串,或者是以.、…开头的相对路径、或者绝对路径。
- 使用require()引入外部模块时,使用的就是模块标识。我们可以通过模块标识来找到指定的模块。
6、Node的模块实现
- Node中虽然使用的是CommonJS规范,但是其自身也对规范做了一些取舍。
- 在Node中引入模块,需要经历如下三个步骤:
—路径分析
—文件定位
—编译执行
在Node中有一个全局对象global,它的作用和网页中的window类似。
在全局中创建的变量都会作为global的属性保存。
在全局中创建的函数都会作为global的方法保存。
- 当Node在执行模块中的代码时,会首先在代码的最顶部,添加如下代码:
function(exports,require,module,__filename,__dirname){
- 在代码的最底部,添加如下代码: }
- 实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递进了5个实参。
① exports:该对象用来将变量或函数暴露到外部
② require:函数,用来引入外部模块
③ module:module代表的当前模块本身,exports是module的属性
既可以使用exports导出,也可以使用module.exports导出
④ __filename:当前模块的完整路径
⑤ __dirname:当前模块所在文件夹完整路径
exports和module.exports:
① 通过exports只能使用.的方式来向外暴露内部变量
exports.xxx = xxx
② 而module.exports既可以通过.的方式,也可以直接赋值
module.exports.xxx = xxxx
module.exports = { }
五、包 package
CommonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具。
CommonJS的包规范由包结构和包描述文件两个部分组成。
1、包结构
- 用于组织包中的各种文件
- 包实际上就是一个压缩文件,解压以后还原为目录。复合规范的目录,应该包含如下文件:
—package.json:描述文件
—bin:可执行二进制文件
—lib:js代码
—doc:文档
—test:单元测试
2、包描述文件
- 描述包的相关信息,以供外部读取分析。
- 包描述文件用于表达非代码相关的信息,它是一个JSON格式的文件:package.json,位于包的根目录下,是包的重要组成部分。
- package.json中的字段:
name、description、version、keywords、maintainers、contributors、bugs、licenses、
repositories、dependencies、homepage、os、cpu、engine、builtin、direcotries、
implements、scripts、author、bin、main、devDependencies。
六、NPM(Node Package Manager)
- CommonJS包规范是理论,NPM是其中一种实践。
- 对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。
- 通过NPM下载的包都放到node_modules文件夹中
- 通过NPM下载的包,直接通过包名引入即可。
- Node在使用模块名字来引入模块时,首先在当前目录的node_modules中寻找是否含有该模块:
① 如果有则直接使用,如果没有则去上一级目录的node_modules中寻找
② 如果有则直接使用,如果没有则再去上一级目录寻找,直到找到为止
③ 直到找到磁盘的根目录,如果依然没有,则报错
1、NPM命令(cmd)
① npm -v :查看版本
② npm:帮助说明
③ npm version:查看所有模块的版本
④ npm search 包名:搜索模块包
⑤ npm install / i 包名:在当前目录安装包
⑥ npm install 包名 + -g:全局模式安装包(全局安装的包一般都是一些工具)
⑦ npm remove 包名:删除包
⑧ npm install 包名 --save:安装包并添加到依赖中
⑨ npm install :下载当前项目所依赖的包
⑩ npm install 文件路径:从本地安装
npm install 包名 -registry=地址:从镜像源安装
npm config set registry 地址:设置镜像源
七、Buffer(缓冲区)
从结构上看Buffer非常像一个数组,它的元素为16进制的两位数。
数组中不能存储二进制的文件,而buffer就专门用来存储二进制数据。
使用buffer不需要引入模块,直接使用即可。
实际上一个元素就表示内存中的一个字节。
实际上Buffer中的内存不是通过JavaScript分配的,而是在底层通过C++申请的。
也就是我们可以直接通过Buffer来创建内存中的空间。
1、Buffer操作
(1)将一个字符串保存到buffer中
var str = "hello world";
//将一个字符串保存到buffer中
var buf = Buffer.from(str);
console.log(buf); //<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
console.log(buf.length); //11 占用内存的大小 表示占用内存11个字节
console.log(str.length); //11 获取的是字符串的长度
Buffer中存储的都是二进制数据,但是是以十六进制的形式来显示。
buffer中的每一个元素的范围是从00—ff (00000000—11111111)
计算机中,一个0或者一个1,称为1位(bit)
8bit = 1byte(字节),字节是数据传输最小的单位
buffer中的一个元素,占用内存一个字节。
英文占用内存中的一个字节,汉字占用内存三个字节
- 1024byte = 1kb
- 1024kb = 1mb
- 1024mb=1gb
- 1024gb=1tb
(2)创建一个指定大小的buffer
//buffer的构造函数都是不推荐使用的
var buf2 = new Buffer(10); //10个字节的buffer
console.log(buf2.length);
Buffer的大小一旦确定,则不能修改。
Buffer实际上是对底层内存的直接操作。
//创建一个10字节的buffer
var buf2 = Buffer.alloc(10);
buf2[0] = 88;
buf2[1] = 255;
buf2[2] = 0xaa;
buf2[10] = 15;
console.log(buf2); //<Buffer 58 ff aa 00 00 00 00 00 00 00>
//只要数字在控制台或页面中输出一定是10进制
console.log(buf2[2]); //170
console.log(buf2[2].toString(16)); //aa
Buffer.allocUnsafe(size) 创建一个指定大小的buffer,但是buffer中可能含有敏感数据.
var buf3 = Buffer.allocUnsafe(10);
console.log(buf3); //
Buffer.from(str):将一个字符串转化为buffer
Buffer.alloc(size):创建一个指定大小的buffer
Buffer.allocUnsafe(size):创建一个指定大小的buffer
buf.toString():将缓冲区的数据转化为字符串
var buf4 = Buffer.from("我是一段文本数据");
console.log(buf4.toString());
八、fs(文件系统)
文件系统(File System):通过Node来操作系统中的文件。
使用文件系统,需要先引入fs模块,fs是核心模块,直接引入,不需要下载。
- 在Node中,与文件系统的交互是非常重要的,服务器的本质就将本地的文件发送给远程的客户端。
- Node通过fs模块来和文件系统进行交互。
- 该模块提供了一些标准文件访问API来打开、读取、写入文件,以及与其交互。
- 要使用fs模块,首先需要对其进行加载。
const fs = require("fs");
1、同步调用
fs模块中所有的操作都有两种形式可供选择同步和异步。
- 同步文件系统会阻塞程序的执行,也就是除非操作完毕,否则不会向下执行代码。
- 异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回。
(1)打开文件
fs.openSync*(path,flags[, mode])
- path:要打开文件的路径
- flags:打开文件要做的操作的类型 r-只读的 w-可写的
- mode 设置文件的操作权限,一般不传
(2)向文件中写入内容
fs.writeSync(fd,string[,position[,encoding]])
- fd:文件的描述符,传递要写入文件的描述符
- string:要写入的内容
- position:写入的起始位置
- encoding:写入的编码,默认utf-8
(3)保存并关闭文件
fs.closeSync(fd)
- fd:要关闭的文件的描述符
var fs = require("fs");
//1、打开文件
var fd = fs.openSync("hello.txt","w");
//2、向文件中写入内容
fd.writeSync(fd,"今天晚上吃什么呢???",2);
//3、保存并关闭文件
fd.closeSync(fd);
2、异步调用
(1)打开文件
fs.open(path, flags[, mode], callback)
- callback:不能省略,是回调函数
- 用来打开一个文件
- 异步调用的方法,结果都是通过回调函数的参数返回的。
- 回调函数有两个参数:
① err 错误对象:如果没有错误则为null
② fd 文件的描述
(2)向文件中写入内容
fs.write(fd,string[,position[,encoding]],callback)
- 用来异步写入一个文件
(3)保存并关闭文件
fs.close(fd,callback)
- 用来关闭文件的
- 异步的close(2)。完成回调只有一个可能的异常参数
/*异步文件写入*/
// (1)打开文件
//fs.open(path, flags[, mode], callback)
var fs = require("fs");
//异步调用的方法,结果都是通过回调函数的参数返回的
fs.open("hello2.txt", "w", function(err, fd) {
//判断是否出错
if (!err) {
//如果没有出错,则对文件进行写入操作
// (2)向文件中写入内容
fs.write(fd, "这是异步写入的内容", function(err) {
if (!err) {
console.log("写入成功~~~");
}
//(3)保存并关闭文件
fs.close(fd, function(err) {
if (!err) {
console.log("文件已关闭~~~");
}
})
});
} else {
console.log(err);
}
})
3、简单文件
(1)写入
同步:fs.writeFileSync(file,data[,options],callback)
异步:fs.writeFile(file,data[,options])
- file:要操作的文件路径
- data:要写入的数据
- option:选项,可以对写入进行设置(可省略)
- callback:写入完成以后执行的函数
- flag: r(只读) w(可写) a(追加)
var fs = require("fs");
//此时的hellow3.txt是相对路径
fs.writeFile("hello3.txt", "这是通过writeFile写入的内容", { flag: "a" }, function(err) {
if (!err) {
console.log("写入成功");
} else {
console.log(err);
}
});
//绝对路径的使用
使用前:C:\Users\lilichao\Desktop\hello.txt
使用后:C:\\Users\\lilichao\\Desktop\\hello.txt
(2)文件读取
同步:fs.readFile(path[,options],callback)
异步:fs.readFile(path[,options])
- path:要读取文件的路径
- options:读取的选项
- callback回调函数:通过回调函数将读取到的内容返回(err,data)
- err:错误对象
- data:读取到的数据,会返回一个Buffer,可转换成字符串
//简单文件读取
var fs = require("fs");
fs.readFile("hello3.txt", function(err, data) {
if (!err) {
// console.log(data);
// 将data写入到文件中
fs.writeFile("hello.jpg", data, function(err) {
if (!err) {
console.log("文件写入成功");
}
})
}
})
4、流式文件
同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出。
往一个文件中写入大量数据时,最好的方法之一是使用流。
(1)写入
流式文件可持续写入。
fs.createWriteStream(path[, options]) 可以用来创建一个可写流
path:文件路径
options:配置的参数,可省略不写
var fs = require("fs");
var ws = fs.createWriteStream("hello4.txt");
//可以通过监听流的open的close事件来监听流的打开和关闭
//打开流
//open只会触发一次,是一次性事件
//on(事件字符串,回调函数) :可以为对象绑定一个事件
//once(事件字符串,回调函数):可以为对象绑定一个一次性的事件,该事件将会在触发一次以后自动失效
ws.once("open", function() {
console.log("流打开了~~~");
});
ws.once("close", function() {
console.log("流关闭了~~~");
})
//通过ws向文件中输入内容,可持续写入
ws.write("通过可写流,写入文件的内容");
ws.write("今天晚上吃什么?");
ws.write("甜皮鸭");
ws.write("稀饭");
//关闭流
ws.close();
(2)文件读取
流式文件的读取也使用于一些比较大的文件,可以分多次将文件读取到内存中。
/流式文件读取
// 流式文件的读取也使用于一些比较大的文件,可以分多次将文件读取到内存中
var fs = require("fs");
//创建一个可读流
var rs = fs.createReadStream("E:/前端软件/学习操作/8.Node.js/03-文件系统/草东-还愿.mp3");
//创建一个可写流
var ws = fs.createWriteStream("a.mp3");
//监听流的开启和关闭
rs.once("open", function() {
console.log("可读流打开了~~");
});
rs.once("close", function() {
console.log("可读流关闭了~~~");
//数据读取完毕,关闭可写流
ws.close();
});
ws.once("open", function() {
console.log("可写流打开了~~~");
});
ws.once("close", function() {
console.log("可写流关闭了~~~");
})
//如果要读取一个可读流中的数据,必须要为可读流绑定一个data事件,data事件绑定完毕,它会自动开始读取数据
rs.on("data", function(data) {
//console.log(data);
//将读取到的数据,写入到可写流中
ws.write(data);
});
pipe()可以将可读流中的内容,直接输出到可写流中。
var fs = require("fs");
//创建一个可读流
var rs = fs.createReadStream("E:/前端软件/学习操作/8.Node.js/03-文件系统/草东-还愿.mp3");
//创建一个可写流
var ws = fs.createWriteStream("b.mp3");
//pipe()可以将可读流中的内容,直接输出到可写流中
rs.pipe(ws);
九、其他操作
1、验证路径是否存在:fs.existsSync(path)
2、获取文件信息:fs.stat(path,callback)
fs.statSync(path)
它会给我们返回一个对象,这个对象中保存了当前对象状态的相关信息
3、删除文件:fs.unlink(path,callback)
fs .unlinkSync(path)
4、列出文件:fs.readdir(path,[,options],callback)
fs.readdirSync(path,[,options])
5、截断文件:fs.truncate(path,len,callback)
fs.truncateSync(path,len)
将文件修改为指定的大小
6、建立目录:fs.mkdir(path,[,mode],callback)
fs.mkdirSync(path,[,mode])
7、对文件进行重命名:fs.rename(oldPath,newPath,callback)
fs.renameSync(oldPath,newPath)
参数:oldPath:旧的路径
newPath:新的路径
8、监视文件的修改:fs.watchFile(filename[,options],listener)
参数:filename:要监视的文件的名字
options:配置选项
listener:回调函数,当文件发送变化时,回调函数会执行
var fs = require("fs");
//一、检查一个文件是否存在
// fs.existsSync(path)
var isExists = fs.existsSync("a.mp3");
console.log(isExists);
//二、获取文件的状态:fs.stat(path,callback)
// fs.statSync(path)
fs.stat("a.mp3", function(err, stat) {
//size:文件的大小
//isFile():是否是一个文件
//isDirectory():是否是一个文件夹
console.log(stat.size);
})
//三、删除文件:fs.unlink(path,callback)
// fs.unlinkSync(path)
fs.unlinkSync("a.mp3");
//四、列出文件 fs.readdir(path,[,options],callback)
// fs.readdirSync(path,[,options])
// 读取一个目录的目录结构
//files是一个字符串的数组,每一个元素就是一个文件夹,或文件的名字
fs.readdir(".", function(err, files) {
if (!err) {
console.log(files);
}
});
//五、截断文件 fs.truncate(path,len,callback)
// fs.truncateSync(path,len)
// 将文件修改为指定的大小
fs.truncateSync("hello2.txt", 14);
//六、建立目录
// fs.mkdir(path,[,mode],callback)
// fs.mkdirSync(path,[,mode])
fs.mkdirSync("abc");
//七、对文件进行重命名:fs.rename(oldPath,newPath,callback)
// fs.renameSync(oldPath,newPath)
// 参数:oldPath:旧的路径
// newPath:新的路径
fs.rename("hello.txt", "笔记本.txt", function(err) {
console.log("修改成功");
});
// 八、监视文件的修改:fs.watchFile(filename[,options],listener)
// 参数:filename:要监视的文件的名字
// options:配置选项
// listener:回调函数,当文件发送变化时,回调函数会执行
// 在回调函数中会有两个参数:cur 当前文件的状态
// prev 修改前文件的状态
// 这两个对象都是stats对象
fs.watchFile("hello2.txt", function(curr, prev) {
console.log("修改前文件大小:" + prev.size);
console.log("修改后文件大小:" + curr.size);
})