1、基础内容
1、命令行
1、CMD命令
1、dir
:列出当前目录下的所有文件
2、cd 目录名
:进入指定目录
3、md 目录名
:新建文件夹
4、rd 目录名
:删除文件夹
5、a.txt
:直接打开当前目录下的文件
2、目录
1、.
:当前目录
2、..
:上一级目录
cd.. //返回上一级目录
3、环境变量(window系统中的变量 )
当我们在命令行窗口打开一个文件或调用一个程序时
1、系统会首先在当前目录下寻找文件程序
2、如果找到了则直接打开
3、如果没有找到则会依次到环境变量 path 的路径中寻找
4、如果找到了则会直接执行
5、如果没有找到则报错
可以将一些常用的文件或程序的路径添加到 path 下,这样我们就可以在任意位置来访问这些文件了
2、进程和线程
1、进程
进程负责为程序的运行提供必备的环境
进程就相当于工厂中的车间
2、线程
线程是计算机中最小的计算单位,线程负责执行进程中的程序
线程就相当于工厂中的工人
3、单线程和多线程
1、单线程
JS是单线程,在执行JS代码的时候网页是停止渲染的。
2、多线程
主流的语言,如Java
3、Node.js简介
1、Node.js是一个能够在服务器端运行JavaScript的开放源代码、跨平台JavaScript运行环境。
2、Node 采用 Google 开发的 V8 引擎运行 js 代码,使用事件驱动、非阻塞和异步I/O模型等技术来提高性能,可优化应用程序的传输量和规模
3、Node 大部分基本模块都用 JavaScript 编写。在 Node 出现之前,JS 通常作为客户端程序设计语言使用,以 JS 写出的程序常在用户的浏览器上运行
4、Node 是事件驱动的,开发者可以在不使用线程的情况下开发出一个能够承载高并发的服务器。其他服务器端语言难以开发高并发应用,而且即使开发出来,性能也不尽人意
5、Node.js 允许通过 JS 和一系列模块来编写服务器端应用和网络相关的应用
6、核心模块包括文件系统 I/O、网络(HTTP、TCP、UDP、DNS、YLS/SSL等)、二进制数据流、加密算法、数据流等等。Node 模块的 API 形式简单,降低了编程的复杂度
7、使用框架可以加速开发。常用的框架有Express.js、Socket.IO和Connect等。Node.js的程序可以在Microsoft Windows、Linux、Unix、Mac OS X等服务器上运行。
8、Node.js也可以使用CoffeeScript、TypeScript、Dart语言,以及其他能够编译成JavaScript的语言编程。
4、网页访问步骤
一个网页需要经过如下几个步骤:
1、用户访问网页并发送请求给网站服务器
2、服务器分出线程来处理该条请求,如果需要进行I/O
操作时,会自动分出一条I/O
线程
3、处理完I/O操作后,服务器将网页响应返回给用户
在如上的步骤中,第一和第三项可以做到优化,唯独无法优化的就是I/O
请求
所以为了防止服务器多条进程造成阻塞现象,服务器端只能是单进程,这样才不会形成阻塞
I/O (Input/Output)- I/O操作指的是对磁盘的读写操作
5、Node的历史
6、Node的用途
1、Web服务API,比如REST
2、实时多人游戏
3、后端的Web服务,例如跨域、服务器端的请求
4、基于Web的应用
5、多客户端的通信,如即时通信
7、执行js
1、在命令行中进入js文件所在目录
2、然后使用 node 文件名
命令来执行该js文件
2、CommonJS规范
1、ECMAScript标准的缺陷
1、没有模块系统
2、标准库较少
3、没有标准接口
4、缺乏管理系统
2、CommonJS规范
1、目的:弥补当前 JavaScript 没有标准的缺陷
2、愿景:希望JS能够在任何地方运行
3、定义:模块引用、模块定义、模块标识
3、模块化
1、如果程序设计的规模达到了一定程度,则必须对其进行模块化。
2、模块化可以有多种形式,但至少应该提供能够将代码分割为多个源文件的机制。
3、在Node中,一个JS文件就是一个模块
4、在Node中,每一个JS文件中的JS代码都是独立运行在一个函数中的,而不是全局作用域
3.1 模块引用
在规范中,定义了require()
方法,这个方法接手模块标识,以此将一个模块引入到当前运行环境中。
模块引用的示例代码:
let md = require("./module");
或
let md = require("./module.js");
1、路径如果使用相对路径,必须以 ./
或 ../
来开头
2、使用 require()
引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块
3.2 模块标识
我们使用require()
引入外部模块时,使用的就是模块标识,我们可以通过模块标识来找到指定的模块
模块分成两大类
1、核心模块
1、由node引擎提供的模块
2、核心模块的标识就是,模块的名字
示例
var fs = require("fs");
2、文件模块
1、由用户自己创建的模块
2、文件模块的标识就是文件的路径(绝对路径,相对路径)
相对路径使用.
或..
开头
var md = require("./02.module");
var math = require("./math");
var fs = require("fs");
//console.log(md);
console.log(math.add(123,456));
//console.log(fs);
3.3 模块定义
1、在运行环境中,提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一的导出的出口。
2、在模块中还存在一个module对象,它代表模块自身,而exports是module的属性。
3、在Node中一个文件就是一个模块。
exports.xxx = function() {}; //向外部暴露变量和方法
module.exports = {};
4、全局对象(global)
在 Node 中有一个全局对象 global,它的作用和网页中 Window 类似
1、在全局中创建的变量都会作为 global 的属性保存
2、在全局中创建的函数都会作为 global 的方法保存
当 Node 在执行模块中的代码时,它会在代码的外部添加如下代码:
function (exports, require, module, __filename, __dirname) {
//模块中的代码
console.log(arguments.callee + "");
}
实际上模块中的代码都是包装在一个函数中执行的并且在函数执行时,同时传递进了如下5个实参:
1、exports
:用来将变量或函数暴露到外部
2、require
:函数,用来引入外部的模块
3、module
:代表的是当前模块本身,exports就是它的属性
4、__filename
:当前模块的完整路径
5、__dirname
:当前模块所在文件夹的完整路径
5、exports与module.exports的区别
前者只能通过 exports.xxx
的方式来向外暴露内部变量,如:
exports.xxx = xxx;
后者既可以通过 module.exports.xxx
的方式,也可以通过直接赋值来向外暴露内部变量,如
module.exports.xxx = xxx;
module.exports = {};
赋值的区分方法:exports直接赋值是直接修改了变量,而module.exports赋值是修改了变量的属性(通过画引用数据类型的内存空间图来理解)
3、包 package
1、规范允许我们将一组相关的模块组合到一起,形成一组完整的工具
2、包规范由包结构和包描述文件两个部分组成
2.1 包结构:用于组织包中的各种文件
2.2 包描述文件:描述包的相关信息,以供外部读取分析
1、包结构
包实际上就是一个压缩文件,解压以后还原为目录,包含如下:
1、package.json
:描述文件(必须的)
2、bin
:目录,存放可执行二进制文件
3、lib
:目录,存放js代码
4、doc
:目录,存放文档
5、test
:目录,存放单元测试文件
2、包描述文件
1、包描述文件用于表达非代码相关的信息,是一个JSON
格式的文件,位于包的根目录下,是包的重要组成部分
2、package.json 主要含有:name(包的名)、description(描述)、version(版本)、keywords(关键字)、maintainers(主要贡献者)、contributors、bugs、licenses(协议)、repository(仓库)、dependencies(依赖)、devDependencies(开发依赖)、main(主文件) 等
注意:任何JSON文件中都不可以写注释
3、NPM(Node Package Manager)
1、CommonJS包规范是理论,NPM是其中一种实践。
2、对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。
3、npm 命令
1、npm -v
:查看 npm 的版本
2、npm version
:查看所有模块的版本
3、npm init -y
:初始化 npm 且跳过手动设置(如需手动设置需去除-y)
4、npm search 包名
:搜索包
5、npm install/i 包名
:安装包
6、npm remove/r 包名
:删除包
7、npm install 包名 --save
:安装包并添加到依赖中
8、npm install
:下载当前项目所依赖的包
9、npm install 包名 -g
:全局安装包(一般都是一些工具)
在安装包的时候,可能发现文件夹中并没有新增文件夹,可能是因为缺少了 package.json
文件,可通过初始化命令添加
4、cnpm 的简介与使用
国内使用 npm 存在的问题
安装npm后,每次我们安装包时,我们的电脑都要和npm服务器进行对话,去npm仓库获取包。
npm默认的仓库地址为:http://registry.npmjs.org
查看当前npm仓库地址命令:npm config get registry
,提示如下图:
因为npm的远程服务器在国外,所以有时候难免出现访问过慢,甚至无法访问的情况。
为了解决这个问题,我们有以下几个解决办法
1、使用淘宝的 cnpm 代替 npm
淘宝为我们搭建了一个国内的npm服务器,它目前是每隔10分钟将国外npm仓库的所有内容“搬运”回国内的服务器上,这样我们直接访问淘宝的国内服务器就可以了,它的地址是:https://registry.npm.taobao.org
使用方法:
第一种:直接安装cnpm 安装淘宝提供的cnpm,并更改服务器地址为淘宝的国内地址, 命令:
npm install -g cnpm --registry=https://registry.npm.taobao.org
以后安装直接采用 cnpm 替代 npm。
例如原生npm命令为:npm install uniq --save
,cnpm命令为:cnpm install uniq --save
。
第二种:替换npm仓库地址为淘宝镜像地址
命令:
npm config set registry https://registry.npm.taobao.org
查看是否更改成功:npm config get registry
,以后安装时,依然用 npm 命令,但是实际是从淘宝国内服务器下载的。
5、寻找包流程
1、node 在使用模块名字来引入模块时,会首先在当前目录的 node_modules
中寻找是否含有该模块
2、如果有则直接使用,如果没有则去上一层目录的 node_modules
中寻找
3、如果有则直接使用,如果没有则再去上一层目录的 node_modules
中寻找,直到找到为止
4、如果找到磁盘的根目录依然没有,则报错
一、Buffer 缓冲区
背景
1、浏览器没有储存图片文件等媒体文件的需求,JS 存的都是一些基本数据类型。
2、服务器需要存储图片/视频/音频等媒体文件,因此有了 Buffer 缓冲器。
1. Buffer 是什么
Buffer 是一个和数组类似的对象,不同是 Buffer 是专门用来保存二进制数据的。
2. Buffer 特点
1、它是一个【类似于数组】的对象,用于存储数据(存储的是二进制数据)。
2、Buffer 的效率很高,存储和读取很快,它是直接对计算机的内存进行操作。
3、Buffer 的大小一旦确定了,不可修改。
4、每个元素占用内存的大小为1字节,其元素为16进制的两位数,每个元素的范围是从 00 - ff
5、Buffer 是Node中的非常核心的模块,无需下载、无需引入,直接即可使用
3. Buffer 的操作
3.1 Buffer 的创建
// 创建一个指定size大小的Buffer
// 安全,里面全是0
var buf = Buffer.alloc(size);
//不安全,可能包含旧数据,需要重写所有数据
var buf = Buffer.allocUnsafe(size);
1、方式一
let buf = new Buffer(10)
console.log(buf)
new Buffer
方式创建一个 Buffer
的实例对象,性能特别差(需要在堆里开辟空间,然后清理空间-置零)
2、方式二
let buf2 = Buffer.alloc(10)
console.log(buf2)
创建一个 Buffer 的实例对象,性能比 new Buffer()
稍强一点,在堆中开辟一块空间(该块空间没有人用过)
3、方式三
let buf3 = Buffer.allocUnsafe(10)
console.log(buf3)
创建一个Buffer
的实例对象,性能最好的,在堆里开辟空间。
3.2 获取 Buffer 的长度
// 获取Buffer的长度
buf.length
3.3 Buffer 的转换
// 相当于Buffer.alloc(size);
var buf = Buffer.allocUnsafe(size);
buf.fill(0) //将可能出现的敏感数据用0全部填充
// 将一个字符串转换为Buffer
var buf = Buffer.from(str);
// 将一个Buffer转换为字符串
var str = buf.toString();
注意:
1、输出的 Buffer 为什么不是二进制?
输出的是16进制,但是存储的是二进制吗,输出的时候会自动转16进制。
2、输出的Buffer不为空?
在堆里开辟空间,可能残留着别人用过的数据,所以 allocUnsafe
二、fs 文件系统
1. Node 中的 fs 文件系统
在 Node 中有一个文件系统,所谓的文件系统,就是对计算机中的文件进行增删改查等操作。
在 NodeJs 中,给我们提供了一个模块,叫做fs模块(文件系统),专门用于操作文件。
fs模块是 Node 的核心模块,使用的时候,无需下载,直接引入。
// 引入fs模块
var fs = require("fs");
fs 中的大部分方法都为我们提供了两个版本:
1、同步方法:带 sync
的方法
同步方法会阻塞程序的执行
同步方法通过返回值返回结果
2、异步方法:不带 sync
的方法
异步方法不会阻塞程序的执行
异步方法都是通过回调函数来返回结果的
Nodejs 的一个特点是异步非阻塞,所以学习的都是异步方法。
2. fs 文件写入
2.1 普通文件写入
1.同步使用
1、使用 fs.openSync()
来打开文件(该方法会返回一个文件的描述符作为结果,可以通过该描述符来对文件进行各种操作),参数为:
1、path
:文件路径
2、flags
:操作的类型(w,r)
let fd = fs.openSync("./file/test1.txt", "w");
2、使用 fs.writeSync()
来写入文件,参数为:
1、fd
:文件描述符
2、string
:要写入的内容
3、position
:写入的起始位置(可选)
4、encoding
:写入的编码,默认为 utf-8(可选)
fs.writeSync(fd, "测试文件的第一行文字");
3、使用 fs.closeSync()
来关闭文件,参数为:
fd
:文件描述符
fs.closeSync(fd);
2.异步使用
使用异步 API 时,只需要在同步的基础上增加回调函数即可,回调函数需要通过参数来返回相应的值,参数通常有:
1、err
:错误对象,若没有错误即为 null
2、fd
:文件描述符
// 打开文件
fs.open("./file/test2.txt", "w", function (err, fd){
if(!err){
// 写入内容
fs.write(fd, "异步操作的第一行文字", function (err){
if(!err){
console.log("成功添加内容");
}
// 关闭文件
fs.close(fd, function (err){
console.log(err);
})
})
}
})
2.2 简单文件写入
简单文件写入方式是一种异步操作,事实上,后面讲的都是异步操作。
1.同步使用
使用 fs.writeFileSync()
来写入,参数为:
1、path
:文件路径
2、data
:要写入的内容
3、options
:可选,可以对写入进行一些设置
fs.writeFileSync("./file/test4.txt", "通过简单文件同步写入的内容");
2.异步使用
使用 fs.writeFile()
来写入,参数比同步多一个回调函数
fs.writeFile(file, data[, options], callback(err) => {})
1、file
:要写入的文件路径+文件名+后缀
2、data
:要写入的数据
3、options
:配置对象(可选参数)
1.encoding:设置文件的编码方式,默认值:utf8(万国码)
2.mode:设置文件的操作权限,默认值是:0o666 = 0o222 + 0o444
0o111:文件可被执行的权限,.exe .msc 几乎不用,linux有自己一套操作方法。
0o222:文件可被写入的权限
0o444:文件可别读取的权限
3.flag:打开文件要执行的操作,默认值是 'w'
a:追加
w:写入
4.callback:回调函数
err:错误对象
举例:
//引入内置的fs模块
let fs = require('fs')
fs.writeFile("./file/test3.txt", "通过简单文件异步写入的内容", function (err){
console.log(err);
if(!err){
console.log("写入成功");
}
})
在Node中有这样一个原则:错误优先。所以有了回调:err=>{}
。
3.flag状态
打开文件的状态如下图
2.3 流式写入
以上两种写入方法都不适合大文件的写入,性能较差,容易导致内存溢出,因此推荐使用流式写入方法
可以把流式文件写入比作是使用水管从河里往家里运水,流式文件写入首先要创建流(水管),然后要检测流的状态,文件传输完毕,则要关闭流(拿开水管)。
1、使用 fs.createWriteStream()
来创建一个可写流(水管搭建好了),参数为:
1、path
:文件路径
2、options
:配置的参数,可选
let ws = fs.createWriteStream("./file/test5.txt");
2、使用 ws.write()
来向文件中输入内容:
ws.write("第一次写入");
ws.write("第二次写入");
3、使用 ws.close()/ws.end()
来关闭该可写流(前者在低版本Node中会出现一些错误,水管不用了,得收起来)
ws.close();
ws.end();
4、使用 ws.once()
可以为对象绑定一个一次性的事件来监听可写流的关闭与否(只要用到了流,就必须监测流的状态):
ws.once("open", function (){
console.log("可写流打开了~~");
})
ws.once("close", function (){
console.log("可写流关闭了~~");
})
3. 文件读取
3.1 简单读取
使用 fs.readFile()
来读取,参数比同步多一个回调函数
fs.readFile("./file/test1.txt", function (err, data){
if(!err){
console.log(data.toString());
}
})
若读取与写入同时使用时,可以达到复制的效果,如下:
fs.readFile("./file/1.jpg", function (err, data){
if(!err){
console.log(data);
fs.writeFile("./file/1_copy.jpg", data, function (err){
if (!err){
console.log("写入成功~~~");
}
})
}
})
3.2 流式读取
3.2.1 常规读取+写入
读取可读流中的数据,需要为可读流绑定一个 data 事件,事件绑定完毕会自动开始读取数据(读取到的数据都在回调函数的参数中)
// 创建一个可读流
let rs = fs.createReadStream("./file/test1.txt");
// 创建一个可写流
let ws = fs.createWriteStream("./file/test1_copy.txt");
// 监听是否开始关闭
rs.once("open", function (){
console.log("可读流打开了");
})
rs.once("close", function (){
console.log("可读流关闭了");
ws.end(); //读完了才关,否则读一条就关了
})
// 读取可读流的数据
rs.on("data", function (data){
console.log(data);
// 写入可写流中
ws.write(data);
})
3.2.2 简便读取+写入
无需绑定 data
事件,只需使用可写流的 rs.pipe()
方法即可将可读流中的内容直接输出到可写流中
// 创建一个可写流
let rs = fs.createReadStream("./file/那些花儿.mp3");
// 创建一个可写流
let ws = fs.createWriteStream("./file/那些花儿_copy.mp3");
// 监听是否开始关闭
rs.once("open", function (){
console.log("可读流打开了");
})
rs.once("close", function (){
console.log("可读流关闭了");
})
rs.pipe(ws);
4. 其他方法
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,[options],callback) / fs.mkdirSync(path,[options])
:创建一个目录
7、fs.rmdir(path,callback) / fs.rmdirSync(path)
:删除一个目录
8、fs.rename(oldPath,newPath,callback) / fs.renameSync(oldPath,newPath)
:对文件进行重命名,同时可以实现移动的效果
9、fs.watchFire(filename[,options],listener)
:监视文件的修改
三、http模块
1、创建最基本的web服务器
1.导入Http模块
const http=require('http')
2.创建web服务器实例
const server=http.createServer()
3.为服务器实例绑定request事件,监听客户端的请求
//使用服务器实例的.on()方法,为服务器绑定一个request事件
server.on('request',(req,res)=>{
//只要有客户端来请求我们自己的服务器,就会触发request事件,从而调用这个事件处理函数
console.log('someone visit our web server.')
})
4.启动服务器
//调用server.listen(端口号,cb回调)方法,即可启动web服务器
server.listen(80,()=>{
console.log('http server running at http://127.0.0.1')
})
2、req请求对象
只要服务器接收到了客户端的请求,就会调用server.on()
为服务器绑定的request
事件处理函数
如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:
server.on('request',(req,res)=>{
//req是请求对象,它包含了与客户端相关的数据和属性,例如:
//req.url是客户端请求的url地址
//req.method 是客户端的method请求类型
const str='Your request url is ${req.url},and request method is ${req.method}'
console.log(str)
})
3、res响应对象
server.on('request',(req,res)=>{
//res是响应对象,它包含了与服务器相关的数据和属性,例如:
//要发送到客户端的字符串
const str='Your request url is ${req.url},and request method is ${req.method}'
//res.end()方法的调用:
//向客户端发送指定内容,并结束这次请求的处理过程
res.end(str)
})
4、解决中文乱码问题
当调用res.end()
方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式
server.on('request',(req,res)=>{
//发送的内容包含中文
conststr='您请求的url地址是${req.url},请求的method类型是${req.method}'
//为了防止中文显示乱码的问题,需要设置响应头
res.setHeader('Content-Type','text/html;charset=utf-8')
//把包含中文的内容,响应给客户端
res.end(str)
})
5、根据不同的url响应不同的html内容
server.on('request',function(req,res){
const url =req.url //1.获取请求的Url地址
let content ='<h1>404 Not found!</h1>'//2.设置默认的内容
if(url=='/'||url ==='/index.html'){
content='<h1>首页</h1>'//3.用户请求的是首页
}else if(url==='/about.html'){
content='<h1>关于页面</h1>'
}
//为了防止中文显示乱码的问题,需要设置响应头
res.setHeader('Content-Type','text/html;charset=utf-8')
//把包含中文的内容,响应给客户端
res.end(str)
})