http协议
http协议英文全称HyperText Transfer Protocol,翻译成中文就是超文本传输协议。它是在 1990 年时提出。最早是1.0版本,现在普遍大家使用的是1.1版本
http协议的特点
- 简单快速:例如常用的方法有 GET、POST、PUT、DELETE等方法。由于HTTP协议简单,所以传输速度很快。
- 灵活:http允许传输任意类型的数据对象,传输的类型由Content-type来加以标记。
- 无状态,无连接:所谓无状态,就是指在http协议中,客户端的每次请求,对于服务器端来讲,都是一次新的请求。无连接和无状态意思差不多,指的是服务器处理完客户的请求后,并且进行处理完毕后,会立即断开连接。
如何标志网络章的资源
URL:对应的英文叫做Uniform Resource Identifier,翻译成中文就是统一资源标志符。其实就是用来定位网络资源在哪一个位置。但是,这个URL只是一个抽象的概念。就有点类似于(面向对象中类和对象的概念),类似于类。具体实现形式就是URL以及URN。
URL:对应的英语就是Uniform Resource Locator,翻译成中文就是统一资源定位符,通过URL,我们就是可以确定一个资源具体在哪个位置。
URN:对应的英文是Uniform Resource Name,翻译成中文是统一资源命名符,其实就是对你的网络资源进行一个命名。
URL的组成部分
比如下面这个URL:
http://www.abc.com:8080/news/index.html?userid=xiejie&userpwd=123#name
这就是一个标准的url,接下来我们要来看一下其组成部分。
协议名称:http协议。
域名部分:www.abc.com
端口部分:8080端口,如果访问的时候没有给出端口,默认也是使用80端口。
虚拟目录:从域名后的第一个/开始到最后一个/为止,这个部分是虚拟目录部分。虚拟目录部分也是一个url中必须的部分。上面的例子中,虚拟目录就是news
文件名部分:index.html
锚部分:#后面就是锚部分。
参数:是从?后面开始,比如上面的例子,userid=xiejie&userpwd=123
http请求(get和post的区别)
1:传输数据的存放位置,get放在URL后面,post放在HTTP请求里面;
2:传输数据的大小:由于浏览器对URL的长度有限制,所以get请求能带的数据也就有长度限制。但是post请求所带的数据没有限制。
3:安全性:get不安全,post比get安全。
HTTP请求方法
HTTP 1.0 主要有3种请求方式:GET、POST和HEAD
HTTP 1.1 又新增了5种请求方式:OPTIONS、PUT、DELETE、TRACE和CONNECT方法
HTTP响应
响应码由三位数字组成,分为5类:
1xx:表示请求已接收,继续处理。
2xx:成功。表示请求已经成功的处理了。
3xx:重定向。
4xx:客户端错误。
5xx:服务器端错误。
200:请求成功。
404:没有找到资源。NOT Found。
403:Forbidden,没有权限访问此资源。
500:服务器端发生错误。
301:重定向。
什么是 nodejs
说简单一点,它就是一个 javascript 的运行环境。nodejs 之父Ryan Dahl 基于google浏览器v8引擎做了一些封装,推出的nodejs
io.js ?
nodejs 是开源,一直由社区在推动其发展,后来有一个公司,Joynet 对nodejs 这个项目很感兴趣,所以这个公司就负责投资nodejs。但是有一群开发者(比较激进)认为Joynet经营策略过于缓慢,在2014年的时候,他们从nodejs里面独立出来了,决定单独发展。
分家没多久之后,Joynet表示要和这群人和解,io.js 项目又重新回归到node.js。io.js 后来就成为了“尝鲜版”,也就是 current 版本。而nodejs就是稳定版,也就是现在的lTS版本
nodejs 介绍
最早我们的js只能在浏览器里面跑。有一个人叫做道格瑞恩(nodejs 之父),它将google浏览器中v8引擎提取了出来,封装了一些c的接口进去,构建了一个可以跑js的环境,这个环境称之为nodejs。
所以nodejs一点都不神秘,它就是一个可以运行js的环境。以前js只能在浏览器运行,但是现在,js可以在浏览器和nodejs环境里面运行,也就是说,多了一个运行js 的环境。
一个web应用,大致分为两为两个端,一个是客户端,另一个是服务器端。那以前,说到客户端的开发,那就是html、css、js,说到服务器端的开发,那就是java、php、ruby、go。但是有了nodejs之后,我们可以使用js来开发服务器端了。
书写第一个nodejs程序
- 使用REPL环境来进行书写。英文全称read-eval-print-loop,翻译成中文就是输入-求值-输出。很多后端语言都提供了REPL环境。如何进入呢?输入node即可。
接下来就执行代码了:
退出REPL环境,Ctrl+c按两次。
REPL环境有什么用?测试小段代码的
2.书写js文件
模块化
因为在计算机程序开发中,随着代码越写越多,一个文件就会越来越长。就会导致这个软件不容易维护。
但是如果你将其拆分成多个模块 ,每个模块最好是一个单独的文件,那么这样维护起来就方便得多。
函数(方法)就是模块化思想最早的体现。
模块化的好处
- 生产效率高
多人协作
模块与模块之间耦合低 - 维护成本低
可以单元测试
node.js模块加载规则
nodejs中的模块分为三大类:文件模块、核心模块以及第三方模块。引入模块都是使用require方法来引入
- 文件模块
require(“路径.扩展名”)
路径分为两种:
(1)以 / 开头的模块标识,指向当前的盘符
(2)以…/或者./开头,这是一个相对路径
require(“/index.js”) 如果当前的文件在 c 盘,那它就是 C:\index.js
require(“./index.js”)当前目录下的 index.js
require(“…/index.js”)上一级目录的index.js - 核心模块
核心模块可以看作是nodejs的心脏,就是nodejs的一些内置模块。常见的内置模块有:
- 全局对象
- 常用的工具模块
- 事件模块
- 文件模块
- …
如果是核心模块,那么直接书写名字即可
rquire(“os”)
3.第三方模块:
所谓第三方模块,就是指社区或者第三方所开发的。比如mysql。
例如在nodejs中你要连接mysql数据库,那么需要先安装mysql
const mysql = require(“mysql”)
模块加载的顺序
有一个优先顺序,从上往下的优先顺序依次为:
- 核心模块,如 http、fs、path
- 以 . 或者 … 开始的相对路径文件模块
- 以 / 开始的绝对路径文件模块
- 非路径形式的模块(第三方模块)
模块缓存
对于加载同一个模块,nodejs只会加载一次。加载完一次之后,nodejs就会将该模块缓存起来。
index2.js
index.js:
运行结果:
缓存规则可以关闭(一般没必要,作为了解)
运行结果:
Commonjs 规范
Commonjs就是一个规范。正如javascript的规范是ECMAScript,可以这么说,javascript是ECMAScript规范的一种实现。以前flash的actionscript也是ECMAScript的一种实现。
Commonjs就是javascript在浏览器环境以外的规范,commonjs是由社区推动,Commonjs的实现也不是说只有nodejs,还有mongodb,couchdb,ringojs这都是Commonjs的实现,只不过nodejs这种实现最火。
Commonjs 里面的模块规范
通过module.exports来导出模块。使用require来导入模块。
index2.js
index.js
为了让开发人员使用起来更方便,nodejs还提供了一个exports对象,它是指向module.exports的,例如:
两种导出方式有区别(面试的时候可能会被问到)真正导出的是module.exports对象,而exports只是一个指向,指向module.exports
换句话说,module.exports我们是可以自由来改变值的,本来它是一个对象,但是可以改成其他数据(数组、字符串…)
但是,exports就不行,因为它是一个指向,如果改为其他值,相当于切断了它指向module.exports
上面的操作切断了指向,但是实际导出的又是module.exports对象,所以打印出来为空对象。
NPM
NPM,英文全称Node Package Manger,翻译中文就是node包管理器。
在NPM上面就有各种各样的包,比如readline-sync
常见的NPM相关命令
- 查看npm版本
npm -v
npm version
- npm init 初始化命令,用于初始化一个项目
一路回车即可。
当然,还有更简单。npm init -y
- npm 安装和卸载模块
安装:npm install 模块名(简写:npm i 模块名)
卸载:npm uninstall 模块名
全局安装和局部安装的区别?
全局安装:涉及到命令行的命令使用时,这一类的包需要全局安装。例如脚手架工具,而当前项目所依赖的包,就本地安装即可。
如何全局安装包?
npm i 模块名 -g
当一个包安装好以后,后端通过require方法引入,前端通过import来引入。
如何查看全局安装的目录?
npm root -g
在安装的时候,可以添加 --save,代表将你安装的包记录到package.json里面,但是,最新的npm,不用加这个参数,也会自动记录。
npm i express --save(作为了解,现在已经不用加这个后缀了)
- npm镜像安装
有些时候,我们在使用npm拉取包时,由于npm的服务器在国外,或者某个资源被屏蔽了,往往导致拉取包失败。有两种方法
(1)安装CNPM
使用CNPM来代替NPM,它的同步频率为每10分钟1次。
npm install -g cnpm
安装好cnpm之后,就可以通过cnpm i 模块名来安装模块,但是,需要注意不会自动写入package.json,需要写一下–save后缀
(2)修改npm的镜像
因为npm的服务器在国外,国内的阿里团队自己也搭建了一个国内服务器,也是每10分钟同步一次,我们就可以将镜像指向从原本的npm服务器修改为国内的taobao镜像
npm config set registry=https://registry.npm.taobao.org
接下来还可以通过下面的命令来查看当前的npm镜像指向
npm config list:查看当前的所有配置
npm config get registry:查看当前的registry(镜像)指向
- npm 查看模块
有些时候,我们想要知道当前安装了什么模块,使用npm list 命令可以进行,使用–depth后缀可以调节查看深度,同样可以添加-g参数来查看全局安装了哪些包
使用npm info 模块名可以查看某一个模块的具体信息
- 清除缓存
有些时候,我们安装包失败,是因为之前的包残留下来的缓存还存在,这个时候需要清楚缓存。
npm cache verify
npm cache clean --force
- npm 指令帮助
npm help 或者使用npm -l 来查看npm的帮助
npm help截图示例
npm -l截图示例
yarn
yarn和npm一样,都是包管理器,yarn是由google、facebook还有一些其他大厂一起推出的,主要是为了解决某些团队在使用npm上所面临的少数问题。
- 安装的时候无法保证速度的一致性
- 安全问题,因为npm在安装时允许运行代码
yarn的官网:https://yarn.bootcss.com/(使用方法具体参考官网)
nodejs 的两大特点,一个是异步无阻塞,另一个是事件驱动。
同步与异步
同步:它是指同一时间只能做一件事,也就是一件事做完之后才能做下一件事。同步的优点在于任何事情都是依次执行的。缺点在于如果同步代码中的某一个步骤花的时间比较长的话,就会导致后面的代码等待时间很长。
异步:刚好就和同步相反,同一时间可以做多件事。实现异步的方法有两种,一种是多线程的方式,还有一种就是单线程非阻塞的方式。
javascript由于是一门单线程语言,所以javascript中实现异步采用的是单线程非阻塞的方式。
阻塞与非阻塞
阻塞:传统的同步代码,当遇到网络通信,文件的读写的时候,通常要消耗较长的时间。这个时候我们的操作系统就会剥夺这个线程的CPU控制权,使其暂停,同时将资源让给其他的工作线程。这种线程的调度方式,我们就称之为阻塞 。
下面是一个关于阻塞的实际例子:
上面的例子,我们使用同步的方式进行IO书写。由于代码是同步的,所以写入文件时,会阻塞后面代码的执行。只有等待文件写入完成后,才能解除阻塞,继续执行后面的代码。
非阻塞:当线程遇到IO操作或者网络请求等比较耗时的操作时,不会像上面一样以阻塞的形式来等待操作完成,而是先将IO操作或者网络请求等发送给操作系统,由操作系统安排其他进程来完成,该线程继续往后面执行。
下面是一个异步写入文件的示例:
同步就是上面的阻塞式,而异步就是非阻塞式。
相比阻塞式,非阻塞式在处理任务的效率更高一些。在大型应用中,一般都会中间加一个 nodejs 服务器,用于分发请求。
nodejs中异步回调函数的特点:错误优先
如果是同步的代码,捕获错误可以通过try…catch的方式来捕获错误,但是异步的情况下,我们无法通过try…catch来进行错误的捕获,所以在nodejs中,把错误对象放在回调函数的第一个参数。
这就是nodejs中所谓的错误优先的特点。
事件驱动
这里有两个点,第一个是EventEmitter,第二个就是事件驱动模式。
- EventEmitter
在浏览器环境下,常见的事件有 click、mousemove、mouseenter 等,这些事件是不是环境已经给你定义好了的。但是,在nodejs中,需要开发者自己去定义一个事件类型,然后显式的来出发。
再来看一个EventEmitter的示例:
在上面的例子中,我们注册了两个事件监听器,然后在触发test1事件时,两个监听器都会触发。类似java中的方法重载。
实际开发中,不会直接使用到EventEmitter,但是其他内置模块很多多继承了EventEmitter。例如:fs、net、http都是EventEmitter的子类。
- 事件循环
js代码会将所有的代码分为两类,一类是同步任务,一类是类似于ajax、setTimeout这种异步任务。
具体的执行顺序:同步代码优先执行,如果遇到异步代码,会将这个异步任务放置到异步处理模块,继续执行后面的同步代码。
在异步处理模块中,当异步任务处理完成之后,将处理结果放置到一个叫做任务队列的地方。等待所有的同步代码执行完成以后,从任务队列获取异步的执行结果。
在异步处理模块,会将异步任务又分为两类:宏任务、微任务
宏任务(macro tasks):包括script整体代码、setTimeout、setInterval、IO、UI render
微任务(micro tasks):包括Promise、MutationObserver(HTML5 的一个新特性)
宏任务和微任务的区别:
主要就是执行顺序上的区别。先取一个宏任务队列来执行,取出所有的微任务,全部执行,再获取下一个宏任务。
示例代码如下:
nodejs系统的架构
nodejs基于chrome浏览器的v8引擎。除了v8引擎以外,还使用C和C++中的高效库,例如libev和libeio
整个架构图大致如下:
在整个nodejs中,javascript代码的运行机理如下图所示:
从左到右、从上到下,整个nodejs被分为4层,应用层、v8引擎层、node api层和libuv层。
线程池里面采用多线程的方式来执行异步代码,执行完之后放入事件循环队列。
因此,nodejs的单线程仅仅是指javascript在v8引擎中运行的时候,而并非整个nodejs的运行环境都是单线层。
使用node.js搭建服务器
下面是一个使用node.js搭建服务器的一个快速入门示例:
调用createServer方法就是创建一个服务器
该方法会返回一个对象,该对象有一个listen方法,决定监听哪一个端口。createServer方法还接收一个回调函数,如下:
该回调函数接收两个参数,一个是http请求、一个是http响应。
扩展1:常见对象
在nodejs里面,全局对象是谁?
浏览器环境下,全局对象为window,而在nodejs下,全局对象为global
使用var声明的变量,正如浏览器环境下会成为window对象的一个属性一样,在nodejs环境中,会成为global对象的一个属性。
理解“全局可用”与“全局对象”的区别
在nodejs中,提供了一些全局对象。它们是global对象的一个属性,只不过这个属性所对应的值又是一个对象。
但是有一些东西不是全局对象,但是是全局可用的。
像__dirname、__filename这种,是全局可用,每个模块都有,但是不是全局的。“全局对象”和“全局可用”的区别如下图:
关于全局对象
-
global:最顶层对象,上面有很多属性和方法。包括setTimeout、setInterval这些都是属于global对象的方法。
-
console:这是global对象的一个属性,对应的值也是一个对象。平时用的最多的就是console.log方法。但是除了log方法,还有warn、dir、table、time、timeEnd
console对象常见方法,参见文档:https://segmentfault.com/a/1190000000481884
- process对象:该对象也是global对象上面的一个属性,对应的值也是一个对象。该对象主要用于处理与进程相关的内容。
扩展2:常用工具模块
这里的常用工具模块,主要就是指nodejs中常用的一些工具模块。
- path:提供了一系列处理和转换文件路径的方法。
- url:提供了一系列处理url相关的方法。
- queryString:提供了字符串相关的处理
path模块
path.dirname§:返回路径p所在的目录
path.basename§:返回文件名的,和上面刚好相反
path.extname§:返回文件的扩展名。
path.resolve§:返回路径p的绝对路径地址。
path.join(p1, p2, …):将多个路径合并在一起。
url 模块
url.parse():将一个url字符串解析成一个对象,从而快速的提取某一个部分的值。
还接收第二个参数,传入布尔值
url.format():该方法可以看作是parse方法的逆向操作。将一个url对象,格式化为一个字符串。
url.resolve():替换域名后面第一个 / 后的内容
querystring模块
querystring.parse():该方法能把一个url字符串解析成一个键值对。
querystring.stringify():相当于上面方法的逆向操作。
文件操作
在nodejs中,文件操作需要引入fs模块,提供的方法大多分为同步和异步两种方式,但是我们在实际开发时,更多的肯定还是使用异步的方式。
- 文件的写入
fs.writeFileSync():同步写入
fs.writeFile():异步写入
下面是关于异步写入文件的示例:
- 文件的内容追加
在已经写了的文件后面追加内容,使用appendFile方法
- 文件的读取
使用readFile方法进行文件的读取
还可以通过toString方法也可以将流转换为utf8
- 文件复制
以前,在nodejs中,没有提供文件复制的api,所以需要我们自己来实现。现在新的nodejs提供了copyFile方法,可以直接进行文件的复制。
- 获取文件的信息
nodejs的fs模块提供stat的方法,可以获取文件的相关信息,返回的是一个对象。
该对象就有一些方法:
isFile():判断是否为文件
isDirectory():判断是否为目录
详细请参见官网:http://nodejs.cn/api/fs.html#fs_class_fs_stats
目录的操作
- 创建目录
在fs模块中有一个mkdir方法。
- 读取目录
读取目录使用readdir方法。例如:
读取的目录如下:
如果读取到所有的文件?
这里需要使用到递归。首先判断是否为文件,如果是文件,则打印出来,如果不是文件,则说明是一个目录,继续递归判断。
3. 删除目录
在nodejs中,删除目录很简单,使用rmdir方法即可,例如:
但是删除目录时,有一点需要注意,只能删除空目录。如果目录下面有文件,需要先删除文件,然后才能删除空目录。使用fs.unlink来进行文件的删除。
数据 IO 的操作
这里会涉及到几个概念,Buffer 缓冲区、Stream 文件流、Pipe 处理大文件
Buffer 缓冲区
我们都知道,javascript 最早只能在浏览器运行,提供了很多字符串相关的便利的操作方法,但是没有提供对二进制数据的操作,究其原因,就是因为这门语言只在浏览器里面运行,有字符串的操作就已经够了。
但是,nodejs 出现,使javascript用来开发服务器成为了可能。但是在开发服务器的时候,往往需要处理文件以及网络请求等涉及IO的操作,因此就需要处理大量的二进制数据。
所以在nodejs里面,就提供了Buffer以及Stream等模块来进行二进制数据的处理。
Buffer相当于是在内存中开辟了一个空间,我们程序员可以手动的去指定这个空间的大小。Buffer这个模块是一个global的全局属性,所以该模块不需要使用require来进行加载。
创建缓冲区,示例如下:
接下来我们向缓冲区写入一些数据
以下的代码示例了如何读取缓冲区的数据
拼接缓冲区
Stream 文件流
流的概念:
所谓流,就像水流一样。例如我们在网络上在线观看电影时候,通常比较大,高清甚至几个G,这个时候并不是整个电影加载好之后才能播放,而是加载一点,播放一点。
这里其实就是涉及到了流的概念,就像下图一样。
理想的方式就是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完。
在nodejs中,操作流的模块,就是stream模块。该模块提供了常用的几个事件:
- data:当有数据可读时会触发
- end:没有更多的数据可读时会触发
- error:发生错误时会触发
- finish:所有数据已被写入到底层系统时触发
stream模块所提供的流的类型:
- readable:可读流
- writeable:可写流
- duplex:双向流(可读可写流)
- transform:变换流(操作被写入数据、然后读出结果)
可读流
在nodejs中使用createReadStream方法就可以创建一个可读流。
可写流
使用createWriteStream方法可以创建一个可写流
代码示例:通过可写流来完成文件的复制功能。
使用Pipe 方法来处理大文件:
当文件很大的时候,使用chunk的效率就比较低。这个时候,可以考虑使用pipe方法。该方法相当于就是在两个文件之间建立了一个管道。
接下来我们使用pipe方法来简化上面的文件复制操作。
资源压缩
在做web开发的时候,服务器向客户端返回数据,通常为了提升效率,我们需要将资源进行一定的压缩。
(1)要明确浏览器端是否能够处理压缩文件
在浏览器端的http请求头的地方,会加上一个Accept-Encoding,你就可以使用gzip或者default来告诉服务器,我这边浏览器可以接受不了压缩文件。
(2)nodejs中如何进行文件的压缩
使用zlib模块,该模块是nodejs的一个核心模块(内置模块),直接require该模块,即可压缩文件,以及解压缩文件
压缩使用的是createGzip方法,下面是一个压缩文件的代码示例:
解压缩使用的是createGunzip方法,下面是一个解压缩文件的示例:
(3)实际的应用示例
在实际开发中,客户端浏览器如果能够支持压缩文件,那么我们将文件进行压缩,并返回,如果不支持,那么就只有返回未压缩的文件。
具体的代码示例:
nodejs 里面的net模块
使用net模块来创建一个服务器
首先,net模块是浏览器里面内置的一个模块。该模块可以用来创建网络服务。之前我们使用过http模块来创建一个http服务器。http模块的底层就是net模块。
http协议的底层是TCP/IP协议。而这个net模块就是创建一个TCP服务器。
下面的代码演示了使用net模块创建一个服务器:
当server对象被创建时,存在一些可以触发的事件。
- listening:当服务器调用server.listen 方法的时候会触发
- connection:当连接创建以后会触发此事件
- close:服务器关闭的时候会触发此事件
- error:发生错误的时候会触发
socket对象
当客户端访问服务器端时,会触发connection事件,会执行后面的回调函数。会有一个对象自动传入到回调函数里面(socket)
HTTP 服务,主要就是涉及HTTP模块。
GET请求
POST 请求