前端经典面试题

526 阅读45分钟

HTML

语义化

是什么

用最恰当的标签来标记内容

为什么

  • 在没有css的情况下,也能很好地呈现网页
  • html结构清晰,便于阅读和代码维护
  • 提升搜索引擎优化效果(SEO)
  • 有助于爬虫

拓展:SEO搜索引擎优化

怎么做

  • 少用无语义标签div、span
  • 语义不明显时用p
  • 强调用strong或em
  • 表格标题caption,表头thead,主体tbody,表头tr+th,内容tr+td
  • 表单用fieldset包起,legend说明
  • 表单元素input用lable说明,for="id"链接

html5新元素

新的语义元素

和div的效果一样,需要css进一步修饰

<header>	定义文档或节的页眉。
<nav>	        定义文档内的导航链接。
<section>	定义文档中的节。
<aside>	        定义页面内容之外的内容。
<article>	定义文档内的文章。
<footer>	定义文档或节的页脚。

<main>	        定义文档的主内容。
<mark>	        定义重要或强调的内容。
<figure>	定义自包含内容,比如图示、图表、照片、代码清单等等。
...

新的表单元素

<datalist>	定义输入控件的预定义选项。
<keygen>	定义键对生成器字段(用于表单)。
<output>	定义计算结果。

html基本结构

<!DOCTYPE html>  // H5标准声明,使用 HTML5 doctype,不区分大小写
<html lang="en">  // 规定语言,英语en,中文ch,日语ja,非中文谷歌浏览器会提示是否翻译此页
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>又是风轻云淡的一天</p>
</body>
</html>

meta标签

meta标签总结

是什么

metadata,元数据,本身不会呈现在网页中,但被浏览器所识别,用来描述数据的数据(字符集,作者,关键字,描述...)

使用

字符集

<meta charset="UTF-8"> // 字符集,中文是gb2312

name+content

<meta name="description" content="不超过150个字符"/>  // 页面描述
<meta name="keywords" content=""/>  // 关键词
<meta name="author" content=""/>  // 作者
<meta name="generator" content=""/>  // 网页制作软件
<meta name="copyright" content=""/>  // 版权

// 定义爬虫的索引方式
<meta name="robots" content=""/>
1.none : 搜索引擎将忽略此网页,等价于noindex,nofollow。
2.noindex : 搜索引擎不索引此网页。
3.nofollow: 搜索引擎不继续通过此网页的链接索引搜索其它的网页。
4.all : 搜索引擎将索引此网页与继续通过此网页的链接索引,等价于index,follow。
5.index : 搜索引擎索引此网页。
6.follow : 搜索引擎继续通过此网页的链接索引搜索其它的网页。

// 双核浏览器渲染方式
<meta name="renderer" content="webkit"> //默认webkit内核
<meta name="renderer" content="ie-comp"> //默认IE兼容模式
<meta name="renderer" content="ie-stand"> //默认IE标准模式

移动端适配

meta+viewport

// 移动端适配
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

http-equiv+content

// 用于告知浏览器以何种版本来渲染页面。一般都设置为最新模式,在各大框架中这个设置也很常见
// 指定IE和Chrome使用最新版本渲染当前页面
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 

// 指定请求和响应遵循的缓存机制
<meta http-equiv="cache-control" content="">
<meta http-equiv="cache-control" content="no-siteapp" /> // 禁止百度转码
1.no-cache: 先发送请求,与服务器确认该资源是否被更改,如果未被更改,则使用缓存。
2.no-store: 不允许缓存,每次都要去服务器上,下载完整的响应。(安全措施)
3.public : 缓存所有响应,但并非必须。因为max-age也可以做到相同效果
4.private : 只为单个用户缓存,因此不允许任何中继进行缓存。(比如说CDN就不允许缓存private的响应)
5.maxage : 表示当前请求开始,该响应在多久内能被缓存和重用,而不去服务器重新请求。例如:max-age=60表示响应可以再缓存和重用 60 秒。

// 用于设定网页的到期时间,过期后网页必须到服务器上重新传输
<meta http-equiv="expires" content="Sunday 26 October 2016 01:00 GMT" />

// 网页将在设定的时间内,自动刷新并调向设定的网址
<meta http-equiv="refresh" content="2;URL=http://www.xxx.com/"> // 意思是2秒后跳转向对应网站

// cookie设定
<meta http-equiv="Set-Cookie" content="name, date">

JavaScript

闭包Closure

是什么

是有权访问另一个函数作用域内变量的函数

装逼解释:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行

实现方式

在函数内部创建另一个函数

特点

  • 函数在执行完毕后其作用域和变量会被销毁,但如果有闭包的存在,其作用域会一直保存到闭包不存在为止
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

// 释放对闭包的引用,此处涉及垃圾回收机制
add5 = null;
add10 = null;
  • 闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。
function test(){
  var arr = [];
  for(var i = 0;i < 10;i++){
    arr[i] = function(){
      return i;
    };
  }
  for(var a = 0;a < 10;a++){
    console.log(arr[a]());
  }
}
test(); // 连续打印 10 个 10

// 把for语句中的var改成let即可打印出0到9
// 因为let隐式劫持块级作用域(let在一对大括号中不能定义两个相同的变量)

//另一种方法
arr[i] = function(i){
    return functon(){
        return i;
    }
}(i)
  • 闭包中的this
var name = "The Window";

var obj = {
  name: "My Object",
  
  getName: function(){
    var that = this;
    return function(){
      return that.name;
    };
  }
};

console.log(obj.getName()());  // My Object

闭包的应用

设计私有的方法和变量。

原理:利用构造函数,在函数内部定义私有的变量和方法,再创建特权方法,特权方法作为闭包有权访问私有变量和方法。这样,在外部只能通过构造函数的实例才能访问这些私有的变量和方法了。

function Animal(){
  
  // 私有变量
  var series = "哺乳动物";
  function run(){
    console.log("Run!!!");
  }
  
  // 特权方法
  this.getSeries = function(){
    return series;
  };
}

模块模式(单例模式)

原理:单例就是只有一个实例的对象。普通方式创建,是用字面量的方式创建一个对象。而模块模式创建,是调用匿名函数+立即执行+返回单例对象,在函数内部可以定义私有的变量,返回的对象定义共有变量

var singleton = (function(){
  
  // 私有变量
  var age = 22;
  var speak = function(){
    console.log("speaking!!!");
  };
  
  // 特权(或公有)属性和方法
  return {
    name: "percy",
    getAge: function(){
      return age;
    }
  };
})();

匿名函数的作用:块级作用域

放在全局中,可以避免污染全局变量

function outer(){
    // 语句
    (function(){
        // 块级作用域
    })()
}

闭包的缺点

  • 占用内存,可能会造成内存泄漏

面试题

function fun(n,o){
  console.log(o);
  return {
    fun: function(m){
      return fun(m,n);
    }
  };
}

var a = fun(0);  // ?
a.fun(1);        // ?        
a.fun(2);        // ?
a.fun(3);        // ?

var b = fun(0).fun(1).fun(2).fun(3);  // ?

var c = fun(0).fun(1);  // ?
c.fun(2);        // ?
c.fun(3);        // ?
undefined
0
0
0
undefined, 0, 1, 2
undefined, 0
1
1

HTTP

HTTP协议

是什么

Hypertext Transfer Protocol,超文本传输协议

浏览器和服务器之间进行“沟通”的一种规范

特点

  • 基于tcp/ip协议的应用层协议(tcp/ip是传输层的)
  • 基于客户端/服务端架构(C/S),浏览器可以看作http客户端
  • 无连接:每次连接只处理一个请求,请求结束立即断开
  • 无状态:无法记住上一次的请求,每次请求都是独立的

扩展:

  • UDP不可靠,不安全,会丢包
  • TCP可靠,且是有连接的
  • 数十万客户端和服务端保持连接状态,对服务器要求严格,于是衍生出http
  • http请求之后,服务器端立即关闭连接、释放资源。这样既保证了资源可用,也吸取了TCP的可靠性的优点
  • B/S结构与C/S结构

消息结构

请求报文

请求行request line,请求头header,空行,请求数据

请求行
GET /BOOK/JAVA.HTML HTTP/1.1
请求方式、请求资源的路径URL、http协议的版本号。

请求头
包含客户机请求的服务器主机名,客户机的环境信息等。
Host:请求的服务器的主机名(域名+端口号)
Accept:客户端可识别的内容类型  (例如:Accept:text/html,image/*)
User-Agent:客户端的软件环境(操作系统,浏览器版本等)
Accept-Charset:客户端采用的编码格式
Accept-Encoding:客户端支持的数据压缩格式
Accept-Language:客户端语言环境
If-Modified-Since:客户端资源的缓存时间
Referer:客户端是从哪个资源来访问服务器的(防盗链)
Cookie:发送请求时将Coockie信息带给服务器
Connection:请求完成后,是否保持连接
--- close:在完成本次请求的响应后,断开连接
--- keep-alive:在完成本次请求的响应后,保持连接,以等待后续请求
Date:当前请求的时间
Cache-Control:指定请求和响应遵循的缓存机制。
Content-Length:请求的内容长度。
Content-Type:请求的与实体对应的MIME信息。

换行

请求体
就是指浏览器端通过http协议发送给服务器的实体数据。例如:name=dylan&id=110
(get请求时,通过url传给服务器的值,此部分为空。post请求时,通过表单发送给服务器的值)

扩展:MIME参考手册

常见MIME类型:

  • 超文本标记语言 text/html
  • 普通文本 text/plain
  • js文件 text/javascript(过时),application/javascript

浏览器显示的内容都有 HTML、XML、GIF、Flash 等,浏览器是通过 MIME Type 区分它们,决定用什么内容什么形式来显示。

MIME Type 是该资源的媒体类型,MIME Type 不是个人指定的,是经过互联网(IETF)组织协商,以 RFC(是一系列以编号排定的文件,几乎所有的互联网标准都有收录在其中) 的形式作为建议的标准发布在网上的,大多数的 Web 服务器和用户代理都会支持这个规范 (顺便说一句,Email 附件的类型也是通过 MIME Type 指定的)。 媒体类型通常通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的。例如:Content-Type:text/HTML。

通常只有一些卓哉互联网上获得广泛应用的格式才会获得一个 MIME Type,如果是某个客户端自己定义的格式,一般只能以 application/x- 开头。

响应报文

状态行,响应头,空行,响应正文

状态行 
HTTP/1.1 200 OK   // 协议版本 状态码 状态描述
(协议的版本号是1.1  响应状态码为200  响应结果为 OK)

响应头
Allow:服务器支持哪些请求方法(如GET、POST等)。
Content-Type:返回的数据的类型MIME
Content-Length:返回的数据的长度
Date:服务器响应时间
Server:服务器的软件信息
Connection:响应完成后,是否断开连接。
--- close:连接已经关闭
--- keep-alive:连接已保持,在等待本次连接的后续请求
Expries:告诉浏览器回送的资源缓存多长时间(过期时间)。如果是-1或者0,表示不缓存
Cache-Control:缓存控制   no-cache
Set-Cookie:设置Http Cookie。
Location:这个头配合302状态码,用于告诉客户端找谁
Content-Encoding:告诉浏览器,服务器的数据压缩格式
Last-Modified:告诉浏览器当前资源缓存时间
Refresh:告诉浏览器,隔多长时间刷新
Transfer-Encoding:告诉浏览器,传送数据的编码格式
ETag:缓存相关的头(可以做到实时更新)

空行

响应体
响应包含浏览器能够解析的静态内容,例如:html,纯文本,图片等等信息

请求方式

HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法。

HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

GET - 获取资源,使用 URL 方式传递参数,大小为 2KB
http://www.example.com/users - 获取所有用户

POST - 修改资源,HTTP Body, 大小默认8M
http://www.example.com/users/a-unique-id - 新增用户

PUT - 更新资源
http://www.example.com/users/a-unique-id - 更新用户

DELETE - 删除资源
http://www.example.com/users/a-unique-id - 删除用户

HEAD - 和GET类似,但是仅获取请求头
常用于判断某个资源是否存在,通过Content-Length判断

GET和POST的区别

两者的区别

  • 发包次数。GET发TCP包一次,POST发TCP包两次。POST先发送请求头,响应100后,再发送请求体,所以POST可能会比GET慢一点,网速好的情况下可忽略。
  • 传输数据量的大小。GET用URL进行传递,数据量受浏览器限制,一般为2kb,而POST无限制。
  • 用途不同。GET用于获取信息,不会改变服务端的数据,POST可能会修改服务端的数据。
  • 请求数据的存放位置不同。GET请求数据放URL中,以 ? 分割URL和传输数据,多个参数用 & 连接,浏览器地址栏可见。POST放在请求体中,抓包才可见。
  • 缓存。GET会被主动缓存,POST不会。缓存就是请求数据相同的话就直接返回缓存的数据。
  • 浏览器回退。GET无影响,但POST会再次发送请求。
  • 历史记录。GET请求参数会存放在历史记录中,POST的参数不会被保留。
  • 编码方式。GET只支持URL编码,POST支持多种。

装B时刻:本质上GET和POST都是TCP请求。

状态码

1xx:指示信息 – 表示请求已接收,继续处理
2xx:成功 – 表示请求已被成功接收
3xx:重定向 – 要完成请求必须进行更进一步的操作
4xx:客户端错误 – 请求有语法错误或请求无法实现
5xx:服务器错误 – 服务器未能实现合法的请求

常见状态码

200 OK:客户端请求成功
204 No Content:没有新文档,浏览器应该继续显示原来的文档
206 Partial Content:客户发送了一个带有Range头的GET请求,服务器完成了它
301 Moved Permanently:所请求的页面已经转移至新的url
302 Found:所请求的页面已经临时转移至新的url
304 Not Modified:客户端有缓冲的文档并发出了一个条件性的请求,服务器告诉客户,原来缓冲的文档还可以继续使用。
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden:对被请求页面的访问被禁止
404 Not Found:请求资源不存在
500 Internal Server Error:服务器发生不可预期的错误
503 Server Unavailable:请求未完成,服务器临时过载或当机,一段时间后可能恢复正常

所有状态码

请求过程

从输入URL到浏览器显示页面发生了什么

解决无连接(keep-alive)

将请求头的Connection设置为keep-alive,服务器收到请求后,会在响应头中回应。

这样一来,客户端和服务端之间的HTTP连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。

参考文档:解决http的无状态和无连接

解决无状态(Cookie和Session)

通过引入cookie和session来维护状态信息。(即可以跟踪浏览器用户的身份)

cookie原理

用户第一次访问服务器的时候,服务器响应报头通常会出现一个Set-Cookie响应头,这里其实就是在本地设置一个cookie,当用户再次访问服务器的时候,http会附带这个cookie过去。

一般都是服务端那边响应一个设置cookie的操作,用js也可以自己设置和访问cookie的,用document.cookie

cookie属性

name
cookie名

value
cookie值

comment
说明

domain
可以访问该Cookie的域名。
如果设置为“.baidu.com”,则所有以“baidu.com”结尾的域名都可以访问该Cookie;
第一个字符必须为“.”

maxAge
Cookie失效的时间,单位秒。
负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。
为0,表示删除该Cookie。

path
该Cookie的使用路径

secure
该Cookie是否仅被使用安全协议传输。这里的安全协议包括HTTPS,SSL等。默认为false

session原理

当客户端请求创建一个session的时候,服务器会先检查这个客户端的请求里是否已包含了一个session标识 - sessionId。

如果已包含这个sessionId,则说明以前已经为此客户端创建过session,服务器就按照sessionId把这个session检索出来使用(如果检索不到,可能会新建一个) 如果客户端请求不包含sessionId,则为此客户端创建一个session并且生成一个与此session相关联的sessionId

sessionId的值一般是一个既不会重复,又不容易被仿造的字符串,这个sessionId将被在本次响应中返回给客户端保存。保存sessionId的方式大多情况下用的是cookie(还可以是head和url)。

两者区别

  • cookie放在浏览器端,session放在服务器端
  • cookie大小不能超过4kb,seesion无限制
  • cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session
  • 有效期上,session由服务端设置,cookie可由js改变
  • 服务器压力不同,cookie不占用服务器资源,session占用服务器的内存资源,可用分布式session解决
  • 生命周期不同。cookie的生命周期是累计时间,即如果我们cookie设置setMaxAge(30),则30秒后失效;session生命周期是间隔时间,如果设置session20min,只在20min内没有访问session,则session失效

HTTP历史版本

HTTP/0.9

  • 只支持GET请求,所以请求数据的大小有限
  • 服务端只能返回HTML纯文本
  • 无请求头和响应头
  • 无连接,无状态码
请求:
GET /mypage.html

响应:
<HTML>
这是一个非常简单的HTML页面
</HTML>

HTTP/1.0

  • 新增POST和HEAD请求方式
  • 根据Content-Type可以支持多种数据格式(图片、音视频、二进制)
  • 有请求头和响应头,请求行要添加协议版本
  • 无连接,但可由Connection字段变为有连接,新增状态码
  • 新增缓存,权限,内容编码

HTTP/1.1

  • 新增OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT
  • 默认长连接,keep-alive
  • 管道机制:同一个TCP连接里,允许多个请求同时发送。基于上面长连接的基础,管道化可以不等第一个请求响应继续发送后面的请求,但响应的顺序还是按照请求的顺序返回。
请求1 > 响应1 --> 请求2 > 响应2 --> 请求3 > 响应3
  • 分块传输编码:可以不使用Content-Length字段,而使用"分块传输编码",对于很耗服务器操作的数据,服务器就不要等到所有操作完成,才能发送数据,因为这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用"流模式"(stream)取代"缓存模式"(buffer)。
请求/响应头
Transfer-Encoding: chunked

// 每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度。
// 最后是一个大小为0的块,就表示本次回应的数据发送完了
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
 
25
This is the data in the first chunk
 
1C
and this is the second one
 
3
con
 
8
sequence
 
0
  • HTTP/1.1支持文件断点续传。RANGE:bytes,HTTP/1.0每次传送文件都是从文件头开始,即0字节处开始。RANGE:bytes=XXXX表示要求服务器从文件XXXX字节处开始传送,断点续传。即返回码是206(Partial Content)
  • 缓存处理。当浏览器请求资源时,先看是否有缓存的资源,如果有缓存,直接取,不会再发请求,如果没有缓存,则发送请求。 通过设置字段cache-control来控制缓存。
  • 新增Host字段,能够使不同域名配置在同一个IP地址的服务器上。

HTTP/2

  • 二进制协议。HTTP1.1数据可以是文本也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧,好处是将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,提高传输效率,还可以定义额外的帧,使用文本实现这种功能,解析数据将会变得非常麻烦。
  • 多路复用。客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"。(双向、实时)
  • 头部压缩。一方面,头信息使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
  • 服务器推进。就是允许服务器未经请求,主动向客户端发送资源。其允许服务器在客户端缓存中填充数据,通过一个叫服务器推送的机制来提前请求。请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源。
  • HTTP2.0中帧具有优先级。客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应。
  • HTTP2.0只适用于HTTPS的场景

HTTP和HTTPS的区别

  • HTTP 是明文传输,HTTPS 通过 SSL/TLS 进行了加密
  • HTTP 的端口号是 80,HTTPS 是 443
  • HTTPS 需要到 CA 申请证书,一般免费证书很少,需要交费
  • HTTPS 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。

HTTP缓存

URI(统一资源标识符)

是什么

统一资源标识符(Uniform Resource Identifier)

一个用于标识某一互联网资源名称的字符串

(定位资源,类似于身份证的作用)

组成

URI包括URL和URN

URL

Uniform Resource Location 统一资源定位符

(用地址定位资源,类似于家庭住址的作用)

URN

Universal Resource Name 统一资源名称

(用名字定位资源,类似于名字的作用)

格式

                       权限                 路径
        ┌───────────────┴───────────────┐┌───┴────┐
  abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
  └┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
  协议        用户信息         主机名    端口                  查询参数          片段

跨域

是什么

一个域下的脚本去请求另一个域下的资源

为什么

因为浏览器的同源策略SOP(Same origin policy),同源是指"协议+域名+端口"三者相同,若三者有其一不同则不可请求到资源。好处:预防某些恶意行为。
注意:域名和其对应的ip也算不同源

怎么做

1. JSONP

原理
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许。基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。带有src属性的img和script和iframe标签,带href属性的link标签

优缺点
优点是简单方便,兼容性好,缺点是只支持get请求

核心思想
网页通过添加一个<script>元素,向服务器请求JSON数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。

实现

原生
<script>
    var script = document.createElement('script');
    script.type = 'text/javascript';
 
    // 传参并指定回调执行函数为onBack
    script.src = 'http://www.domain2.com:8080?callback=onBack';
    document.head.appendChild(script);
 
    // 回调执行函数
    function onBack(res) {
        console.log(res.data);
    }
 </script>
 
jquery
$.ajax({
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "onBack",    // 自定义回调函数名
    data: {}
});

vue
this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'onBack'
}).then((res) => {
    console.log(res); 
})

配合的后端node实现,其他服务器语言也可以
const querystring = require('querystring');
const http = require('http');
const server = http.createServer();
server.on('request', function(req, res) {
    var params = qs.parse(req.url.split('?')[1]);
    var fn = params.callback;
 
    // jsonp返回设置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');
 
    res.end();
});
server.listen('8080');

2. CORS

CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。

1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin

2、带cookie跨域请求:前后端都需要进行设置

【前端设置】根据xhr.withCredentials字段判断是否带有cookie

原生
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容

// 前端设置是否带cookie
xhr.withCredentials = true;

xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
 
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }

jquery
$.ajax({
    ...
   xhrFields: {
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});

vue-resourse
Vue.http.options.credentials = true

axios
axios.defaults.withCredentials = true

3. window.name + iframe

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

a.html:(http://www.domain1.com/a.html)
var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // 加载跨域页面
    iframe.src = url;

    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(iframe);

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};

// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});
proxy.html:(http://www.domain1.com/proxy....)
中间代理页,与a.html同域,内容为空即可。
b.html:(http://www.domain2.com/b.html)
<script>
    window.name = 'This is domain2 data!';
</script>

4. document.domain + iframe

适用范围

主域相同,子域不同的场景

核心思想

两个页面都通过js强制设置document.domain为基础主域,就实现了同域

实现

父窗口:(http://www.domain.com/a.html)
<iframe style="dispay:none" id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
    
    // 操作子域的页面
    var ifr = document.getElementById("iframe");
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    // 通过doc.getElementById()可以操作子域的页面
</script>

子窗口:(http://child.domain.com/b.html)
<script>
    document.domain = 'domain.com';
    // 获取父窗口中变量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

5. postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };

    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>
b.html:(http://www.domain2.com/b.html)
<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

HTTPS

超棒的解释:一个故事讲完HTTPS

HTTPS流程图

浏览器机制

网页生成过程

  1. HTML被HTML解析器解析为DOM树
  2. CSS被CSS解析器解析为CSSOM树
  3. 将HTML树和CSS树整合成渲染树 Render Tree
  4. 生成布局 flow,即将渲染树所有节点进行平面合成
  5. 将布局绘制 paint 在屏幕上

渲染 = 生成布局 flow + 绘制 paint

以上步骤渲染最消耗时间

重排(reflow)

是什么

当文档中的节点位置或者尺寸大小改变时,浏览器需要重新计算该节点的几何属性,将其放在正确的位置上,这个过程叫做重排。

别名:回流

回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流

触发重排

页面首次渲染。

浏览器窗口大小发生改变——resize。

元素尺寸或位置发生改变——边距、填充、边框、宽度和高度。

元素内容变化(文字数量或图片大小,input框输文字)。

元素字体大小变化。

添加或者删除可见的DOM元素。

激活CSS伪类(例如::hover)。

设置style属性

查询某些属性或调用某些方法。

重排影响的范围

  • 全局范围:从根节点html开始对整个渲染树进行重新布局。
  • 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局。如把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。

缺点

需要更新渲染树,性能花销非常大,UI界面展示缓慢 —— 要减少重排次数

性能花销跟渲染树有多少节点需要重新构建有关系 —— 要减少重排范围

重绘(repaint)

是什么

当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

触发重绘

元素的颜色、背景、边框形状、是否可见

重排和重绘的关系

"重绘"不一定会出现"重排","重排"必然会出现"重绘"

浏览器渲染队列机制

是什么

当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。

举例

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';

理论上,进行了4次重排+重绘
实际上,由于浏览器渲染队列机制,只进行1次重排+重绘

强制刷新队列

div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);

4次重排+重绘

强制刷新队列的style样式请求:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
    (元素垂直水平偏移,宽高)
  • scrollTop, scrollLeft, scrollWidth, scrollHeight
    (元素上、左边缘与视图间的距离,整体宽高)
  • clientTop, clientLeft, clientWidth, clientHeight
    (元素可见宽高)
  • getComputedStyle(), 或者 IE的 currentStyle

注意:无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联

因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘

性能优化

减少重排

  • 对样式的修改,尽量通过设置class属性,减少用dom对象.style.样式来修改
  • 减少dom操作,若需要多次访问一个dom,先暂存它的应用(var a = docunemt.xxx)
  • 离线改变dom,因为不可见的元素不会触发重排和重绘
dom.display = 'none'
// 修改dom样式
dom.display = 'block'
  • position属性为absolute或fixed的元素,重排开销比较小,不用考虑它对其他元素的影响
  • 优化动画,通过牺牲平滑来实现加载速度,如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多
  • 分离读写操作
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
  • 利用GPU(图像加速器)
/*
 * 将 2d transform 换成 3d
 * 就可以强制开启 GPU 加速
 * 提高动画性能
 */
div {
  transform: translate3d(10px, 10px, 0);
}
  • 不要使用table布局,因为table中某个元素旦触发了reflow,那么整个table的元素都会触发reflow。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围

同步JS(执行环境/调用栈/作用域链)

JavaScript是一门单线程的语言,意味着在一段时间里只能处理一件事,即一个线程只能处理一条语句。

执行环境

执行环境execution context,别名执行上下文,分为全局执行环境和局部执行环境。每个函数被调用时都会生成自己的执行环境,执行环境定义了变量和函数有权访问的数据,从而决定了它们的行为。

变量对象

每个执行环境都有一个对应的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

活动对象

正在被执行的变量对象是活动对象(位于调用栈栈顶的变量对象是活动对象)

调用栈

JS是一门单线程的语言,所以只有一个调用栈。开始执行代码时,往调用栈压入全局执行环境,遇到函数时,再压入函数执行环境,执行完毕则弹出。(规则:先进后出)

作用域链

串起一系列变量对象的链条。当执行到某一变量或函数时,先在当前的变量对象中查询,找不到就到上一个执行环境的变量对象中查询,再查不到就再上一级,一直到最外层全局执行环境的变量对象。

用以上概念解释闭包

函数在执行完毕后会销毁它的活动对象,但是闭包的存在,会让活动对象保存,仅让执行环境对应的作用域链销毁。

异步JS(消息队列)

阻塞

在同步JS下,像图片处理和网络请求的操作将耗费较多时间,其阻塞了调用栈或者说主线程的执行,在这些操作期间无法执行其他操作。所以引入了异步回调

消息队列

用来存放web API(如setTimeOut)、网络请求、DOM事件(如鼠标/键盘事件)的回调。

事件轮询(event loop)

事件轮询的工作是监听调用栈,并确定调用栈是否为空。

如果调用栈是空的,它将检查消息队列,看看是否有任何挂起的回调等待执行。

当调用堆栈为空,消息队列有回调,就将回调推入调用栈

const bar = () => {
  console.log('bar');
}
const baz = () => {
  console.log('baz');
}
const foo = () => {
  console.log('foo');
  setTimeout(bar, 0);
  baz();
}
foo();

// 输出
foo
baz
bar
// setTimeOut的回调被放进了消息队列,只有调用栈为空才被执行

ES6任务队列

ES6引入了任务队列的概念,任务队列是 JS 中的 promise 所使用的。

消息队列和任务队列的区别在于,任务队列的优先级高于消息队列,这意味着任务队列中的promise 将在消息队列中的回调之前执行。

const bar = () => {
  console.log('bar');
};

const baz = () => {
  console.log('baz');
};

const foo = () => {
  console.log('foo');
  setTimeout(bar, 0);
  new Promise((resolve, reject) => {
    resolve('Promise resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
  baz();
};

foo();

// 输出
foo
baz
Promised resolved
bar

宏任务和微任务的说法

参考资料:事件轮询

从输入URL到页面展示

输入地址

当我们开始在浏览器中输入网址的时候,浏览器其实就已经在智能匹配可能的url了,他会从历史记录,书签等地方,找到已经输入的字符串可能对应的url,然后给出智能提示,让你可以补全url地址。对于 google的chrome的浏览器,他甚至会直接从缓存中把网页展示出来,就是说,你还没有按下enter,页面就出来了。

DNS解析——查找域名的IP地址

请求一旦发起,浏览器首先要做的事情就是解析这个域名。

DNS查找顺序:浏览器缓存→系统缓存→路由器缓存→ISP DNS 缓存→递归搜索。

  1. 浏览器缓存。浏览器会缓存DNS记录一段时间,操作系统没有设定浏览器存储DNS记录的时间长短,不同的浏览器会存储各自的一个固定时间,时长为2~30分钟不等。

  2. 系统缓存。查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址。

  3. 路由器缓存。将查询请求发给路由器,查找其dns缓存。

  4. ISP DNS缓存。ISP 即网络服务提供商,如中国移动,中国电信。操作系统会把这个域名发送给本地区的域名服务器,通常是提供给你接入互联网的应用提供商。这个专门的域名解析服务器性能都会很好,它们一般都会缓存域名解析结果,当然缓存时间是受域名的失效时间控制的,一般缓存空间不是影响域名失效的主要因素。大约80%的域名解析都到这里就已经完成了,所以ISP DNS主要承担了域名的解析工作。

5.1 递归搜索

(1)本地 DNS服务器即将该请求转发到互联网上的根域(即一个完整域名最后面的那个点,通常省略不写)。  

(2)根域将所要查询域名中的顶级域(假设要查询ke.qq.com,该域名的顶级域就是com)的服务器IP地址返回到
本地DNS。

(3) 本地DNS根据返回的IP地址,再向顶级域(就是com域)发送请求。

(4) com域服务器再将域名中的二级域(即ke.qq.com中的qq)的IP地址返回给本地DNS。

(5) 本地DNS再向二级域发送请求进行查询。

(6) 之后不断重复这样的过程,直到本地DNS服务器得到最终的查询结果,并返回到主机。
这时候主机才能通过域名访问该网站。

5.2 迭代搜索

当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析。局部DNS服务器不是自己向其他DNS服务器进行查询,而是把能解析该域名的其他DNS服务器的IP地址返回给客户端DNS程序,客户端DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。

也就是说,迭代解析只是帮你找到相关的服务器而已,而不会帮你去查。比如说:baidu.com的服务器ip地址在192.168.4.5这里,你自己去查吧,本人比较忙,只能帮你到这里了。

补充说明:DNS是什么

DNS(Domain Name System,域名系统),主要用于域名与 IP 地址的相互转换

可以理解为电话本的功能,保存域名于IP地址的对应关系。

补充说明:DNS负载均衡

当一个网站有足够多的用户的时候,假如每次请求的资源都位于同一台机器上面,那么这台机器随时可能会蹦掉。处理办法就是用DNS负载均衡技术,它的原理是在DNS服务器中为同一个主机名配置多个IP地址,在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器,从而达到负载均衡的目的。例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等。

建立TCP连接——三次握手

拿到域名对应的IP地址之后,浏览器会以一个随机端口(1024<端口<65535)向服务器的WEB程序(常用的有httpd,nginx等)80端口发起TCP(Transmission Control Protocol传输控制协议)的连接请求。

http-80端口;https-443端口

第一次握手:客户端A将标志位SYN置为1,随机产生一个值为seq=J(J的取值范围为=1234567)的数据包到服务器,客户端A进入SYN_SENT状态,等待服务端B确认;

第二次握手:服务端B收到数据包后由标志位SYN=1知道客户端A请求建立连接,服务端B将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端A以确认连接请求,服务端B进入SYN_RCVD状态。

第三次握手:客户端A收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务端B,服务端B检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端A和服务端B进入ESTABLISHED状态。

完成三次握手,随后客户端A与服务端B之间可以开始传输数据了。

SYN(synchronous 建立联机);ACK(acknowledgement 确认);PSH(push 传送);

FIN(finish 结束);RST(reset 重置);URG(urgent 紧急);

seq(Sequence number 顺序号码);ack(Acknowledge number确认号码);ACK为标识位

为什么需要三次握手

目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”

书中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。

假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”。主要目的防止server端一直等待,浪费资源。

发送HTTP请求

建立TCP连接后,发起http请求,详细内容见本文HTTP部分。

服务器的永久重定向响应

服务器给浏览器响应一个301永久重定向响应,这样浏览器就会访问“www.google.com/” 而非“google.com/”。

为什么服务器一定要重定向而不是直接发送用户想看的网页内容呢?

其中一个原因跟搜索引擎排名有关。如果一个页面有两个地址,就像http://www.yy.com/和http://yy.com/,搜索引擎会认为它们是两个网站,结果造成每个搜索链接都减少从而降低排名。而搜索引擎知道301永久重定向是什么意思,这样就会把访问带www的和不带www的地址归到同一个网站排名下。还有就是用不同的地址会造成缓存友好性变差,当一个页面有好几个名字时,它可能会在缓存里出现好几次。

301和302的区别和联系

301是永久重定向,302是临时重定向

共同点:

301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)。

不同点:

301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;

302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。 SEO302好于301。

应用场合: 当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转。使用301跳转的大概场景:换域名;在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个;空间服务器不稳定,换空间的时候。

重定向原因

(1)网站调整(如改变网页目录结构);

(2)网页被移到一个新地址;

(3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。 这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。

浏览器跟踪重定向地址

现在浏览器知道了 "www.google.com/"才是要访问的正确地址,所以它会发送另一个http请求。

服务器处理请求

后端从在固定的端口接收到TCP报文开始,它会对TCP连接进行处理,对HTTP协议进行解析,并按照报文格式进一步封装成HTTP Request对象,供上层使用。

一些大一点的网站会将你的请求到反向代理服务器中,因为当网站访问量非常大,网站越来越慢,一台服务器已经不够用了。于是将同一个应用部署在多台服务器上,将大量用户的请求分配给多台机器处理。此时,客户端不是直接通过HTTP协议访问某网站应用服务器,而是先请求到Nginx,Nginx再请求应用服务器,然后将结果返回给客户端,这里Nginx的作用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。

通过Nginx的反向代理,我们到达了web服务器,服务端脚本处理我们的请求,访问我们的数据库,获取需要获取的内容等等。

扩展:反向代理

客户端本来可以直接通过HTTP协议访问某网站应用服务器,网站管理员可以在中间加上一个Nginx,客户端请求Nginx,Nginx请求应用服务器,然后将结果返回给客户端,此时Nginx就是反向代理服务器。

服务器返回一个HTTP响应

详细内容见本文的HTTP部分

浏览器显示HTML

渲染部分见本文的浏览器机制-网页生成过程。

浏览器在解析html文件时,会”自上而下“加载,并在加载过程中进行解析渲染。在解析过程中,如果遇到请求外部资源时,如图片、外链的CSS、iconfont等,请求过程是异步的,并不会影响html文档进行加载。

当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以我明平时的代码中,js是放在html文档末尾的。

JS的解析是由浏览器中的JS解析引擎完成的,比如谷歌的是V8。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。

JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。

浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)

在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来获得这些文件

这些请求都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等...

不像动态页面,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取,或者可以放到CDN中

断开连接——四次挥手

第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。