Node.js
笔记来源:学习B站尚硅谷教程,仅供参考
Node.js简介
• Node.js是一个能够在服务器端运行JavaScript的运行环境。
• Node采用Google开发的V8引擎运行js代码,使用事件驱动
、非阻塞
和异步I/O模型
等技术来提高性能,可优化应用程序的传输量和规模。
• Node大部分基本模块都用JavaScript编写
。在Node出现之前,JS通常作为客户端程序设计语言使用,以JS写出的程序常在用户的浏览器上运行。
• 目前,Node已被IBM、Microsoft、Yahoo!、Walmart、Groupon、SAP、 LinkedIn、Rakuten、PayPal、Voxer和GoDaddy等企业采用
• Node主要用于编写像Web服务器一样的网络应用,这和PHP和Python是类似的。
• 但是Node与其他语言最大的不同之处在于,PHP等语言是阻塞的而Node是非阻塞的。
• Node是事件驱动的。开发者可以在不使用线程的情况下开发出一个能够承载高并发的服务器。其他服务器端语言难以开发高并发应用,而且即使开发出来,性能也不尽人意。
• Node把JS的易学易用和Unix网络编程的强大结合到了一起。
• Node.js允许通过JS和一系列模块来编写服务器端应用和网络相关的应用。
• 核心模块包括文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL等)、二进制数据流、加密算法、数据流等等。Node模块的API形式简单,降低了编程的复杂度
。
• 使用框架可以加速开发。常用的框架有Express.js、Socket.IO和Connect等。Node.js的程序可以在Microsoft Windows、Linux、Unix、MacOS等服务器上运行。
• Node.js也可以使用CoffeeScript、TypeScript、Dart语言,以及其他能够编译成JavaScript的语言编程。
Node的历史
Node的用途
• Web服务API,比如REST
• 实时多人游戏
• 后端的Web服务,例如跨域、服务器端的请求
• 基于Web的应用
• 多客户端的通信,如即时通信
安装NODE.JS
Node
- Node是对ES标准一个实现,Node也是一个JS引擎
- 通过Node可以使js代码在服务器端执行
- Node仅仅对ES标准进行了实现,所以在Node中不包含DOM 和 BOM
- Node中可以使用所有的内建对象
String Number Boolean Math Date RegExp Function Object Array
而BOM和DOM都不能使用
但是可以使用 console 也可以使用定时器(setTimeout() setInterval())
- Node可以在后台来编写服务器
Node编写服务器都是单线程的服务器
- 进程
- 进程就是一个一个的工作计划(工厂中的车间)
- 线程
- 线程是计算机最小的运算单位(工厂中的工人)
线程是干活的
- 传统的服务器都是多线程的
- 每进来一个请求,就创建一个线程去处理请求
- Node的服务器单线程的
- Node处理请求时是单线程,但是在后台拥有一个I/O线程池
COMMONJS规范
ECMAScript标准的缺陷
• 没有模块系统(ES6才有)
• 标准库较少
• 没有标准接口
• 缺乏管理系统(类似于360卫士中的软件管理,专门管理软件的安装和卸载)
模块化
• 如果程序设计的规模达到了一定程度,则必须对其进行模块化。
• 模块化可以有多种形式,但至少应该提供能够将代码分割为多个源文件的机制。
• CommonJS 的模块功能可以帮我们解决该问题。
• CommonJS规范的提出,主要是为了弥补当前JavaScript没有模块化标准的缺陷。
• CommonJS规范为JS指定了一个美好的愿景,希望JS能够在任何地方运行。
• CommonJS对模块的定义十分简单:
– 模块引用
– 模块定义
– 模块标识
模块引用
在Node中引入模块,需要经历如下3个步骤(了解即可):
– 路径分析
– 文件定位
– 编译执行
• 在CommonJS规范中,定义了require()方法,这个方法接收模块标识(模块的路径也是属于模块标识),以此将一个模块引入到当前运行环境中。
模块引用的示例代码:
var math = require('math.js'); //引入math模块
//引入的模块的后缀可以省略
var math = require('math'); //引入math模块
在node中,通过require()函数来引入外部的模块,require()可以传递一个文件的路径作为参数(模块标识),node将会通过模块标识来找到指定的模块
注意:如果模块标识是文件路径,若使用相对路径,必须以.或..开头
node在使用模块名字来引入模块时,它会首先在当前目录的node_modules中寻找是否含有该模块
如果有则直接使用,如果没有则去上一级目录的node_modules中寻找
如果有则直接使用,如果没有则再去上一级目录寻找,直到找到为止
直到找到磁盘的根目录,如果依然没有,则报错
使用require()引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块
模块分成三大类
内建模块
- 由C++编写的底层模块
核心模块
- 由node引擎提供的模块
- 核心模块的标识就是--->模块的名字,例如,引入核心模块fs--->require("fs")
文件模块
- 由用户自己创建的模块
- 文件模块的标识就是--->文件的路径(绝对路径,相对路径),例如引入自定义模块02.module--->require("./0.2.module")
相对路径使用.或..开头
模块定义
• 在Node中一个js文件就是一个模块。
• 在Node中,每一个js文件中的js代码都是独立运行在一个函数中,而不是全局作用域,所以一个模块的中的变量和函数在其他模块中无法访问
//每一个js文件中的js代码都是独立运行在一个函数中,只不过这个函数隐藏起来而已, 可以暂时理解成这样,往下学习会具体说明这个隐藏起来的函数:
(function(){
//定义变量和方法
//...
console.log();
})();
• 在运行环境中,提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一的导出的出口。
• 在模块中还存在一个module对象,它代表模块自身,而exports是module的属性。
exports.xxx = function() {}; //暴露方法
exports.x = 1; //暴露属性
//
module.exports.xxxx = function() {};
模块自身函数的介绍
需要提前了解的知识点:
// 在函数内部,如果变量不用var、let等关键字标明,直接给该变量赋值,则该变量为全局变量
// a = 10; //此时的变量a为全局变量
var a = 10;
/*
在node中有一个全局对象 global,它的作用和网页中window类似
在全局中创建的变量都会作为global的属性保存
在全局中创建的函数都会作为global的方法保存
*/
node模块的自身函数及参数
//实际上模块中的代码都是包装在一个函数中,由nodeJs引擎调用执行的,并且在函数执行时,同时传递进了5个实参
function (exports, require, module, __filename, __dirname) {
//函数体是我们自己定义的变量和方法
}
exports
- 该对象用来将变量或函数暴露到外部
require
- 函数,用来引入外部的模块
module
- module代表的是当前模块本身
- exports就是module的属性
- 既可以使用 exports 导出,也可以使用module.exports导出
__filename
当前模块的完整路径(例如:C:\Users\lilichao\WebstormProjects\class0705\01.node\04.module.js)
__dirname
当前模块所在文件夹的完整路径(例如:C:\Users\lilichao\WebstormProjects\class0705\01.node)
测试
04.module.js
/*
arguments.callee
- 这个属性保存的是当前执行的函数对象
* */
console.log(arguments.callee + "");
运行结果:
exports 和 module.exports的区别
首先可以这样理解:在js模块中存在 let exports = module.exports的赋值语句,如下:
//nodeJs引擎会在调用模块方法时,会把module.exports的值赋值给形参变量exports,即函数就能得到module.exports的值(这个值可以是基本数据类型,也可以是引用数据类型,)
//实际上module中的exports是一个对象,可以module.exports.变量 = xx,所以module.exports的值一般是地址值,也就是形参变量exports也指向一个地址值
function (exports, require, module, __filename, __dirname) {
//可以看做
//let exports = undefined;
//exports = module.exports
}
即变量exports变量指向module.exports的地址值
当局部变量exports的指针改变时,exports 就不等于 module.exports
//exports重新改变指针,exports 就不等于 module.exports
exports = {};
console.log(exports == module.exports); //false
exports = {"a":"111"} //此时外部引用当前模块时,是无法访问 exports对象里面的变量的,因为外部引用当前模块时,都是从module.exports.xxx开始访问的
使用场景:
/*
exports 和 module.exports
- 通过exports只能使用.的方式来向外暴露内部变量
exports.xxx = xxx
- 而module.exports既可以通过.的形式,也可以直接赋值
module.exports.xxx = xxxx
module.exports = {}
*/
包 Package
概念
- CommonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具。
- CommonJS的包规范由包结构和包描述文件两个部分组成。
-
包结构
(不是重点,了解即可):用于组织包中的各种文件 -
包描述文件
(一个package.json文件,很重要):描述包的相关信息,以供外部读取分析
包结构
- 包实际上就是一个压缩文件,解压以后还原为目录。符合规范的目录,应该包含如下文件
- package.json 描述文件
- bin 可执行二进制文件
- lib js代码
- doc 文档
- test 单元测试
包描述文件
- 包描述文件用于表达非代码相关的信息,它
是一个JSON格式的文件 – package.json
,位于包的根目录下,是包的重要组成部分。 - package.json中的字段
– name、description、version、keywords、maintainers、contributors、bugs、licenses、repositories、dependencies、homepage、os、cpu、engine、builtin、directories、implements、scripts、author、bin、main、devDependencies。
NPM(Node Package Manager)
- CommonJS包规范是理论,NPM是其中一种实践。
- 对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。
NPM命令
- npm的命令
- npm -v 查看npm的版本
- npm version 查看所有模块的版本
- npm search 包名 搜索包
- npm install / i 包名 安装包
- npm remove / r 包名 删除包
- npm install 包名 --save 安装包并添加到依赖中 *****
- npm install 下载当前项目所依赖的包
- npm install 包名 -g 全局安装包(全局安装的包一般都是一些工具)
- npm remove 包名 删除一个模块
- npm install 包名 –registry=地址 从镜像源安装
- npm config set registry 地址 设置镜像源
cnpm
npm是node官方的包管理器,连接的服务器在国外。cnpm是个中国版的npm,是淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm
安装淘宝镜像
npm install -g cnpm --registry=https://registry.npm.taobao.org
永久使用
npm config set registry https://registry.npm.taobao.org
使用方法:和之前的npm使用的命令基本差不多,只是把npm换成cnpm
例
npm install math --save
cnpm install math --save
Buffer(缓冲区)
Buffer特点
- Buffer的结构和数组很像,操作的方法也和数组类似
- 数组中不能存储二进制的文件,而buffer就是专门用来存储二进制数据
- Buffer是Node中的一个全局变量,使用Buffer不需要引入模块,直接使用即可
- 在buffer中存储的都是二进制数据,但是在显示时都是以16进制的形式显示
- Buffer的大小一旦确定,则不能修改,Buffer中的内存不是通过JavaScript分配的,而是在底层通过C++申请的,Buffer实际上是对底层内存的直接操作
Buffer的操作
字符串转Buffer
Buffer.from(str , [encoding])
var str = "Hello 尚硅谷";
//将一个字符串保存到buffer中
var buf = Buffer.from(str);
console.log(buf.length); //15 占用内存的大小,一个汉字(utf-8)占3个字节
console.log(str.length);//9 字符串的长度
console.log(buf);//<Buffer 48 65 6c 6c 6f 20 e5 b0 9a e7 a1 85 e8 b0 b7>
//buf.toString() 将缓冲区中的数据转换为字符串
console.log(buf.toString()); //Hello 尚硅谷
Buffer转字符串
Buffer对象.toString([encoding] , [start] , [end]);
创建指定大小的Buffer对象
//不推荐使用Buffer构造函数来指定Buffer对象的大小
var buf2 = new Buffer(10);//10个字节的buffer
//用Buffer.alloc(长度)创建指定大小的Buffer对象,在内存中开辟一个连续空间(内存里的值是干净的,因为遗留的值被清空)
var buf2 = Buffer.alloc(10);
//创建一个指定大小的Buffer,但是可能包含敏感数据(通过alloUnsafe的方法分配的内存,内存里的值没有被清空,遗留了一些垃圾数据)
Buffer.alloUnsafe(size)
//通过索引,来操作buf中的元素
buf2[0] = 88;
buf2[1] = 255;
buf2[2] = 0xaa;
buf2[4] = 255;
//只要数字在控制台或页面中输出一定是10进制
for(var i=0 ; i<buf2.length ; i++){
console.log(buf2[i]);
}
运算结果:
fs(文件系统)
概述
- 在Node中,与文件系统的交互是非常重要的,服务器的本质就将本地的文件发送给远程的客户端
- Node通过fs模块来和文件系统进行交互
- 该模块提供了一些标准文件访问API来打开、读取、写入文件,以及与其交互。
- 要使用fs模块,首先需要对其进行加载,fs是核心模块,直接引入不需要下载
const fs = require("fs");
文件系统简单来说就是通过Node来操作系统中的文件
文件的状态:
同步和异步调用
-
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");
//打开文件
var fd = fs.openSync("hello.txt" , "w");
//向文件中写入内容
fs.writeSync(fd , "今天天气真不错~~~", 2);
//关闭文件
fs.closeSync(fd);
console.log("程序向下执行~~~");
异步文件写入
/**
异步文件写入
fs.open(path, flags[, mode], callback)
- 用来打开一个文件
- 异步调用的方法,结果都是通过回调函数的参数返回的
- 回调函数两个参数:
err 错误对象,如果没有错误则为null
fd 文件的描述符
fs.write(fd, string[, position[, encoding]], callback)
- 用来异步写入一个文件
fs.close(fd, callback)
- 用来关闭文件
*/
//引入fs模块
var fs = require("fs");
//打开文件
fs.open("hello2.txt","w",function (err , fd) {
//判断是否出错
if(!err){
//如果没有出错,则对文件进行写入操作
fs.write(fd,"这是异步写入的内容",function (err) {
if(!err){
console.log("写入成功~~");
}
//关闭文件
fs.close(fd , function (err) {
if(!err){
console.log("文件已关闭~~~");
}
});
});
}else{
console.log(err);
}
});
console.log("程序向下执行~~~");
简单文件写入writeFile
异步的简单文件写入:fs.writeFile(file, data[, options], callback)
同步的简单文件写入:fs.writeFileSync(file, data[, options])
注:此方法writeFile底层已经封装了打开文件和关闭文件的操作,所以我们在实际运用中只管写入即可(这就是简单文件的写入的含义)
writeFile方法的参数
- file 要操作的文件的路径
- data 要写入的数据
- options 选项,可以对写入进行一些设置(这个选项是可选值,不写时有默认值,不重要,可以忽略)
- callback 当写入完成以后执行的函数
/*
简单文件写入
fs.writeFile(file, data[, options], callback)
fs.writeFileSync(file, data[, options])
- file 要操作的文件的路径
- data 要写入的数据
- options 选项,可以对写入进行一些设置(这个选项是可选值,不写时有默认值,不重要,可以忽略)
- callback 当写入完成以后执行的函数
- flag
r 只读
w 可写
a 追加
*/
//引入fs模块
var fs = require("fs");
/*fs.writeFile("hello3.txt","这是通过writeFile写入的内容",{flag:"r+"} , function (err) {
if(!err){
console.log("写入成功~~~");
}else{
console.log(err);
}
});*/
// 正斜杠/ 和反斜杠\ 都可以;由于反斜杠是特殊字符,需要转义才可以用,见下
//C:\\Users\\lilichao\\Desktop\\hello.txt
//此方法writeFile底层已经封装了打开文件和关闭文件的操作,所以我们在实际运用中只管写入即可(这就是简单文件的写入的含义)
//在Node中,回调函数的参数一般是'错误参数' 优先,所以,回调函数中要么不写参数,要么第一个参数一定是'错误参数'
fs.writeFile("C:/Users/lilichao/Desktop/hello.txt", "这是通过writeFile写入的内容", { flag: "w" }, function (err) {
if (!err) {
console.log("写入成功~~~");
} else {
console.log(err);
}
});
流式文件写入
同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出
总结:大文件的写入适合用流式文件写入
创建一个可写流
/*
fs.createWriteStream(path[, options])
- 可以用来创建一个可写流
- path,文件路径
- options 配置的参数
*/
/*
同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出
*/
var fs = require("fs");
//流式文件写入
//创建一个可写流
/*
fs.createWriteStream(path[, options])
- 可以用来创建一个可写流
- path,文件路径
- options 配置的参数
*/
var ws = fs.createWriteStream("hello3.txt");
//可以通过监听流的open和close事件来监听流的打开和关闭
/*
on(事件字符串,回调函数)
- 可以为对象绑定一个事件
once(事件字符串,回调函数)
- 可以为对象绑定一个一次性的事件,该事件将会在触发一次以后自动失效
* */
//open事件:流打开的事件; close事件:流关闭的事件
//用on或者once方法给'流打开事件'和'流关闭事件'绑定监听事件
// 这里讨论用on 还是 once?
//用open事件来分析(close事件也是一样),数据流只会开启一次,直到关闭,所以open事件也只会触发一次;当用on方法绑定监听时,open事件只触发一次,但是绑定的回调函数一直和open事件保持联系,既然open只触发一次,一直绑定函数的话有点浪费资源,大才小用
// 用once方法绑定比较合适,因为当open事件和回调函数只发生一次捆绑
ws.once("open", function () {
console.log("流打开了~~~");
});
ws.once("close", function () {
console.log("流关闭了~~~");
});
//通过ws向文件中输出内容
ws.write("通过可写流写入文件的内容");
ws.write("今天天气真不错");
ws.write("锄禾日当午");
ws.write("红掌拨清清");
ws.write("清清真漂亮");
//关闭流,end方法或者close方法都可以,老版本的close方法在数据还没完全写入到指定文件夹时,已经把流的管道关闭了,可能得到的数据会不全;建议用end方法
// ws.end();
ws.close();
文件的读取
简单文件读取
/*
1.同步文件读取
2.异步文件读取
3.简单文件读取
fs.readFile(path[, options], callback)
fs.readFileSync(path[, options])
- path 要读取的文件的路径
- options 读取的选项
- callback回调函数,通过回调函数将读取到内容返回(err , data)
err 错误对象
data 读取到的数据,会返回一个Buffer
4.流式文件读取
*/
var fs = require("fs");
var path = "C:/Users/lilichao/Desktop/笔记.mp3";
fs.readFile("an.jpg" , function (err , data) {
if(!err){
//console.log(data);
//将data写入到文件中
fs.writeFile("C:/Users/lilichao/Desktop/hello.jpg",data,function(err){
if(!err){
console.log("文件写入成功");
}
} );
}
});
流式文件读取
方式1(麻烦):
/*
流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中
*/
var fs = require("fs");
//创建一个可读流
var rs = fs.createReadStream("C:/Users/lilichao/Desktop/笔记.mp3");
//创建一个可写流
var ws = fs.createWriteStream("a.mp3");
//监听流的开启和关闭
rs.once("open", function () {
console.log("可读流打开了~~");
});
rs.once("close", function () {
console.log("可读流关闭了~~");
//数据读取完毕,关闭可写流
ws.end();
});
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);
});
执行要点:
方式2:
/*
流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中
*/
var fs = require("fs");
//创建一个可读流
var rs = fs.createReadStream("C:/Users/lilichao/Desktop/笔记.mp3");
//创建一个可写流
var ws = fs.createWriteStream("b.mp3");
//pipe()可以将可读流中的内容,直接输出到可写流中
rs.pipe(ws);