部分八股文整理--缓存、http等

缓存

什么是缓存

浏览器缓存Brower Caching是浏览器对之前请求过的文件进行缓存,以便再次访问的时候提高页面展示的速度。比如当我们第一次访问CSDN网站的时候,PC会把从网站上的图片和数据下载到电脑上。当再次访问CSDN网站的时候,网站直接从PC加载,这就是缓存。

为什么需要缓存

  • 浏览器缓存是将文件保存在客户端,减少重复请求浪费网络带宽,只产生很小的网络消耗。ps:打开本地资源也会产生网络消耗
  • 缓解服务器的压力,不用重复请求数据
  • 提升前端性能,提高访问速度,提供更好的用户体验

web缓存的类别

主要分为客户端、服务端、数据库三种缓存

数据库缓存

什么是数据库缓存

我们知道常见的数据库,比如oracle、mysql等,数据都是存放在磁盘中。虽然在数据库层也做了对应的缓存,但这种数据库层次的缓存一般针对的是查询内容,而且粒度也太小,一般只有表中数据没有变更的时候,数据库对应的cache才发挥了作用。但这并不能减少业务系统对数据库产生的增、删、查、改的庞大IO压力。所以数据库缓存技术在此诞生,实现热点数据的高速缓存,提高应用的响应速度,极大缓解后端数据库的压力,常用redis作为缓存

解决缓存一致性的问题
方案一

通过 key 的过期时间,mysql 更新时,redis 不更新。 这种方式实现简单,但不一致的时间会很长。如果读请求非常频繁,且过期时间比较长,则会产生很多长期的脏数据。

优点:

  • 开发成本低,易于实现;
  • 管理成本低,出问题的概率会比较小。

不足

  • 完全依赖过期时间,时间太短容易缓存频繁失效,太长容易有长时间更新延迟(不一致)
方案二

在方案一的基础上扩展,通过 key 的过期时间兜底,并且,在更新 mysql 时,同时更新 redis。

优点

  • 相对方案一,更新延迟更小。

不足

  • 如果更新 mysql 成功,更新 redis 却失败,就退化到了方案一;
  • 在高并发场景,业务 server 需要和 mysql,redis 同时进行连接。这样是损耗双倍的连接资源,容易造成连接数过多的问题。
方案三

针对方案二的同步写 redis 进行优化,增加消息队列,将 redis 更新操作交给 kafka,由消息队列保证可靠性,再搭建一个消费服务,来异步更新 redis。

方案四

通过订阅 binlog 来更新 redis,把我们搭建的消费服务,作为 mysql 的一个 slave,订阅 binlog,解析出更新内容,再更新到 redis。

CDN缓存

浏览器的网络请求

  1. 用户在浏览器中输入要访问的网址域名。
  2. 浏览器向本地 DNS 服务器请求对域名的解析。
  3. 如果本地 DNS 服务器有域名的解析结果,那么直接响应用户请求,返回该域名对应的 IP 地址。
  4. 如果本地 DNS 服务器没有域名的解析结果,那么则会递归地向 DNS 系统请求解析,随后将该结果返回给用户。
  5. 浏览器得到域名解析结果后,其实也就是域名对应的 IP 地址。
  6. 随后浏览器向服务器请求内容。
  7. 服务器将用户请求内容返回给浏览器。

通过这么复杂的步骤,用户就可以看到页面内容了。但实际上,在第 6、7 这两步的时候,其中间也经过了非常复杂的过程。为了更清晰地表述,我们可以将这个过程分为 3 个主要节点,如下图所示。

网站服务器通过公网出口,再通过长途骨干网,最后通过用户的宽带广猫到达用户所在的局域网,最终才到达用户电脑的浏览器。其中长途骨干网的传输是最为耗时的,它需要经过网站服务器所在的机房、骨干网、用户所在城域网、用户所在接入网等,其物理传输距离非常遥远。

什么是CDN

CDN即内容分发网络Content Delivery Network,CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。简单地说,CDN 可以提前把数据存在离用户最近的数据节点,从而避免长途跋涉经过长途骨干网,最终达到减少骨干网负担、提高访问速度的目的。

访问流程

  1. 浏览器发起图片 URL 请求,经过本地 DNS 解析,会将域名解析权交给域名 CNAME 指向的 CDN 专用 DNS 服务器。
    • 使用A记录和CNAME进行域名解析的区别
    • A记录就是把一个域名解析到一个IP地址(Address,特制数字IP地址),而CNAME记录就是把域名解析到另外一个域名。其功能是差不多,CNAME将几个主机名指向一个别名,其实跟指向IP地址是一样的,因为这个别名也要做一个A记录的。但是使用CNAME记录可以很方便地变更IP地址。如果一台服务器有100个网站,他们都做了别名,该台服务器变更IP时,只需要变更别名的A记录就可以了。
  1. CDN 的 DNS 服务器将 CDN 的全局负载均衡设备 IP 地址返回给浏览器。
  2. 浏览器向 CDN 全局负载均衡设备发起 URL 请求。
  3. CDN 全局负载均衡设备根据用户 IP 地址,以及用户请求的 URL,选择一台用户所属区域的区域负载均衡设备,向其发起请求。
  4. 区域负载均衡设备会为用户选择最合适的 CDN 缓存服务器(考虑的依据包括:服务器负载情况,距离用户的距离等),并返回给全局负载均衡设备。
  5. 全局负载均衡设备将选中的 CDN 缓存服务器 IP 地址返回给用户。
  6. 用户向 CDN 缓存服务器发起请求,缓存服务器响应用户请求,最终将用户所需要的内容返回给浏览器。
  7. 如果该CDN缓存服务器里没有用户想要的内容,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器获取资源。
  8. 成功获取资源后逐层返回并将资源缓存。

代理服务器缓存

如果没有代理缓存,代理服务器仅仅只是一个中转作用,转发客户端和服务器的报文,中间不会存储任何数据。但是一旦给代理加上缓存后,事情就有些变化了。它在转发报文的同时,还要把报文存入自己的Cache里。下一次再有同样的请求,代理服务器就可以自己决断,从自己的缓存中取出数据返回给客户端,就无需再去源服务器获取。这样就降低了用户的等待时间,同时也节约了源服务器的网络带宽。

浏览器缓存

http缓存
缓存位置
Server Worker

Server Worker 是运行在浏览器的独立线程,一般用来缓存。Server Worker 涉及请求拦截,so传输协议是HTTPS来保障安全。

Server Worker 是“外建”的缓存机制,可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的,这是与其他内建缓存机制的区别。

Memory Cache(内存缓存)

内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等,占据该进程一定的内存资源,但是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

内存缓存特点:①读取速度快 ②时效性:页面关闭进程的内存清空释放

Disk Cache(硬盘缓存)

硬盘中的缓存。在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的,绝大部分的缓存都来自 Disk Cache。为什么呢?cuz根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。

硬盘缓存比内存缓存读取速度慢,读取需要对硬盘进行I/O操作,会导致重新解析缓存内容,造成读取路的复杂

Memory Cache 和 Disk Cache的相同和不同比较

CSS文件加载一次就可以渲染,不会频繁的读取,存储在Disk Cache;js脚本可能会随时会被执行,存储在Memory Cache,若存储在硬盘中,会因为I/O开销大导致浏览器失去响应。

三级缓存原理
  • 内存缓存,优先加载,速度最快
  • 本地缓存,次优先加载,速度快
  • 网络缓存,最后加载,速度慢,浪费流量
强缓存

浏览器并不会向服务器发送任何请求,直接从本地缓存中读取文件,返回200

  • 200 form memory cache : 不访问服务器,一般已经加载过该资源且缓存在了内存当中,直接从内存中读取缓存。浏览器关闭后,数据将不存在(资源被释放掉了),再次打开相同的页面时,不会出现from memory cache。
  • 200 from disk cache: 不访问服务器,已经在之前的某个时间加载过该资源,直接从硬盘中读取缓存,关闭浏览器后,数据依然存在,此资源不会随着该页面的关闭而释放掉下次打开仍然会是from disk cache。

控制强制缓存的字段分别是Expires和Cache-Control,Cache-Control优先级比Expires高

Expries

http1.0规范,它设置一个值绝对时间的GMT格式的时间字符串,这个是资源失效时间(客户端的时间小于Expires的值),在这个时间之前都是命中缓存。

缺陷:Expires控制缓存原理是客户端的时间和服务端返回的时间作对比,若两个时间偏差大的话,会造成强制缓存直接失效

Cache-Control

http1.1规范,替代Expires,它利用的是相对时间,利用header信息字段的max-age值判断

当值设为max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。

(1) max-age:用来设置资源(representations)可以被缓存多长时间,单位为秒;

(2) s-maxage:和max-age是一样的,不过它只针对代理服务器缓存而言;

(3)public:指示响应可被任何缓存区缓存;

(4)private:只能针对个人用户,而不能被代理服务器缓存;

(5)no-cache:强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回304,未变更。这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。

(6)no-store:禁止一切缓存(这个才是响应不被缓存的意思)。

协商缓存
  1. 浏览器向服务器发起请求,命中缓存返回304,失效返回200和请求结果
  • 向服务器发起请求,服务器根据这个请求的Request header的一些参数来判断是否命中协商缓存;命中返回304状态码并且会带上新的response header告诉浏览器可以从缓存中读取资源。没有命中返回200状态码和请求的结果。
  1. 协商缓存成功,返回304流程图

  1. 协商缓存失效,返回200流程图

  1. Last-Modified/If-Modified-Since、ETag/If-None-Match
  • 协商换头字段Last-Modified/If-Modified-Since 和ETag/If-None-Match是成对出现的,呈现一一对应的关系
  • Last-Modified/If-Modified-Since 是http1.0的头字段,ETag/If-None-Match是http1.1的头字段
  • Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效
一些概念

①Last-Modified

浏览器向服务器发送资源最后的修改时间

②If-Modified-Since

当资源过期时,发现响应头具有Last-Modified声明,则再次向服务器请求时带上头if-modified-since,表示请求时间。服务器收到请求后,发现有if-modified-since则与被请求资源的最后修改时间进行对比(Last-Modified),若最后修改时间较新,说明资源又被改过,则返回最新资源,返回200;若最后修改时间较小,说明资源无新修改,返回304 ,使用缓存文件。

③ETag

http1.1属性,Etag是 Entity tag的缩写,可以理解为“被请求变量的实体值”,Etag是服务端的一个资源的标识,在 HTTP 响应头中将其传送到客户端。

④If-None-Match

当资源过期时,发现响应头具有Etag声明,则再次向服务器请求时带上头if-none-match(唯一标识Etag值)。服务器收到该请求后,发现有If-None-Match则根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。

Cookie
特点
  • 服务器通过设置Set-Cookie 响应头来设置 cookie
  • 浏览器得到 cookie 后,每次同源的请求的请求头都会带上 cookie
  • 服务器读取 cookie 就知道了登录用户的信息(如账户名等)
  • cookie 实际上存储在本地计算机的硬盘里
  • cookie 的最大储存量一般只有4K
缺点
  • Cookie很容易被用户篡改( Session 可以解决这个问题,防止用户篡改)
  • Cookie 的默认有效期理论上在用户关闭页面后就失效,实际上在在20分钟左右,不同浏览器策略不同。但是后端可以强制设置有效期(如何设置见下文)。
  • Cookie 也有一定的同源策略,不过跟 AJAX 的同源策略稍微有些不同。如:
  • 当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上 baidu.com 对应的 Cookie
  • 当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的Cookie,还会带上 qq.com 的 Cookie
  • 另外 Cookie 还可以根据路径做限制,请自行了解,这个功能用得比较少。
有了Cookie,我们就可以实现这两件事
  • 第一个作用是识别用户身份。如: 比如用户 A 用浏览器访问了 demo.com ,那么 demo.com 的服务器就会立刻给 A 返回一段数据「uid=1」(这就是 Cookie)。当 A 再次访问 demo.com 的其他页面时,就会附带上「uid=1」这段数据。这样服务端就知道 A 是谁了。
  • 第二个作用是记录历史。如: 假设 demo.com 是一个购物网站,当 A 在上面将商品 B1 、B2 加入购物车时,JS 可以改写 Cookie,改为「uid=1; cart=B1,B2」,表示购物车里有 B1 和 B2 两样商品了。这样一来,当用户关闭网页,过三天再打开网页的时候,依然可以看到 B1 、B2躺在购物车里,因为浏览器并不会无缘无故地删除这个 Cookie。
Session

在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。

为什么要使用Session

因为很多第三方可以获取到这个Cookie,服务器无法判断Cookie是不是真实用户发送的,所以Cookie可以伪造,伪造Cookie实现登录进行一些HTTP请求。如果从安全性上来讲,Session比Cookie安全性稍微高一些,我们先要知道一个概念–SessionID。SessionID是什么?客户端第一次请求服务器的时候,服务器会为客户端创建一个Session,并将通过特殊算法算出一个session的ID,下次请求资源时(Session未过期),浏览器会将sessionID(实质是Cookie)放置到请求头中,服务器接收到请求后就得到该请求的SessionID,服务器找到该id的session返还给请求者使用。

缺点

因为Session是存储在服务器当中的,所以Session过多,会对服务器产生压力。在我看来,Session的生命周期算是减少服务器压力的一种方式。

Token

token的意思是“令牌”,是用户身份的验证方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器) 。还可以把不变的参数也放进token,避免多次查。

Cookie和Session的区别

1、Cookie可以存储在浏览器或者本地,session只能存在服务器

2、Session比Cookie更具有安全性

3、Session占用服务器性能,Session过多,增加服务器压力

4、单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie

5、所以个人建议:

  • 将登陆信息等重要信息存放为SESSION
  • 其他信息如果需要保留,可以放在COOKIE中
Token和Session的区别
  • token就是令牌,比如你授权(登录)一个程序时,他就是个依据,判断你是否已经授权该软件;
  • cookie就是写在客户端的一个txt文件,里面包括你登录信息之类的,这样你下次在登录某个网站,就会自动调用cookie自动登录用户名;
  • session和cookie差不多,只是session是写在服务器端的文件,也需要在客户端写入cookie文件,但是文件里是你的浏览器编号.Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
Web Storage

在介绍Web Storage之前,有必要先讲一下cookie。在没有Web Storage之前,客户端浏览器存储数据都是通过cookie来实现的。cookie因其自身的特性,在一些方面有其独有的优势,比如可配置过期时间、可跨域共享(具有相同祖先域名时)、与服务器数据交互等,但在做数据存储方面,其缺点显而易见:

  • 客户端发送请求时,cookie会作为头部将无用数据一起发送给服务器
  • 请求被拦截后,cookie数据有泄漏和被篡改的安全风险
  • cookie存储数据的大小被限制在4K。IE8、Firefox、opera每个域对cookie的数量也有限制,上限是50,Safari/WebKit没有限制。

因此cookie不适合做大数据量的存储,相比之下,Web Storage更适合存储大量数据:

  • 每个域名下可提供5M的存储容量(不同浏览器可能有差异,比如IE是10M)
  • key/value键值对的方式存储字符串,方便数据存取操作
  • 存储在客户端本地,不会随请求发送给服务端

Web Storage分为两种,即 sessionStorage localStorage

以下是sessionStorage和localStorage之间的区别:

  • sessionStorage 为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。
  • localStorage 同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。
IndexedDB

为了实现客户端缓存,虽然已经有很多技术被不断推出和应用,但是随着浏览器功能的不断增强和完善,现有的缓存技术,其局限性也变得越来越明显:

  • 容量太小: cookie和Web Storage最多也就支持5M,已经不能满足需求了
  • 检索不方便: cookie和Web Storage都是以字符串形式存储数据,复杂对象数据存取前需要做处理,比较麻烦
  • 不能提供搜索功能,不能建立自定义的索引

IndexedDB 具有以下特点:

  • 键值对储存: IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
  • 异步: IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  • 支持事务: IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  • 同源限制: IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  • 储存空间大: IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
  • 支持二进制储存: IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

防抖节流

防抖

为什么要防抖

有的操作是高频触发的,但是其实触发一次就好了,比如我们短时间内多次缩放页面,那么我们不应该每次缩放都去执行操作,应该只做一次就好。再比如说监听输入框的输入,不应该每次都去触发监听,应该是用户完成一段输入后在进行触发。

防抖就是防止抖动,避免事件的重复触发

总结:等用户高频事件完了,再进行事件操作

防抖怎么做

设计思路

事件触发后开启一个定时器,如果事件在这个定时器限定的时间内再次触发,则清除定时器,在写一个定时器,定时时间到则触发。

function debounce(fn, delay){
	let timer = null
	return function(arguments){	//这个参数是给fn传参的
		clearTimeout(timer)
		timer = setTimeout(()=>{
			fn.call(this, arguments)
		},delay)
	}
}
let a = debounce(ajax,500)
a('post')

节流

防抖存在一个问题,事件会一直等到用户完成操作后一段时间在操作,如果一直操作,会一直不触发。比如说是一个按钮,点击就发送请求,如果一直点,那么请求就会一直发布出去。这里正确的思路应该是第一次点击就发送,然后上一个请求回来后,才能再发。

节流就是减少流量,将频繁触发的事件减少,并每隔一段时间执行。即,控制事件触发的频率

总结:某个操作希望上一次的完成后再进行下一次,或者希望隔一段时间触发一次。

节流怎么做

设计思路

我们可以设计一种类似控制阀门一样定期开放的函数,事件触发时让函数执行一次,然后关闭这个阀门,过了一段时间后再将这个阀门打开,再次触发事件。

function throttle(fn, delay){
	let valid = true
	return function(arguments){
		if(valid){
			setTimeout(()=>{
				fn.call(this, arguments)
				valid = true
			}, delay)
			valid = false
		}
	}
}

应用场景

防抖:

  • search搜索联想,用户在不断输入值时,用防抖来节约请求资源
  • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

节流:

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多

总结

防抖和节流相同点:

防抖和节流都是为了阻止操作高频触发,从而浪费性能。

防抖和节流区别:

  • 防抖是触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。适用于可以多次触发但触发只生效最后一次的场景。
  • 节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率。

Vue

生命周期

  • vue2生命周期:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestory、destoryed
  • vue3生命周期:setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted

v-if和v-show

v-show和v-if都是用来显示隐藏元素,v-if还有一个v-else配合使用,两者达到的效果都一样,性能方面去有很大的区别。

v-show

v-show不管条件是真还是假,第一次渲染的时候都会编译出来,也就是标签都会添加到DOM中。之后切换的时候,通过display: none;样式来显示隐藏元素。可以说只是改变css的样式,几乎不会影响什么性能。

v-if

在首次渲染的时候,如果条件为假,什么也不操作,页面当作没有这些元素。当条件为真的时候,开始局部编译,动态的向DOM元素里面添加元素。当条件从真变为假的时候,开始局部编译,卸载这些元素,也就是删除。

性能方面

v-if绝对是更消耗性能的,因为v-if在显示隐藏过程中有DOM的添加和删除,v-show就简单多了,只是操作css。

使用场景

因为v-show无论如何都会渲染,如果在一些场景下很难出现,那么使用v-if。如果是一些固定的,条件内容都不怎么会改变的,频繁切换的,使用v-show会比较省性能。如果是子组件,每次切换子组件不执行生命周期,使用v-show,如果子组件需要重新执行生命周期,那么使用v-if才能触发。

Diff算法

https://juejin.cn/post/6994959998283907102

Diff同层对比

新旧虚拟DOM对比的时候,Diff算法比较只会在同层级进行, 不会跨层级比较。 所以Diff算法是:深度优先算法。 时间复杂度:O(n)

Diff对比流程

当数据改变时,会触发setter,并且通过Dep.notify去通知所有订阅者Watcher,订阅者们就会调用patch方法,给真实DOM打补丁,更新相应的视图。

newVnode和oldVnode:同层的新旧虚拟节点

patch方法

这个方法作用就是,对比当前同层的虚拟节点是否为同一种类型的标签(同一类型的标准,下面会讲):

  • 是:继续执行patchVnode方法进行深层比对
  • 否:没必要比对了,直接整个节点替换成新虚拟节点
function patch(oldVnode, newVnode) {
	// 比较是否为一个类型的节点
	if (sameVnode(oldVnode, newVnode)) {
		// 是:继续进行深层比较
		patchVnode(oldVnode, newVnode)
	} else {
		// 否
		const oldEl = oldVnode.el // 旧虚拟节点的真实DOM节点
		const parentEle = api.parentNode(oldEl) // 获取父节点
		createEle(newVnode) // 创建新虚拟节点对应的真实DOM节点
		if (parentEle !== null) {
			api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
			api.removeChild(parentEle, oldVnode.el)  // 移除以前的旧元素节点
			// 设置null,释放内存
			oldVnode = null
		}
	}

	return newVnode
}
sameVnode方法

判断是否是同一类型节点

function sameVnode(oldVnode, newVnode) {
  return (
    oldVnode.key === newVnode.key && // key值是否一样
    oldVnode.tagName === newVnode.tagName && // 标签名是否一样
    oldVnode.isComment === newVnode.isComment && // 是否都为注释节点
    isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data
    sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同
  )
}
patchVnode方法
  • 找到对应的真实DOM,称为el
  • 判断newVnode和oldVnode是否指向同一个对象,如果是,那么直接return
  • 如果他们都有文本节点并且不相等,那么将el的文本节点设置为newVnode的文本节点。
  • 如果oldVnode有子节点而newVnode没有,则删除el的子节点
  • 如果oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化之后添加到el
  • 如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要
function patchVnode(oldVnode, newVnode) {
  const el = newVnode.el = oldVnode.el // 获取真实DOM对象
  // 获取新旧虚拟节点的子节点数组
  const oldCh = oldVnode.children, newCh = newVnode.children
  // 如果新旧虚拟节点是同一个对象,则终止
  if (oldVnode === newVnode) return
  // 如果新旧虚拟节点是文本节点,且文本不一样
  if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) {
    // 则直接将真实DOM中文本更新为新虚拟节点的文本
    api.setTextContent(el, newVnode.text)
  } else {
    // 否则

    if (oldCh && newCh && oldCh !== newCh) {
      // 新旧虚拟节点都有子节点,且子节点不一样

      // 对比子节点,并更新
      updateChildren(el, oldCh, newCh)
    } else if (newCh) {
      // 新虚拟节点有子节点,旧虚拟节点没有

      // 创建新虚拟节点的子节点,并更新到真实DOM上去
      createEle(newVnode)
    } else if (oldCh) {
      // 旧虚拟节点有子节点,新虚拟节点没有

      //直接删除真实DOM里对应的子节点
      api.removeChild(el)
    }
  }
}
updateChildren方法

互相进行比较,总共有五种比较情况:

  • 1、oldS 和 newS 使用sameVnode方法进行比较,sameVnode(oldS, newS)
  • 2、oldS 和 newE 使用sameVnode方法进行比较,sameVnode(oldS, newE)
  • 3、oldE 和 newS 使用sameVnode方法进行比较,sameVnode(oldE, newS)
  • 4、oldE 和 newE 使用sameVnode方法进行比较,sameVnode(oldE, newE)
  • 5、如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射到旧节点下标的 key -> index 表,然后用新 vnode 的 key 去找出在旧节点中可以复用的位置

HTTP

HTTP和HTTPS

HTTP特点

  1. http协议支持客户端/服务端模式,也是一种请求/响应模式的协议。
  2. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。
  3. 灵活:HTTP允许传输任意类型的数据对象。传输的类型由Content-Type加以标记。
  4. 无连接:限制每次连接只处理一个请求。服务器处理完请求,并收到客户的应答后,即断开连接,但是却不利于客户端与服务器保持会话连接,为了弥补这种不足,产生了两项记录http状态的技术,一个叫做Cookie,一个叫做Session。
  5. 无状态:无状态是指协议对于事务处理没有记忆,后续处理需要前面的信息,则必须重传。
URI和URL的区别

HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

  • URI:Uniform Resource Identifier 统一资源标识
  • URL:Uniform Resource Location 统一资源定位

URI 是用来标示 一个具体的资源的,我们可以通过 URI 知道一个资源是什么。

URL 则是用来定位具体的资源的,标示了一个具体的资源位置。互联网上的每个文件都有一个唯一的URL。

HTTP报文组成
请求报文构成
  1. 请求行:包括请求方法、URL、协议/版本
  2. 请求头(Request Header)
  3. 请求正文
响应报文构成
  1. 状态行
  2. 响应头
  3. 响应正文
常见的请求方法
  • GET:请求指定的页面信息,并返回实体主体。
  • POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
  • HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
  • PUT:从客户端向服务器传送的数据取代指定的文档的内容。
  • DELETE:请求服务器删除指定的页面。
响应状态码

访问一个网页时,浏览器会向web服务器发出请求。此网页所在的服务器会返回一个包含HTTP状态码的信息头用以响应浏览器的请求。

状态码分类:
  • 1XX- 信息型,服务器收到请求,需要请求者继续操作。
  • 2XX- 成功型,请求成功收到,理解并处理。
  • 3XX - 重定向,需要进一步的操作以完成请求。
  • 4XX - 客户端错误,请求包含语法错误或无法完成请求。
  • 5XX - 服务器错误,服务器在处理请求的过程中发生了错误。

常见状态码

100Continue继续。客户端应继续其请求
101Switching Protocols切换协议。
200OK请求成功。一般用于GET与POST请求
201Created已创建。成功请求并创建了新的资源
202Accepted已接受。已经接受请求,但未处理完成
203Non-Authoritative Information非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204No Content无内容。服务器成功处理,但未返回内容。
205Reset Content重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。
206Partial Content部分内容。服务器成功处理了部分GET请求
300Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与301类似。使用GET和POST请求查看
304Not Modified未修改。客户端通过缓存访问资源
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized请求要求用户的身份认证
402Payment Required保留,将来使用
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。
405Method Not Allowed客户端请求中的方法被禁止
500Internal Server Error服务器内部错误,无法完成请求
501Not Implemented服务器不支持请求的功能,无法完成请求
502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。
504Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求
505HTTP Version not supported服务器不支持请求的HTTP协议的版本,无法完成处理
一般http中存在如下问题:
  • 请求信息明文传输,容易被窃听截取。
  • 数据的完整性未校验,容易被篡改
  • 没有验证对方身份,存在冒充危险

HTTPS

为了解决上述HTTP存在的问题,就用到了HTTPS。

HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

SSL(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

浏览器在使用HTTPS传输数据的流程

  1. 首先客户端通过URL访问服务器建立SSL连接。
  2. 服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端。
  3. 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
  5. 服务器利用自己的私钥解密出会话密钥。
  6. 服务器利用会话密钥加密与客户端之间的通信。
HTTPS的缺点
  • HTTPS协议多次握手,导致页面的加载时间延长近50%;
  • HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;
  • 申请SSL证书需要钱,功能越强大的证书费用越高。
  • SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。

总结HTTPS和HTTP的区别

  • HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理。
  • http和https使用连接方式不同,默认端口也不一样,http是80,https是443。

HTTP协议是无状态的和Connection: keep-alive的区别

  • 无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系
  • HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)
  • 从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接
  • Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间

HTTP1.0、HTTP1.1、HTTP2.0和WebSocket

HTTP1.0和HTTP1.1的区别

1. 缓存处理
  • 在HTTP1.0中主要使用header里的If-Modified-Since(协商缓存),Expires(强缓存)来做为缓存判断的标准
  • HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
2. 带宽优化及网络连接的使用
  • HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能
  • HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
3. 错误通知的管理
  • 在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
4. Host头处理

在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。

5. 长连接

HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。

HTTP1.1和HTTP2.0的区别

SPDY

2012年google如一声惊雷提出了SPDY的方案,优化了HTTP1.X的请求延迟,解决了HTTP1.X的安全性,具体如下:

  • 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing) 。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
  • 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
  • header压缩。 前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
  • 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
  • 服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图


SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。

HTTP2.0

http2 刚开始为SPDY协议,起初Google为了减少加载延迟而开发的,通过使用例如压缩,多路复用,优化等。 目前该协议已经被很多浏览器所支持。

从技术角度而言,http1.1和2.0 最大的区别是二进制框架层。与 http1.1把所有请求和响应作为纯文本不同,http2 使用二进制框架层把所有消息封装成二进制,且仍然保持http语法,消息的转换让http2能够尝试http1.1所不能的传输方式。

HTTP2.0和SPDY的区别
  1. HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
  2. HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE
HTTP1.1流水线和队头阻塞

客户端首次接受的响应常常不能完全渲染。相反,它包含一些其他需要的资源。 因此,客户端必须发出一些其他请求。 Http1.0客户端的每一个请求必须重新连接,这是非常耗时和资源的。

Http1.1 通过引入长连接和流水线技术处理了这个问题。 通过长连接,http1.1假定这个tcp连接应该一直打开直到被通知关闭。 这就允许客户端通过同一连接发送多个请求。不巧的是,这优化策略有个瓶颈。当一个队头的请求不能收到响应的资源,它将会阻塞后面的请求。这就是知名的队头阻塞问题。虽然添加并行的tcp连接能够减轻这个问题,但是tcp连接的数量是有限的,且每个新的连接需要额外的资源。

HTTP2 二进制框架层的优势

在http2 , 二进制框架层编码 请求和响应 并把它们分成更小的包,能显著的提高传输的灵活性。

http1.1利用多个tcp连接来减少队头阻塞的影响相反,http2在两端建立一个单独的连接。该连接包含多个数据流。 每个流包含多个请求/响应格式的消息 。 最终,每个消息被划分为更小的帧单元。

HTTP2.0和HTTP1.X相比的新特性
  • 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
  • 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
  • header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
  • 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。
HTTP2.0的多路复用和HTTP1.X中的长连接复用有什么区别?
  • HTTP/1.* 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
  • HTTP/1.1 Pipeling解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
  • HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;
为什么需要头部压缩?

假定一个页面有100个资源需要加载(这个数量对于今天的Web而言还是挺保守的), 而每一次请求都有1kb的消息头(这同样也并不少见,因为Cookie和引用等东西的存在), 则至少需要多消耗100kb来获取这些消息头。HTTP2.0可以维护一个字典,差量更新HTTP头部,大大降低因头部传输产生的流量。具体参考:HTTP/2 头部压缩技术介绍

HTTP2.0多路复用有多好?

HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐则被称为 TCP 慢启动。由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。HTTP/2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升。

WebSocket

https://www.cnblogs.com/chyingp/p/websocket-deep-in.html

什么是WebSocket

HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。

和HTTP对比的优点

概括地说就是:支持双向通信,更灵活,更高效,可扩展性更好。

  1. 支持双向通信,实时性更强。
  2. 更好的二进制支持。
  3. 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部。
  4. 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)

浏览器相关

页面加载过程

输入URL到页面呈现发生了什么?

  1. 浏览器接受URL开启网络请求线程(涉及到:浏览器机制,线程与进程等)
  2. 开启网络线程到发出一个完整的http请求(涉及到:DNS解析,TCP/IP请求,5层网络协议等)
  3. 从服务器接收到请求到对应后台接受到请求(涉及到:负载均衡,安全拦截,后台内部处理等)
  4. 后台与前台的http交互(涉及到:http头,响应码,报文结构,cookie等)
  5. 缓存问题(涉及到:http强缓存与协商缓存等)
  6. 浏览器接受到http数据包后的解析流程(涉及到html词法分析,解析成DOM树,解析CSS生成CSSOM树,合并生成render渲染树。然后layout布局,painting渲染,复合图层合成,GPU绘制,等)

Chrome浏览器有哪些进程呢

  1. 浏览器进程(Browser Process),这个是浏览器的主进程,主要负责包括地址栏、前进后退按钮、处理网络访问、文件访问等。
  2. 渲染进程(Renderer Process),控制显示网站的选项卡内的所有内容。
  3. 插件进程(Plugin Process),控制网站使用的所有插件。
  4. GPU(GPU Process),与其他进程隔离处理GPU任务,由于GPU处理来自多个应用程序的请求并将它们绘制在同一表面上,因此将其分为不同的过程。
  5. 网络进程(NetWork Process),负责页面的网络资源加载,之前是放在浏览器进程中的一个线程运行,现在独立出来。

DNS域名解析

为什么需要DNS域名解析?

因为我们在浏览器中输入的URL通常是一个域名,并不会直接去输入IP地址(纯粹因为域名比IP好记),但我们的计算机并不认识域名,它只知道IP,所以就需要这一步操作将域名解析成IP。

URL组成部分
  • protocol:协议头,比如http,https,ftp等;
  • host:主机域名或者IP地址;
  • port:端口号;
  • path:目录路径;
  • query:查询的参数;
  • hash:#后边的hash值,用来定位某一个位置。
解析过程
  • 首先会查看浏览器DNS缓存,有的话直接使用浏览器缓存
  • 没有的话就查询计算机本地DNS缓存(localhost)
  • 还没有就询问递归式DNS服务器(就是网络提供商,一般这个服务器都会有自己的缓存)
  • 如果依然没有缓存,那就需要通过 根域名服务器 和TLD域名服务器 再到对应的 权威DNS服务器 找记录,并缓存到 递归式服务器,然后 递归服务器 再将记录返回给本地

发送HTTP请求

拿到了IP地址后,就可以发起HTTP请求了。HTTP请求的本质就是TCP/IP的请求构建。建立连接时需要**「3次握手」进行验证,断开链接也同样需要「4次挥手」**进行验证,保证传输的可靠性。

3次握手
  • 第一次握手:客户端发送位码为 SYN = 1(SYN 标志位置位),随机产生初始序列号 Seq = J 的数据包到服务器。服务器由 SYN = 1(置位)知道,客户端要求建立联机。
  • 第二次握手:服务器收到请求后要确认联机信息,向客户端发送确认号Ack = (客户端的Seq +1,J+1),SYN = 1,ACK = 1(SYN,ACK 标志位置位),随机产生的序列号 Seq = K 的数据包。
  • 第三次握手:客户端收到后检查 Ack 是否正确,即第一次发送的 Seq +1(J+1),以及位码ACK是否为1。若正确,客户端会再发送 Ack = (服务器端的Seq+1,K+1),ACK = 1,以及序号Seq为服务器确认号J 的确认包。服务器收到后确认之前发送的 Seq(K+1) 值与 ACK= 1 (ACK置位)则连接建立成功。

4次挥手
  • 客户端发送一个FIN Seq = M(FIN置位,序号为M)包,用来关闭客户端到服务器端的数据传送。
  • 服务器端收到这个FIN,它发回一个ACK,确认序号Ack 为收到的序号M+1。
  • 服务器端关闭与客户端的连接,发送一个FIN Seq = N 给客户端。
  • 客户端发回ACK 报文确认,确认序号Ack 为收到的序号N+1。

五层网络协议

1、应用层(DNS,HTTP):DNS解析成IP并发送http请求;

2、传输层(TCP,UDP):建立TCP连接(3次握手);

3、网络层(IP,ARP):IP寻址;

4、数据链路层(PPP):封装成帧;

5、物理层(利用物理介质传输比特流):物理传输(通过双绞线,电磁波等各种介质)。

「OSI七层框架:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层」

服务器接收请求做出响应

HTTP 请求到达服务器,服务器进行对应的处理。 最后要把数据传给浏览器,也就是返回网络响应。

跟请求部分类似,网络响应具有三个部分:响应行、响应头和响应体。

响应完成之后怎么办?TCP 连接就断开了吗?

不一定。这时候要判断Connection字段, 如果请求头或响应头中包含Connection: Keep-Alive, 表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接。否则断开TCP连接, 请求-响应流程结束。

浏览器解析渲染页面

解析HTML构建DOM Tree

浏览器在拿到服务器返回的网页之后,首先会根据顶部定义的DTD类型进行对应的解析,解析过程将被交给内部的GUI渲染线程来处理。

HTML解释器的工作就是将网络或者本地磁盘获取的HTML网页或资源从字节流解释成DOM树🌲结构

通过上图可以清楚的了解这一过程:首先是字节流,经过解码之后是字符流,然后通过词法分析器会被解释成词语(Tokens),之后经过语法分析器构建成节点,最后这些节点被组建成一颗 DOM 树。

对于线程化的解释器,字符流后的整个解释、布局和渲染过程基本会交给一个单独的渲染线程来管理(不是绝对的)。由于 DOM 树只能在渲染线程上创建和访问,所以构建 DOM 树的过程只能在渲染线程中进行。但是,从字符串到词语这个阶段可以交给单独的线程来做,Chrome 浏览器使用的就是这个思想。在解释成词语之后,Webkit 会分批次将结果词语传递回渲染线程。

这个过程中,如果遇到的节点是 JS 代码,就会调用 JS引擎 对 JS代码进行解释执行,此时由于 JS引擎 和 GUI渲染线程 的互斥,GUI渲染线程 就会被挂起,渲染过程停止,如果 JS 代码的运行中对DOM树进行了修改,那么DOM的构建需要从新开始

如果节点需要依赖其他资源,图片/CSS等等,就会调用网络模块的资源加载器来加载它们,它们是异步的,不会阻塞当前DOM树的构建

如果遇到的是 JS 资源URL(没有标记异步),则需要停止当前DOM的构建,直到 JS 的资源加载并被 JS引擎 执行后才继续构建DOM

为什么不建议频繁操作DOM?

我们都知道操作DOM其实是非常耗性能的,所以我们不仅要避免去操作DOM,还要减少访问DOM的次数。

因为在浏览器中,DOM和JS的实现,并不是在同一个引擎中完成的。DOM是属于渲染引擎中的东⻄,⽽JS⼜是JS引擎中的东⻄。当我们通过JS操作DOM的时候,就涉及到了两个线程之间的通信,那么势必会带来⼀些性能上的损耗。操作DOM次数⼀多,也就等同于⼀直在进⾏线程之间的通信,并且操作DOM可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

解析CSS构建CSSOM Tree

CSS解释器会将CSS文件解释成内部表示结构,生成CSS规则树,这个过程也是和DOM解析类似的,CSS 字节转换成字符,接着词法解析与法解析,最后构成 CSS对象模型(CSSOM) 的树结构

构建渲染树(Render Tree)

等DOM Tree与CSSOM Tree都构建完毕后,接着将它们合并成渲染树(Render Tree),渲染树 只包含渲染网页所需的节点,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上。

渲染(布局,绘制,合成)
  • 计算CSS样式 ;
  • 构建渲染树 ;
  • 布局,主要定位坐标和大小,是否换行,各种position overflow z-index属性 ;
  • 绘制,将图像绘制出来。
回流和重绘

(1)Reflow:即回流。一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树。

(2)Repaint:即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了

回流的成本开销要高于重绘,而且一个节点的回流往往回导致子节点以及同级节点的回流, 所以优化方案中一般都包括,尽量避免回流。

「回流一定导致重绘,但重绘不一定会导致回流」

会导致回流的操作:
  • 页面首次渲染(无法避免且开销最大的一次)
  • 浏览器窗口大小发生改变(resize事件)
  • 元素尺寸或位置发生改变(边距、宽高、边框等)
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化(font-size)
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法

「合成(composite)」

最后一步合成( composite ),这一步骤浏览器会将各层信息发送给GPU,GPU将各层合成,显示在屏幕上

普通图层和复合图层

可以简单的这样理解,浏览器渲染的图层一般包含两大类:普通图层以及复合图层

首先,普通文档流内可以理解为一个复合图层(这里称为默认复合层,里面不管添加多少元素,其实都是在同一个复合图层中)

其次,absolute布局(fixed也一样),虽然可以脱离普通文档流,但它仍然属于默认复合层。

然后,可以通过硬件加速的方式,声明一个新的复合图层,它会单独分配资源 (当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)

可以简单理解下:「GPU中,各个复合图层是单独绘制的,所以互不影响」,这也是为什么某些场景硬件加速效果一级棒

可以Chrome源码调试 -> More Tools -> Rendering -> Layer borders中看到,黄色的就是复合图层信息

同源策略和跨域

源的定义

(Origin)是指由 URL 中协议、主机名(域名 domain)以及 端口共同组成的部分。

URL结果原因
http://store.company.com/dir2/other.html同源只有路径不同
http://store.company.com/dir/inner/another.html同源只有路径不同
https://store.company.com/secure.html不源协议不同, 端口不同
http://store.company.com:81/dir/etc.html不同源端口不同
http://news.company.com/dir/other.html不同源主机不同

同源策略的限制

  • Cookie 、LocalStorage 和 IndexDB无法读取。
  • 无法获取或操作另一个资源的DOM。
  • AJAX请求不能发送。

请求跨域的解决方案

请求跨域是通过 http 与 服务端 进行通讯,常用方案如下:

  • CORS
  • JSONP
  • Websocket
  • 请求代理
CORS

CORS的全称是 Cross-Origin Resource Sharing 跨域资源共享。

是浏览器为 AJAX 请求设置的一种跨域机制,让其可以在服务端允许的情况下进行跨域访问。主要通过 HTTP 响应头来告诉浏览器服务端是否允许当前域的脚本进行跨域访问。

跨域资源共享将 AJAX 请求分成了两类:

  • 简单请求
  • 非简单请求
简单请求
简单请求需要符合以下特征
  • 请求方法为 GET、POST、HEAD
  • 请求头只能使用下面的字段:
    • Accept 浏览器能够接受的响应内容类型。
    • Accept-Language浏览器能够接受的自然语言列表。
    • Content-Type 请求对应的类型,只限于 text/plain、multipart/form-data、application/x-www-form-urlencoded。
    • Content-Language浏览器希望采用的自然语言。
    • Save-Data浏览器是否希望减少数据传输量。
简单请求流程如下
  • 浏览器发出简单请求的时候,会在请求头部增加一个 Origin 字段,对应的值为当前请求的源信息。
  • 当服务端收到请求后,会根据请求头字段 Origin 做出判断后返回相应的内容。
  • 浏览器收到响应报文后会根据响应头部字段 Access-Control-Allow-Origin 进行判断,这个字段值为服务端允许跨域请求的源,其中通配符 * 表示允许所有跨域请求。如果头部信息没有包含 Access-Control-Allow-Origin 字段或者响应的头部字段 Access-Control-Allow-Origin 不允许当前源的请求,则会抛出错误。
非简单请求
  • 只要不符合上述简单请求的特征,会变成非简单请求,浏览器在处理非简单的请求时,浏览器会先发出一个预检请求(Preflight)。这个预检请求为 OPTIONS 方法,并会添加了 1 个请求头部字段 Access-Control-Request-Method,值为跨域请求所使用的请求方法。
  • 在服务端收到预检请求后,除了在响应头部添加 Access-Control-Allow-Origin 字段之外,至少还会添加 Access-Control-Allow-Methods 字段来告诉浏览器服务端允许的请求方法,并返回 204 状态码。
  • 服务端还根据浏览器的 Access-Control-Request-Headers 字段回应了一个 Access-Control-Allow-Headers 字段,来告诉浏览器服务端允许的请求头部字段。
  • 浏览器得到预检请求响应的头部字段之后,会判断当前请求服务端是否在服务端许可范围之内,如果在则继续发送跨域请求,反之则直接报错。
CORS常用头部字段
    • 请求首部字段, Origin 指示了请求来自于哪个站点, 包括协议、域名、端口、不包括路径部分,在不携带凭证的情况下,可以使是一个*,表示接受任意域名的请求
    • 响应头,用来标识允许哪个域的请求
    • 响应头,用来标识允许哪些请求方法被允许
    • 响应首部, 用于预检请求中,列出了将会在正式请求的允许携带的请求头信息。
    • 响应头,用来告诉浏览器,服务器可以自定义哪些字段暴露给浏览器
    • 是否允许携带Credentials,Credentials可以是 cookies, authorization headers 或 TLS client certificates。
    • 预检请求的缓存时长
JSONP

JSONP(JSON with Padding)的意思就是用 JSON 数据来填充。

怎么填充呢?

结合它的实现方式可以知道,就是把 JSON 数填充到一个回调函数中。是利用 script 标签跨域引用 js 文件不会受到浏览器同源策略的限制,具有天然跨域性。

假设我们要在 http://www.a.com 中向 http://www.b.com 请求数据。

  1. 全局声明一个用来处理返回值的函数 fn,该函数参数为请求的返回结果。
function fn(result) {   
	console.log(result) 
}
  1. 将函数名与其他参数一并写入 URL 中。

let url = 'http://www.b.com?callback=fn&params=...';

  1. 动态创建一个 script 标签,把 URL 赋值给 script 的 src属性。
let script = document.createElement('script'); 
script.setAttribute("type","text/javascript"); 
script.src = url;
document.body.appendChild(script);
  1. 当服务器接收到请求后,解析 URL 参数并进行对应的逻辑处理,得到结果后将其写成回调函数的形式并返回给浏览器。

fn({ list: [], ... })

  1. 在浏览器收到请求返回的 js 脚本之后会立即执行文件内容,即可获取到服务端返回的数据。

JSONP 虽然实现了跨域请求,但也存在以下的几个问题:

  • 只能发送 GET 请求,限制了参数大小和类型。
  • 请求过程无法终止,导致弱网络下处理超时请求比较麻烦。
  • 无法捕获服务端返回的异常信息。
Websocket

Websocket 是 HTML5 规范提出的一个应用层的全双工协议,适用于浏览器与服务器进行实时通信场景。

全双工通信传输的一个术语,这里的“工”指的是通信方向。

“双工”是指从客户端到服务端,以及从服务端到客户端两个方向都可以通信,“全”指的是通信双方可以同时向对方发送数据。与之相对应的还有半双工和单工,半双工指的是双方可以互相向对方发送数据,但双方不能同时发送,单工则指的是数据只能从一方发送到另一方。

下面是一段简单的示例代码。在 a 网站直接创建一个 WebSocket 连接,连接到 b 网站即可,然后调用 WebScoket 实例 ws 的 send() 函数向服务端发送消息,监听实例 ws 的 onmessage 事件得到响应内容。

let ws = new WebSocket("ws://b.com");
ws.onopen = function(){
  // ws.send(...);
}
ws.onmessage = function(e){
  // console.log(e.data);
}
请求代理

我们知道浏览器有同源策略的安全限制,但是服务器没有限制,所以我们可以利用服务器进行请求转发。

以 webpack 为例,利用 webpack-dev-server 配置代理, 当浏览器发起前缀为 /api 的请求时都会被转发到 http://localhost:3000 服务器,代理服务器将获取到响应返回给浏览器。对于浏览器而言还是请求当前网站,但实际上已经被服务端转发。

// webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
};

// 使用 Nginx 作为代理服务器
location /api {
    proxy_pass   http://localhost:3000;
}

页面跨域解决方案

请求跨域之外,页面之间也会有跨域需求,例如使用 iframe 时父子页面之间进行通信。常用方案如下:

  • postMessage
  • document.domain
  • window.name(不常用)
  • location.hash + iframe(不常用)
postMessage

window.postMessage 是 HTML5 推出一个新的函数,用来实现父子页面之间通信,而且不论这两个页面是否同源。

以 https://test.com 和 https://a.test.com 为例子:

// https://test.com
let child = window.open('https://a.test.com');
child.postMessage('hello', 'https://a.test.com');

上面的代码通过 window.open() 函数打开了子页面,然后调用 child.postMessage() 函数发送了字符串数据hello给子页面。

在子页面中,只需要监听message事件即可得到父页面的数据。代码如下:

// https://a.test.com
window.addEventListener('message', function(e) {
  console.log(e.data); // hello
},false);

子页面发送数据时则要通过 window.opener 对象来调用 postMessage() 函数.

// https://a.test.com
window.opener.postMessage('hello', 'https://test.com');
document.domain
  • domain 属性可返回下载当前文档的服务器域名。通过修改 document.domain 的值来进行跨域, 这种情况适合主域名相同,子域名不同的页面。
  • 我们以 https://www.test.com/parent.html,在这个页面里面有一个 iframe,其 src 是 http://a.test.com/child.html。
  • 这时只要把 https://www.test.com/parent.html 和 http://a.test.com/child.html 这两个页面的 document.domain 都设成相同的域名,那么父子页面之间就可以进行跨域通信了,同时还可以共享 cookie。
  • 但要注意的是,只能把 document.domain 设置成更高级的父域才有效果,例如在 ·http://a.test.com/child.html 中可以将 document.domain 设置成 test.com。

ARP协议(网络层)

ARP全称地址解析协议(Address Resolution Protocol) ,它解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。

ARP 工作原理? 只希望大家记住几个关键词:ARP 表、广播问询、单播响应

MAC地址

MAC 地址的全称是 媒体访问控制地址(Media Access Control Address) 。如果说,互联网中每一个资源都由 IP 地址唯一标识(IP 协议内容),那么一切网络设备都由 MAC 地址唯一标识

可以理解为,MAC 地址是一个网络设备真正的身份证号IP 地址只是一种不重复的定位方式(比如说住在某省某市某街道的张三,这种逻辑定位是 IP 地址,他的身份证号才是他的 MAC 地址),也可以理解为 MAC 地址是身份证号,IP 地址是邮政地址。MAC 地址也有一些别称,如 LAN 地址、物理地址、以太网地址等。

还有一点要知道的是,不仅仅是网络资源才有 IP 地址,网络设备也有 IP 地址,比如路由器。但从结构上说,路由器等网络设备的作用是组成一个网络,而且通常是内网,所以它们使用的 IP 地址通常是内网 IP,内网的设备在与内网以外的设备进行通信时,需要用到 NAT 协议。

MAC 地址的长度为 6 字节(48 比特),地址空间大小有 280 万亿之多( 2 48 2^{48} 248),MAC 地址由 IEEE 统一管理与分配,理论上,一个网络设备中的网卡上的 MAC 地址是永久的。不同的网卡生产商从 IEEE 那里购买自己的 MAC 地址空间(MAC 的前 24 比特),也就是前 24 比特由 IEEE 统一管理,保证不会重复。而后 24 比特,由各家生产商自己管理,同样保证生产的两块网卡的 MAC 地址不会重复。

MAC 地址具有可携带性、永久性,身份证号永久地标识一个人的身份,不论他到哪里都不会改变。而 IP 地址不具有这些性质,当一台设备更换了网络,它的 IP 地址也就可能发生改变,也就是它在互联网中的定位发生了变化。

最后,记住,MAC 地址有一个特殊地址:FF-FF-FF-FF-FF-FF(全 1 地址),该地址表示广播地址

ARP协议工作原理

ARP 协议工作时有一个大前提,那就是 ARP 表

在一个局域网内,每个网络设备都自己维护了一个 ARP 表,ARP 表记录了某些其他网络设备的 IP 地址-MAC 地址映射关系,该映射关系以 <IP, MAC, TTL> 三元组的形式存储。其中,TTL 为该映射关系的生存周期,典型值为 20 分钟,超过该时间,该条目将被丢弃。

ARP 的工作原理将分两种场景讨论:

  1. 同一局域网内的 MAC 寻址
  2. 从一个局域网到另一个局域网中的网络设备的寻址

同一局域网内的 MAC 寻址

假设当前有如下场景:IP 地址为137.196.7.23的主机 A,想要给同一局域网内的 IP 地址为137.196.7.14主机 B,发送 IP 数据报文。

再次强调,当主机发送 IP 数据报文时(网络层),仅知道目的地的 IP 地址,并不清楚目的地的 MAC 地址,而 ARP 协议就是解决这一问题的。

为了达成这一目标,主机 A 将不得不通过 ARP 协议来获取主机 B 的 MAC 地址,并将 IP 报文封装成链路层帧,发送到下一跳上。在该局域网内,关于此将按照时间顺序,依次发生如下事件:

  1. 主机 A 检索自己的 ARP 表,发现 ARP 表中并无主机 B 的 IP 地址对应的映射条目,也就无从知道主机 B 的 MAC 地址。
  2. 主机 A 将构造一个 ARP 查询分组,并将其广播到所在的局域网中。ARP 分组是一种特殊报文,ARP 分组有两类,一种是查询分组,另一种是响应分组,它们具有相同的格式,均包含了发送和接收的 IP 地址、发送和接收的 MAC 地址。当然了,查询分组中,发送的 IP 地址,即为主机 A 的 IP 地址,接收的 IP 地址即为主机 B 的 IP 地址,发送的 MAC 地址也是主机 A 的 MAC 地址,但接收的 MAC 地址绝不会是主机 B 的 MAC 地址(因为这正是我们要问询的!),而是一个特殊值——FF-FF-FF-FF-FF-FF,之前说过,该 MAC 地址是广播地址,也就是说,查询分组将广播给该局域网内的所有设备。
  3. 主机 A 构造的查询分组将在该局域网内广播,理论上,每一个设备都会收到该分组,并检查查询分组的接收 IP 地址是否为自己的 IP 地址,如果是,说明查询分组已经到达了主机 B,否则,该查询分组对当前设备无效,丢弃之。
  4. 主机 B 收到了查询分组之后,验证是对自己的问询,接着构造一个 ARP 响应分组,该分组的目的地只有一个——主机 A,发送给主机 A。同时,主机 B 提取查询分组中的 IP 地址和 MAC 地址信息,在自己的 ARP 表中构造一条主机 A 的 IP-MAC 映射记录。ARP 响应分组具有和 ARP 查询分组相同的构造,不同的是,发送和接受的 IP 地址恰恰相反,发送的 MAC 地址为发送者本身,目标 MAC 地址为查询分组的发送者,也就是说,ARP 响应分组只有一个目的地,而非广播。
  5. 主机 A 终将收到主机 B 的响应分组,提取出该分组中的 IP 地址和 MAC 地址后,构造映射信息,加入到自己的 ARP 表中。

在整个过程中,有几点需要补充说明的是:

  1. 主机 A 想要给主机 B 发送 IP 数据报,如果主机 B 的 IP-MAC 映射信息已经存在于主机 A 的 ARP 表中,那么主机 A 无需广播,只需提取 MAC 地址并构造链路层帧发送即可。
  2. ARP 表中的映射信息是有生存周期的,典型值为 20 分钟。
  3. 目标主机接收到了问询主机构造的问询报文后,将先把问询主机的 IP-MAC 映射存进自己的 ARP 表中,这样才能获取到响应的目标 MAC 地址,顺利的发送响应分组。

总结来说,ARP 协议是一个广播问询,单播响应协议

不同局域网内的 MAC 寻址

更复杂的情况是,发送主机 A 和接收主机 B 不在同一个子网中,假设一个一般场景,两台主机所在的子网由一台路由器联通。这里需要注意的是,一般情况下,我们说网络设备都有一个 IP 地址和一个 MAC 地址,这里说的网络设备,更严谨的说法应该是一个接口。路由器作为互联设备,具有多个接口,每个接口同样也应该具备不重复的 IP 地址和 MAC 地址。因此,在讨论 ARP 表时,路由器的多个接口都各自维护一个 ARP 表,而非一个路由器只维护一个 ARP 表。

接下来,回顾同一子网内的 MAC 寻址,如果主机 A 发送一个广播问询分组,那么 A 所在子网内的所有设备(接口)都将不会捕获该分组,因为该分组的目的 IP 地址在另一个子网中,本子网内不会有设备成功接收。那么,主机 A 应该发送怎样的查询分组呢?整个过程按照时间顺序发生的事件如下:

  1. 主机 A 查询 ARP 表,期望寻找到目标路由器的本子网接口的 MAC 地址。(看看通讯录里面有没有能够联系上B的人)
    • 目标路由器指的是,能够根据目的主机 B 的 IP 地址,分析出 B 所在的子网,并把报文转发到 B 所在子网的那个路由器。
  1. 主机 A 未能找到目标路由器的本子网接口的 MAC 地址,将采用 ARP 协议,问询到该 MAC 地址,由于目标接口与主机 A 在同一个子网内,该过程与同一局域网内的 MAC 寻址相同。(没有就在班群里喊,看看谁认识B的)
  2. 主机 A 获取到目标接口的 MAC 地址,先构造 IP 数据报,其中源 IP 是 A 的 IP 地址,目的 IP 地址是 B 的 IP 地址,再构造链路层帧,其中源 MAC 地址是 A 的 MAC 地址,目的 MAC 地址是本子网内与路由器连接的接口的 MAC 地址。主机 A 将把这个链路层帧,以单播的方式,发送给目标接口。(找到认识B的人之后 (我称之为中间人),跟他说“帮我带个信给B”)
  3. 目标接口接收到了主机 A 发过来的链路层帧,解析,根据目的 IP 地址,查询转发表,将该 IP 数据报转发到与主机 B 所在子网相连的接口上。到此,该帧已经从主机 A 所在的子网,转移到了主机 B 所在的子网了。(中间人收到你的消息后,就帮你去联系B)
  4. 路由器接口查询 ARP 表,期望寻找到主机 B 的 MAC 地址。(中间人先看一下自己的通讯录里面有没有B)
  5. 路由器接口如未能找到主机 B 的 MAC 地址,将采用 ARP 协议,广播问询,单播响应,获取到主机 B 的 MAC 地址。(没有的话就去喊话,“B你在哪里啊,有人找你”)
  6. 路由器接口将对 IP 数据报重新封装成链路层帧,目标 MAC 地址为主机 B 的 MAC 地址,单播发送,直到目的地。(成功找到B后,把A的话带给B)

网络安全

XSS攻击

XSS:跨站脚本 (Cross-Site Scripting):代码注入方式

  • 早期常⻅于⽹络论坛,起因是⽹站没有对⽤户的输⼊进⾏严格的限制,使得攻击者可以将脚本上传到帖⼦让其他⼈浏览到有恶意脚本的⻚ ⾯
  • 攻击者往Web页面里插入恶意html标签或者javascript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点

XSS防范方法

  • 首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;
  • 其次任何内容写到页面之前都必须加以encode,避免不小心把html tag弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击

CSRF攻击

CSRF:跨站点请求伪造(Cross-Site Request Forgeries)

  • XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。
  • CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。
    攻击者诱导受害者进⼊第三⽅⽹站,在第三⽅⽹站中,向被攻击⽹站发送跨站请求。利⽤受害者在被攻击⽹站已经获取的注册凭证,绕过后台的⽤户验证,达到冒充⽤户对被攻击的⽹站执⾏某项操作的⽬的。

要完成一次CSRF攻击: 登录受信任网站A,并在本地生成Cookie;在不登出A的情况下,访问危险网站B
⼀个典型的CSRF攻击有着如下的流程:

  • 受害者登录 a.com ,并保留了登录凭证(Cookie)
  • 攻击者引诱受害者访问了 b.comb.coma.com 发送了⼀个请求: a.com/act=xx 浏览器会默认携带a.com的Cookie
  • a.com接收到请求后,对请求进⾏验证,并确认是受害者的凭证,误以为是受害者⾃⼰发送的请求
  • a.com以受害者的名义执⾏了act=xx
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执⾏了⾃⼰定义的操作

CSRF的防御

服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数
通过验证码的方法

CSRF通常从第三⽅⽹站发起,被攻击的⽹站⽆法防⽌攻击发⽣,只能通过增强⾃⼰⽹站针对CSRF的防护能⼒来提升安全性。

CSRF的两个特点: CSRF(通常)发⽣在第三⽅域名。 CSRF攻击者不能获取到Cookie等信息,只是使⽤。
防护策略如下:
阻⽌不明外域的访问: 1,同源检测 ; 2,Samesite Cookie
提交时要求附加本域才能获取的信息:1,CSRF Token; 2,双重Cookie验证

SQL注入

sql注入原理

sql注入原理

  • 通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

sql注入防范

  • 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等
  • 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取
  • 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
  • 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息

DDoS攻击

DDoS 攻击究竟是什么?

DDos 全名 Distributed Denial of Service,翻译成中文就是分布式拒绝服务。指的是处于不同位置的多个攻击者同时向一个或数个目标发动攻击,是一种分布的、协同的大规模攻击方式。单一的 DoS 攻击一般是采用一对一方式的,它利用网络协议和操作系统的一些缺陷,采用欺骗和伪装的策略来进行网络攻击,使网站服务器充斥大量要求回复的信息,消耗网络带宽或系统资源,导致网络或系统不胜负荷以至于瘫痪而停止提供正常的网络服务。

举个例子

我开了一家有五十个座位的重庆火锅店,由于用料上等,童叟无欺。平时门庭若市,生意特别红火,而对面二狗家的火锅店却无人问津。二狗为了对付我,想了一个办法,叫了五十个人来我的火锅店坐着却不点菜,让别的客人无法吃饭。

上面这个例子讲的就是典型的 DDoS 攻击,一般来说是指攻击者利用“肉鸡”对目标网站在较短的时间内发起大量请求,大规模消耗目标网站的主机资源,让它无法正常服务。在线游戏、互联网金融等领域是 DDoS 攻击的高发行业。

攻击方式很多,比如 ICMP FloodUDP FloodNTP FloodSYN FloodCC 攻击DNS Query Flood等等

如何应对 DDoS 攻击?

高防服务器

还是拿开的重庆火锅店举例,高防服务器就是我给重庆火锅店增加了两名保安,这两名保安可以让保护店铺不受流氓骚扰,并且还会定期在店铺周围巡逻防止流氓骚扰。

高防服务器主要是指能独立硬防御 50Gbps 以上的服务器,能够帮助网站拒绝服务攻击,定期扫描网络主节点等,这东西是不错,就是贵~

黑名单

面对火锅店里面的流氓,我一怒之下将他们拍照入档,并禁止他们踏入店铺,但是有的时候遇到长得像的人也会禁止他进入店铺。这个就是设置黑名单,此方法秉承的就是“错杀一千,也不放一百”的原则,会封锁正常流量,影响到正常业务。

DDoS 清洗

DDos 清洗,就是我发现客人进店几分钟以后,但是一直不点餐,我就把他踢出店里。

DDoS 清洗会对用户请求数据进行实时监控,及时发现 DOS 攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。

CDN 加速

CDN 加速,我们可以这么理解:为了减少流氓骚扰,我干脆将火锅店开到了线上,承接外卖服务,这样流氓找不到店在哪里,也耍不来流氓了。

在现实中,CDN 服务将网站访问流量分配到了各个节点中,这样一方面隐藏网站的真实 IP,另一方面即使遭遇 DDoS 攻击,也可以将流量分散到各个节点中,防止源站崩溃。

HTML和CSS

content-box与border-box区别

W3C标准盒模型(content-box)

属性width,height只包含内容content,不包含border和padding。

IE盒模型(border-box)

属性width,height包含border和padding,指的是content+padding+border。

1.盒模型尺寸:

  • content-box(默认样式)
  • border-box

2.content-box与border-box区别

两者的盒子的宽度是否包含表框和内边距

content-box 的 width 不包括 padding 和 border

border-box 的 width 包括 padding 和 border

JavaScript

闭包

函数 函数内部能访问到的变量 的总和,就是一个闭包

闭包的作用

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。

例子

假设我们在做一个游戏,在写其中关于「还剩几条命」的代码。

如果不用闭包,你可以直接用一个全局变量:

window.lives = 30 // 还有三十条命

这样看起来很不妥。万一不小心把这个值改成 -1 了怎么办。所以我们不能让别人「直接访问」这个变量。怎么办呢?

用局部变量。

但是用局部变量别人又访问不到,怎么办呢?

暴露一个访问器(函数),让别人可以「间接访问」。

!function(){

  var lives = 50

  window.奖励一条命 = function(){
    lives += 1
  }

  window.死一条命 = function(){
    lives -= 1
  }

}()

为什么所有的JavaScript函数都是闭包

因为JavaScript中的函数总是可以访问外部词法作用域的变量

EventLoop

为了防止某个耗时任务导致程序假死的问题,JavaScript把待执行的任务分成了两类

  • 同步任务
  • 异步任务, 由JavaScript委托给宿主环境进行执行,异步任务执行完成后,会通知JavaScript主线程执行异步任务的回调函数

宏任务和微任务

宏任务和微任务的执行顺序

每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有的微任务之后,再继续执行下一个宏任务

构造函数

构造函数是一种特殊的函数

function Star(uname, age){
    this.uname = uname
    this.age = age
    this.sing = function(){
        console.log('我会唱歌')    
    }
}
var a = new Star('a', 18)

new在执行时会做四件事情:

  • 在内存中创建一个新的空对象
  • 让this指向这个新对象
  • 执行构造函数里面的代码,给这个新对象添加属性和方法
  • 返回这个新对象(所以构造里面不需要return)

实例成员:通过a.age访问。静态成员:通过Star.sex访问

构造函数的问题

每次实例化时里面的方法都要创建一个新的,浪费内存

原型prototype

构造函数通过原型分配的函数是所有对象所共享的

每一个构造函数都有一个prototype属性,可以把不变的方法定义在prototype对象上,让对象实例共享方法

Star.prototype.sing = function(){
    console.log('我会唱歌')   
}

对象身上系统自己添加一个__proto__属性,指向构造函数的原型对象prototype

方法查找规则:先看对象,在看构造函数原型

对象原型(proto)和构造函数(prototype)原型对象里面都有constructor(构造函数)属性,指向构造函数本身

Star.prototype = {
    //修改了原来的原型对象,必须手动地利用constructor指回原来的构造函数
    constructor: Star,
    sing: function(){},
}

原型链

原型对象this指向实例对象,谁调用就指向谁

扩展内置对象方法

Array.prototype.sum = function(){
    let sum = 0;
    for(let i = 0; i < this.length; i++){
        sum += this[i]    
    }
    return sum;
}

预解析

JavaScript代码是由浏览器中的JavaScript解析器来执行的。

  • JavaScript解析器在运行JavaScript代码时分为两步:预解析和代码执行
    • 预解析 js引擎会把js里面的所有var和function提升到当前作用域的最前面
    • 代码执行 按照代码书写的顺序从上往下执行
  • 预解析分为 变量预解析(变量提升)和 函数预解析(函数提升)
    • 变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作
    • 函数提升 把函数声明提升到当前作用域的最前面 不调用函数

垃圾回收策略

标记清除算法

策略

标记清除(Mark-Sweep),目前在 JavaScript引擎 里这种算法是最常用的,到目前为止的大多数浏览器的 JavaScript引擎 都在采用标记清除算法,只是各大浏览器厂商还对此算法进行了优化加工,且不同浏览器的 JavaScript引擎 在运行垃圾回收的频率上有所差异

引擎在执行 GC(使用标记清除算法)时,需要从出发点去遍历内存中所有的对象去打标记,而这个出发点有很多,我们称之为一组 根 对象,而所谓的根对象,其实在浏览器环境中包括又不止于 全局Window对象、文档DOM树 等

整个标记清除算法大致过程就像下面这样

  • 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
  • 然后从各个根对象开始遍历,把不是垃圾的节点改成1
  • 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
  • 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
优点

标记清除算法的优点只有一个,那就是实现比较简单,打标记也无非打与不打两种情况,这使得一位二进制位(0和1)就可以为其标记,非常简单

缺点

标记清除算法有一个很大的缺点,就是在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了 内存碎片(如下图),并且由于剩余空闲内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配的问题

假设我们新建对象分配内存时需要大小为 size,由于空闲内存是间断的、不连续的,则需要对空闲内存列表进行一次单向遍历找出大于等于 size 的块才能为其分配(如下图)

那如何找到合适的块呢?我们可以采取下面三种分配策略

  • First-fit,找到大于等于 size 的块立即返回
  • Best-fit,遍历整个空闲列表,返回大于等于 size 的最小分块
  • Worst-fit,遍历整个空闲列表,找到最大的分块,然后切成两部分,一部分 size 大小,并将该部分返回

这三种策略里面 Worst-fit 的空间利用率看起来是最合理,但实际上切分之后会造成更多的小块,形成内存碎片,所以不推荐使用,对于 First-fit 和 Best-fit 来说,考虑到分配的速度和效率 First-fit 是更为明智的选择

综上所述,标记清除算法或者说策略就有两个很明显的缺点

  • 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块
  • 分配速度慢,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢

标记整理(Mark-Compact)算法 就可以有效地解决,它的标记阶段和标记清除算法没有什么不同,只是标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存(如下图)

引用计数法

策略

引用计数(Reference Counting),这其实是早先的一种垃圾回收算法,它把 对象是否不再需要 简化定义为 对象有没有其他对象引用到它,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,目前很少使用这种算法了,因为它的问题很多,不过我们还是需要了解一下

它的策略是跟踪记录每个变量值被使用的次数

  • 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
  • 如果同一个值又被赋给另一个变量,那么引用数加 1
  • 如果该变量的值被其他的值覆盖了,则引用次数减 1
  • 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
问题

循环引用

function test(){
  let A = new Object()
  let B = new Object()
  
  A.b = B
  B.a = A
}

如上所示,对象 A 和 B 通过各自的属性相互引用着,按照上文的引用计数策略,它们的引用数量都是 2,但是,在函数 test 执行完成之后,对象 A 和 B 是要被清理的,但使用引用计数则不会被清理,因为它们的引用数量不会变成 0,假如此函数在程序中被多次调用,那么就会造成大量的内存不会被释放

我们再用标记清除的角度看一下,当函数结束后,两个对象都不在作用域中,A 和 B 都会被当作非活动对象来清除掉,相比之下,引用计数则不会释放,也就会造成大量无用内存占用,这也是后来放弃引用计数,使用标记清除的原因之一

优点

引用计数算法的优点我们对比标记清除来看就会清晰很多,首先引用计数在引用值为 0 时,也就是在变成垃圾的那一刻就会被回收,所以它可以立即回收垃圾

而标记清除算法需要每隔一段时间进行一次,那在应用程序(JS脚本)运行过程中线程就必须要暂停去执行一段时间的 GC,另外,标记清除算法需要遍历堆里的活动以及非活动对象来清除,而引用计数则只需要在引用时计数就可以了

缺点

引用计数的缺点想必大家也都很明朗了,首先它需要一个计数器,而此计数器需要占很大的位置,因为我们也不知道被引用数量的上限,还有就是无法解决循环引用无法回收的问题,这也是最严重的

call, apply,bind的区别

我们都知道call,apply,bind都可以用来改变this指向,但这三个函数稍稍有些不同。

  • call与apply唯一的区别就是它们的传参方式不同,call从第二个参数开始都是传给函数的,apply只有两个参数,第二个参数是一个数组,传给函数的参数都写在这个数组里面
  • call与apply改变了函数的this指向后会立即执行,而bind是改变函数的this指向并返回这个这个函数,不会立即执行
  • call与apply的返回值是函数的执行结果,bind的返回值是改变了this指向的函数的拷贝

call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。(来自MDN)

「语法:」 fun.call(thisArg,arg1[,arg2,arg3…])

「解释:」 call方法用来为一个函数指定this对象,第一个参数是你想要指定的那个对象,后面都是传给该函数的参数,之间用逗号隔开

apply

「apply()」 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。(来自MDN)

「语法:」 fun.apply(thisArg,[arg1,arg2,arg3…])

「解释:」 apply方法与call方法基本类似,不同的是,两者的参数形式,apply方法传递的是一个由若干个参数组成的数组。

bind

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

「语法:」 fun.bind(thisArg,arg1[,arg2,arg3…])()

「解释:」 bind方法用来为方法指定this对象并返回一个新的函数,它的参数与call函数一样。「它本身是不会调用的,需要自己手动调用。」

「注意这里需要自己再调用一次,因为bind只会返回这个改变了this指向的函数,并不会自己执行」

call,apply该用哪个?

  • 参数数量,顺序确定就用call,参数数量,顺序不确定就用apply
  • 参数数量少用call,参数数量多用apply
  • 参数集合已经是一个数组的情况,最好用apply

Ajax优缺点

  • 优点
    • 无需刷新页面与服务端进行通信
    • 允许根据用户事件更新部分页面内容
  • 缺点
    • 没有浏览历史,不可回退
    • 存在跨域问题(同源)
    • SEO不友好

commonJs和es6的区别

https://www.rstk.cn/news/62669.html?action=onClick

ps: 内容大多为网络上收集的,若有侵权,请联系本人删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值