/**
* 1.常见浏览器的内核
如图'浏览器内核图'
2. 浏览器的主要组成部分
*用户界面 包括地址栏 前进后退按钮 书签菜单等
*浏览器引擎 在用户界面和呈现引擎之间传送指令
*呈现引擎 负责显示请求的内容,如果请求的内容是HTML,他就负责解析HTML 和CSS 内容 并将解析后的内容显示在屏幕上
*网络 用于网络调用 比如HTTP请求
*用户界面后端 用于绘制基本的窗口小部件 比如组合框和窗口
*JavaScript解释器 用于解析和执行JavaScript代码
*数据存储 这是持久层 浏览器需要在硬盘上保存各种数据,例如Cookie 新的HTML规范(HTML5)定义了'网络数据库' 这是一个完整但是轻便的浏览器内数据库
值得注意的是,和大多数浏览器不同,Chrome 浏览器每个标签页都分别对应一个呈现引擎实例,每个标签页都是一个独立进程
3. 为什么JavaScript是单线程的,与异步冲突吗
js其实没有线程概念的,单线程也只是相对于多线程而言
JS的单线程是指一个浏览器进程中只有一个JS执行线程,同一时刻内只有一段代码在执行
举个例子:假设JS支持多线程操作,JS可以操作DOM,那么一个线程获取DOM,一个线程删除DOM,这样明显不合理
来看段代码
function foo() {
console.log("first")
setTimeout(( function(){
console.log( 'second' )
}),5)
}
for (var i = 0
foo()
}
打印结果就是首先是很多个first,然后再是second。
异步机制是浏览器的两个或以上常驻线程共同完成的,举个例子,比如异步请求由两个常驻线程,JS执行线程和事件触发线程共同完成的
**JS执行线程发起异步请求,浏览器会开启一个HTTP请求线程执行请求,这时JS的任务完成 继续执行线程队列中剩下任务
**然后在未来的第一个时刻事件触发线程监视到之前的发钱的HTTP请求已完成,他就会把完成事件拆入到JS执行队列的尾部等待JS处理
再比如定时器触发(setTimeout setInterval)是由 浏览器的定时线程 执行的定时计数,然后在定时时间把定时处理函数的执行请求插入到JS执行队列的尾端(所以用这两个函数的时候,实际的执行时间是大于或等于指定时间的,不保证能准确定时的)
所以说 JS单线程 与异步更多是浏览器行为,之间不冲突
4. CSS加载会造成阻塞吗
先给出结论
**CSS不会阻塞DOM解析,但是会阻塞DOM渲染
** CSS会阻塞JS执行 并不会阻塞JS文件下载
先讲讲CSSOM的作用
**第一个是提供给JavaScript操作样式表的能力
** 第二个是为布局树的合成提供基础的样式信息
** 这个CSSOM提现在DOM中就是document.styleSheets
由之前的讲过的浏览器渲染流程我们可以看出
DOM和CSSOM通常是并行构建的,所以 CSS加载不会阻塞DOM解析
然而由于Render Tree是依赖DOM Tree CSSOM Tree的,所以它必须等到两者都加载完毕,完成相应的构建,才开始渲染因此 CSS加载会阻塞DOM渲染
由于JavaScript是可以操作DOM和CSS样式,如果在修改这些属性的同时渲染界面 (即JavaScript线程和UI线程同时进程) 那么渲染线程前后获得的元素数据就可能不一致了
因此为了防止渲染出现不可预期的结果,浏览器设置 GUI渲染线程 与JavaScript引擎为互斥的关系
**有个需要注意的点 有时候JS需要等待CSS的下载,这是为什么??**
仔细思考下,其实这样做是有道理的,如果脚本的内容是获取元素的样式,宽高等css控制的属性,浏览器是需要计算的,也就是依赖于CSS,浏览器也无法感知脚本内容到底是什么,为了避免样式获取,因而只好等前面所有样式下载完毕后 在执行JS
**JS文件下载和CSS文件下载是并行的 有时候CSS文件很大,所以JS需要等待**
因此,样式表会在后面JS执行前先加载执行完毕,所以CSS会阻塞后面JS的执行
5.为什么JS会阻塞页面加载
先给出结论, **JS阻塞DOM解析 也就是会阻塞页面
这也是为什么说JS文件放在最下面的原因 那为什么会阻塞DOM解析呢
可以这么理解:
*由于JavaScript是可以操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程 和UI线程同时运行) 那么渲染线程前后获得的元素数据就可能不一致了
*因此为了防止渲染出现不可预期的结果,浏览器设置(GUI渲染线程与JavaScript引擎线程执行完毕 才会接着执行)
*因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉
另外 如果JavaScript文件中没有操作DOM相关代码,就可以将该JavaScript 脚本设置为异步加载通过async 或defer来标记代码
6.defer和async的区别
**两者都是异步去加载外部JS文件,不会阻塞DOM解析
**async是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照他们指定的先后顺序执行,该属性对内联脚本不作用(即 没有src属性的脚本)
**defer 是在JS加载完成后,整个文档解析完成后,触发 DOMContentLoaded 事件前执行, 如果缺少 src属性, 该属性不应该被使用,因为这种情况下它不起作用
7.DOMContentLoaded与load区别
**DOMContentLoaded事件触发时,仅当DOM解析完成后,不包括样式表,图片等资源
**onload事件触发过,页面上所有的DOM样式表,脚本 图片 等资源加载完毕
那么也就是说DOMContentLoaded -》 load
我们再聊聊 async 和defer区别
带async 的脚本一定会在load事件之前执行,可能会DOMContentLoaded之前或之后执行
**情况1 HTML还没有解析完的时候,async脚本已经加载完了,那么HTML停止解析,去执行脚本,脚本执行完毕后触发DOMContentLoaded事件
**情况2 HTML解析完了之后,async脚本才加载完,然后再执行脚本,那么在HTML解析完毕 async脚本还没加载完的时候就触发DOMContentLoaded事件
如果是script标签中包含defer 那么这块脚本将不会影响HTML文档的解析,而是等待HTML解析完成之后才会执行, 解析完成后才会执行,而DOMContentLoaded 只有在defer脚本执行结束后才会被触发
**情况1 HTML还没解析完成时,defer脚本已经加载完毕,那么defer脚本将等待HTML解析完成后再执行
defer脚本执行完毕后触发DOMContentLoaded 事件
**情况2 HTML解析完成后,defer脚本还没加载完毕,那么defer脚本继续加载,加载完成后直接执行,执行完毕后触发DOMContentLoaded 事件
8. 为什么CSS动画比JavaScript高效
我们如果了解的话,都知道will-change只是一个优化的手段,使用JS改变transform也可以享受这个属性带来的变化,所以这个说法上有点不妥
如果是动画这个问题,更应该使用CSS动画,涉及的知识点设施重排重绘合成
如果是说尽可能的避免重排重绘,具体是哪些操作如果是非要去操作JS的话,有哪些优化手段
*使用createDocumentFragment 进行批量的DOM操作
*对于热size scroll等进行防抖节流处理
*rAF优化
9.防抖节流
节流的意思让函数有节制的执行,什么叫节制呢 一段时间只执行一次
规定时间内,只触发一次函数,如果这个时间内触发多次,只有一次生效
function throttle(fn, delay) {
let flag = true
let timer = null
return function(...agrs) {
let context = this
if (!flag) return
flag = false
clearTimeout(timer)
timer = setTimeout (() => {
fn.apply(context, args)
}, delay)
}
}
函数防抖
在事件被触发n秒后再执行回调,如果在这n秒内又被触发则重新计时
核心思想 每次事件触发都会删除原有的定时器,建立新的定时器,通常意思是只管最后一次触发后的开始计时
function dobunce(fn, delay) {
let timer = null
return function(...args) {
let context = this
if (timer) clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(context, args)
}, delay)
}
}
如何使用debounce 和 throttle 以及常见的坑
自己造一个 debounce / throttle 的轮子看起来多么诱人,或者随便找个博文复制过来。「我是建议直接使用 underscore 或 Lodash」 。如果仅需要 _.debounce 和 _.throttle 方法,可以使用 Lodash 的自定义构建工具,生成一个 2KB 的压缩库。使用以下的简单命令即可:
npm i -g lodash-cli
npm i -g lodash-clilodash-cli include=debounce,throttle
复制代码
常见的坑是,不止一次地调用 _.debounce 方法:
// 错误
$(window).on('scroll', function() {
_.debounce(doSomething, 300)
})
// 正确
$(window).on('scroll', _.debounce(doSomething, 200))
复制代码
debounce 方法保存到一个变量以后,就可以用它的私有方法 debounced_version.cancel(),lodash 和 underscore.js 都有效。
let debounced_version = _.debounce(doSomething, 200)
$(window).on('scroll', debounced_version)
// 如果需要的话debounced_version.cancel()
适合应用场景
防抖 * search搜索用户不断输入值时,用防抖来节约Ajax请求,也就是输入框事件 *window触发resize时,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流 *鼠标点击事件 监听滚动事件 游戏发射子弹的频率
10. 谈一谈你对requestAnimationFrame(rAF)理解???
????
???
???
11. 能不能实现图片的懒加载
页面可见区域宽度: document.body.clientWidth
网页可见区域高度:document.body.clientHeight
网页可见区域宽(包括边线的宽):document.body.offsetWidth
网页可见区域高(包括边线的高):document.body.offsetHeight
网页正文全文宽:document.body.scrollWidth
网页正文全文高:doucment.body.scrollHeight
网页被卷去的高:document.body.scrollTop
网页被卷去的左:document.body.scrollLeft
网页正文部分上:window.screenTop
网页正文部分左:window.screenLeft
屏幕分辨率的高:window.screen.heigt
屏幕分辩率的宽:window.screen.width
屏幕可用工作区高度:window.screen.availHeight
原理思路
*拿到所有的照片 img dom
*重点是第二步,判断当前图片是否到了可视范围
*到了可视区域高度后,就将img 的data-src属性设置给src
*绑定window的scroll事件
*为了用户的体验更佳,设置一个占位图
<style>
img{
display: block
height: 320px
margin-top: 20px
margin: 10px auto
}
</style>
<img src="default.png" data-src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1595328889118&di=1665d7e122bc96be92d0f3e1b2f5e302&imgtype=0&src=http%3A%2F%2Fwork.361ser.com%2FContent%2Fueditor%2Fnet%2Fupload%2Fimage%2F20171014%2F6364359407281350179759303.jpg" />
第一种方式
let Img = document.getElemntsByTagName('img')
let len = Img.length
let count = 0
function lazyLoad () {
let viewH = document.body.clientHeight // 可见区域高度
let scrollTop = document.body.scrollTop // 滚动条距离顶部高度
for (let i = count
if (Img[i].offsetTop < scrollTop + viewH) {
if(Img[i].getAttribute('src') === 'default.png') {
Img[i].src = Img[i].getAttribute('data-src')
count ++
}
}
}
}
function throttle(fn, delay) {
let flag = true
let timer = null
return function(...args) {
let context = this
if(!flag) return
flag = false
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args)
flag = true
}, delay)
}
}
window.addEventListener('scroll', throttle(lazyLoad, 1000))
lazyLoad() // 首次加载
12. 说一说你对Cookie localStorage sessionStorage
HTTP是一个无状态协议,这里主要指的是HTTP1.x版本,简单的可以理解为即使同一个客户端连续两次发送请求给同个服务器,服务器也无法识别这个 是同一个客户端,导致的问题,比如现实生活中加入一个商品到自己购物车,但是刷新页面就会丢失
为了解决HTTP1.x无状态导致的问题,后来出现Cookie
**其实Cookie的存在也不是为了解决通讯协议无状态的问题,只是为了解决客户端与服务端会话状态的问题,这个状态是说后端服务的状态而非通讯协议的状态
Cookie存放在本地的好处,就说你关闭了浏览器,Cookie依然可以生效
12.1 Cookie设置
怎么设置简单来说如下
1. 客户端发送HTTP请求到服务器
2. 当服务器收到HTTP请求时,在响应头里面加一个Set-Cookie字段
3. 浏览器收到相应后保存下 Cookie
4. 之后对该服务器每一次请求,都通过Cookie字段将Cookie信息发给服务器
12.2 Cookie指令
如图'Cookie属性图'
12.3 Expires/Max-Age
Expires用于设置Cookie的过期时间
Set-Cookie: id=aadsasa
*当Expires 属性缺省时,表示是会话属性 Cookie
*像上图 Expires的值为session 表示就是会话属性Cookie
*会话性Cookie的时候,值保存在客户端内存中,并在用户关闭浏览器的时候失效
*需要注意的是,有些浏览器提供了会话恢复功能,关闭浏览器会话期Cookie会保留下来?????
*与会话性Cookie相对的是持久性Cookie,持久性Cookie会保存在用户的硬盘中,直至过期或者清除Cookie
Max-Age 用于设置在Cookie失效之前需要经过的秒数,比如
Set-Cookie: id=3as2112xsasa
假如Expires 和Max-Age 都存在,Max-Age优先级更高
12.4 Domain
Domain 指定了Cookie 可以送达的主机名,假如没有指定,那么默认值为当前文档访问地址中的主机部分
不能跨域设置Cookie
12.5 Path
Path指定了一个URL路径,这个路径必须出现在要请求的资源的路径中才可以发送Cookie首部, 比如设置
Path=/docs, /docs/Web/ 下的资源会带Cookie首部,/test 则不会携带Cookie首部
【Domain和Path标识共同定义了Cookie作用域,即是Cookie应该发送给哪些URL】
12.6 Secure属性
标记为Secure的Cookie只应通过被HTTPS协议加密过的请求发送给服务器,使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器之间的传输过程中不被窃取和篡改
12.7 HTTPOnly
设置HTTPOnly属性可以防止客户端脚本通过document.cookie等方式访问Cookie,有助于避免XSS攻击
12.8 SameSite
SameSite属性可以让Cookie在跨站请求时不会被发送,从而可以阻止跨站请求伪造(CSRF)
那这个属性修改有什么影响呢?
如图'SameSite属性修改影响'
从上图可以看出,对大部分的web应用而言,Post表单,iframe,AJAX,Image这四种情况从以前的跨站会发送三方Cookie,SameSite会变为不发送
12.8 Cookie的作用
1. 会话状态管理(如用户登录状态 购物车 游戏分数 或其它需要记录的信息)
2. 个性化设置(如 用户自定义的设置 主题)
3. 浏览器行为跟踪(如 跟踪分析用户行为等等)
12.9 Cookie的缺点
从大小 安全 增加请求大小
1.容量缺陷 Cookie的体积上限4k 存储信息很少
2. 降低性能, Cookie紧跟着域名,不管域名下某个地址是否需要Cookie,请求都会带着完整Cookie,请求数量增加 造成浪费
3. 安全缺陷,Cookie是以纯文本的形式在浏览器和服务器之间传递,很容易被非法用户获取,当HTTPOnly为false时候,Cookie信息还可以直接用JS脚本读取。
12.10 localStorage 和 sessionStorage
在 web 本地存储场景上,cookie 的使用受到种种限制,最关键的就是存储容量太小和数据无法持久化存储。
异同点如图'local Session Storage 异同点'
**操作方式
接下来我们看具体如何操作localStorage 和sessionStorage
let obj = { name: 'jay', age: 10 }
localStorage.setItem('name': 'jay' )
localStorage.setItem('info': JSON.stringfy(obj))
接着进入相同的域名就能拿到相应的值
let name = localStroage.getItem('name')
let info = JSON.parse(localStroage.getItem('info'))
从这里可以看出 localStorage 存储的都是字符串 ,如果是存储对象需要调用JSON的stringfy,并且JSON.parse 再转为对象
***应用场景
localStorage 适持久化缓存数据, 比如页面偏好配置,官网logo base64格式的图片资源
sessionStorage 适合一次性临时数据保存,存储本地浏览信息记录,这样子 页面关闭就不需要这些记录,还有对表单信息进行维护,这样页面刷新表单信息不会丢失
13. 缓存
13.1 强缓存
强缓存两个字段 Expires Cache-Control
强缓存分为两种情况,一种是发送HTTP请求,一种是不需要
首先检查强缓存,这个阶段不需要发送HTTP请求,通过查找不同的字段来进行
不同的HTTP版本所以不同
Expires
Expires 即过期时间,此时间是根据服务器时间而言,存在于 服务端返回的响应头中,在这个过期时间之前 可以直接从缓存里获取数据,无需再次请求,
Expires: Mon, 29 Jun 2020 11:10:23 6MT
表示在2020年7月29日11:10:23过期,过期时就会重新向服务器发起请求
这个方式有个问题 服务器时间和浏览器时间可能不一致 所以HTTP1.1提出新的字段替代
Cache-Control
HTTP1.1 版本中,是用的就是这个字段,这个字段采用的时间是过期时长,对应的是max-age
Cache-Control: max-age=6000
6000秒 可以直接使用缓存
当然它还有很多关键的指令 梳理了几个重要的
**当Expires和Cache-Control 同时存在时候,优先考虑Cache-Control
**当然如果资源失效了, 也就是没有命中强缓存,接下来就是协商缓存
13.2 协商缓存
强缓存失效,浏览器在请求头中携带相应的 缓存Tag 来向服务器发送请求,服务器根据对应的Tag 来决定是否需要缓存
协商缓存分为两种, Last-Modified 和ETag 两者各有优势,并不存在谁对谁错的优势,与上面强缓存有所不同
Last-Modified
这个字段展示的是 最后的修改时间 在浏览器第一次给服务器发送请求后,服务器就会在响应头中加上这个字段
浏览器接收到后, 如果再次请求 就会在请求头中携带 If-Modified-since字段
这个字段对应的值 也就是服务器传来的最后的修改时间
服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中 该资源最后的修改时间对比
**如果请求头中的这个值小于最后修改时间,说明是时候更新了,返回新的资源,跟常规请求HTTP请求的相应的流程一样
**否则返回304 告诉浏览器用缓存
E-Tag
E-Tag是服务器根据当前文件的内容,对文件生成唯一标识,比如MD5算法,只要里面的内容有改动这个值就会修改,服务器通过把响应头把该字段给浏览器
浏览器接收到If-None-Match后 会跟服务器上该资源的ETag进行比对
**如果两者一样的话返回304 走缓存
**如果是不一样内容更新返回新的资源,走HTTP请求流程
两者对比
*性能上 Last-Modified 由于 ETag, Last-Modifed记录的是时间点,而ETag需要根据文件的MD5算法生成对应的hash值
*精度上,ETag 优于 Last-Modefied ETag 按照内容给资源带上标识,能够准确地感知资源的变化,Last-Modeifed在某些场景并不能准备感知变化比如
【编辑了资源文件,但是文件内容没有修改这个会造成焕春失败】
【Last-Modified 能够感知的单位时间是秒,如果文件在1秒内改变了很多次,那么这个时候Last-modified并没有体现出修改】
最后如果两种方式都支持的话,服务器会有限考虑ETag
13.3 缓存位置
接下来我们考虑使用缓存的话,缓存的位置在哪
浏览器缓存位置分为四种
Service Worker
Memory Cache
Disk Cache
Push Cache
1)Service Worker
这个场景 比如PWA 它借鉴了Web Worker的思路,由于它脱离了浏览器的窗体,因此无法直接访问DOM,他能完成的操作比如 【离线缓存、消息推送、网络代理,其中离线缓存就是Service Worker Cache】
2) Memory Cache
指的是内存缓存,从效率上讲它是最快的,从存活时间上讲又是最短的,当渲染进程结束后,内存缓存也就不存在
3) Disk Cache
存储在磁盘中的缓存,从 存取效率上讲是比 内存缓存慢的, 优势是存储量和存储时间
4) Disk Cache VS Memory Cache
两者对比,主要的策略
内容使用率高的话,文件优先进入磁盘
比较大的JS,CSS文件会直接放入磁盘,反之放入内存
5) Push Cache
推送缓存,这算是浏览器中的最后一道防线,它是HTTP/2 的内容
13.4 总结
*首先检查Cache-Control 看强缓存是否可用
*如果可以直接用,否则进入协商缓存,发送HTTP请求,服务器通过请求头中的If-Modified-Since 或者If-Modified-Match 字段检查资源是否更新
*资源更新返回200再走HTTP请求流程
*否则返回304 浏览器走缓存请求资源
14. 说说输入URL到页面呈现发生了什么
一旦问这个问题的话,我觉得肯定是一个非常深的问题了,无论从深度还是广度上,要真的答好这个题目,或者梳理清楚的话,挺难的,毕竟一个非常综合性的问题,我作为一个刚刚入门的小白,只能梳理部分知识,更深的知识可以去看看参考链接。
那么我们就开始吧,假设你输入的内容是 hhtps://www.baidu.com
14.1网络请求
14.1.1 构建请求
首先浏览器构建【请求行】信息,构建好后,浏览器准备发起请求
GFT /HTTP1.1
GET 是请求方法,路径就是根路径 HTTP协议版本1.1
14.1.2 查找缓存
在真正发起网络请求之前,浏览器会现在浏览器缓存中查询是否有需要请求的文件
先检查强缓存,如果命中的话直接使用,否则进入下一步,上边有强缓存的知识点
14.1.3 DNS解析
输入的域名的话,我们需要根据域名去获取对应的IP地址,这个过程需要依赖一个服务系统,叫做是DNS域名解析,从查找到获取到具体的IP的过程叫做是DNS解析 DNS可以查看下边的阮一峰日志
http://www.ruanyifeng.com/blog/2016/06/dns.html
首先,浏览器提供了DNS数据缓存功能,如果一个域名已经解析过了,那么就会把解析的结果缓存下来,下次查找的话直接去缓存中找,不需要结果DNS解析
【解析过程如下】
1.首先查看是否有对应的域名缓存,有的话直接用缓存的IP访问
ipconfig/displaydns
// 输入这个命令就可以查看对应的电脑中是否有缓存
2. 如果缓存中没有,划去查找hosts文件, 一般在 c:\windows\system32\drivers\etc\hosts
3. 如果hosts文件里没有找到解析的域名,则将 域名发往自己配置的dns服务器, 也叫本地dns服务器
ipconfig/all
// 这个命令可以查看自己的本地dns服务器
4. 如果 本地 dns 服务器有相应的域名记录,则返回记录
电脑的 dns服务器一般是各大运营商如电信联通等提供的,本身缓存了大量的常见域名的ip,所以常见的网站,都是有记录的,不需要找根服务器】
5. 如果电脑自己的服务器没有记录,会去找根服务器, 根服务器全球只要13组,回去找其中之一,找了根服务器后, 根服务器会根据请求的域名,返回对应的 顶级域名服务器 如:
1. 如果请求的域名是 xxx.com 则返回负责com域名的服务器
2. 如果是xxx.cn 则发给负责cn域的服务器
3. 如果是xxx.ca 则发给负责ca域的服务器
6. 顶级域服务器收到请求,会返回二级服务器的地址
1. 比如一个网址是www.xxx.edu.cn 则顶级域名服务器再转发给负责 .edu.cn 域的二级服务器
7. 以此类推,最终会发到负责 查域名的,最精确的那台dns 可以得到查询结果
8. 最后一步 本地dns服务器,把最终的解析结果,返回给客户端,对客户端来讲,只是一去一回的事情,客户端并不知道本地dns服务器经过了千山万水
14.1.4 TCP连接
我们需要了解的是Chrome 在同一个域名下要求最多有6个tcp连接,超过6个就需要等待
TCP连接建立的阶段有三个
*通过三次握手建立客户端和服务器之间连接
*数据传输
*断开连接,数据传输完成 四次挥手来断开连接
从上可以看出,TCP的数据传输的可靠性,三次握手 四次挥手 和数据包检验保证数据到达接收方
14.1.5 发送HTTP请求
TCP 连接完成后,接下来就可以与服务器进行通信,就是常说的HTTP请求,HTTP请求需要三种
请求行 请求头 请求体
Accept: text/html,application/xhtml+xml,application/xml
Accept-Encoding: gzip, deflate, br
Accept-Language: ah-cn,zh
Cache-Control: no-Cache
Connection: keep-alive
Cookie: cookie信息
Host: juejin
Pragma: no-cache
Upgrade-Insecure-Requests: 2
User-Agent: Mozilla/5.0 (Windows NT 10.0
最后就是请求体,请求体只有在POST请求常见下存在,常见的表单提交
14.2网络响应
HTTP请求到服务器,服务器进行对应的处理,最后要把数据传给浏览器,也就是我们通常说的返回网络响应
跟请求部分类似,网络响应有是哪个部分,响应行 响应头 响应体
响应行 HTTP/1.1 200 ok
对应的响应头数据是怎么样的
Access-Control-Max-Age: 86400
Cache-control: private
Connection: close
Content-Encoding: gzip
Content-Type: text/html
Date: Wed, 22 Jul 2020 13:24:49 GMT
Vary: Accept-Encoding
Set-Cookie: ab={}
Transfer-Encoding: chunked
接下来,我们数据拿到了,你认为就会断开TCP连接吗
这个的看响应头中的Connection字段,上面的字段值为close,那么就会断开,一般情况下,HTTP1.1版本的话,通常请求头会包含[Connection: Keep-Alive]表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接
上面的情况就会断开TCP连接,请求-响应流程结束
到这里就是网络流程基本结束,,接下来就是渲染流程
14.3渲染阶段
总的来说有以下几个阶段
*构建DOM树
*样式计算
*布局阶段
*分层
*分块
* 光栅化
* 合成
关于渲染流程的话,可以看之前总结的一篇 https://juejin.cn/post/6847902222349500430
15. 重排重绘
https://juejin.cn/post/6847902222349500430
16. 跨域 同源策略 跨域解决方案
跨域,是指浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全机制
16.1 同源策略
同源策略是一个安全策略,同源指得是 协议 域名 端口相同
如图'同源策略'
浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确的授权情况下,不能读写对方的资源
限制了一下行为
*Cookie LocalStorage和indexDB无法读取
*DOM和JS对象无法获取
*Ajax请求发送不出去
16.2 解决方案
例举了几个常用的
*jsonp跨域
利用script 标签没有跨域属性,网页可以拿到从其他源产生动态的JSON数据,当然了JSONP请求一定要对方服务器支持
【与AJAX相比】JSONP和AJAX相同,都是客户端向服务器发送请求,从服务器获取数据的方式,但是AJAX属于同源策略,JSONP属于非同源
【JSONP的优点】兼容性比较好,可用于解决主流浏览器的跨域数据访问的问题,缺点是只支持get请求,具有局限,不安全可能受到XSS攻击
【思路】1.创建script标签 2.设置script标签的src属性,以问好传递参数,设置好回调callback名称 3. 插入HTML文本中 4.调用回到函数,res参数就是获取的数据
```
let script = document.createElement('script')
script.src = 'http:www.baidu.cn/login?username=renzhen&callback'
document.body.appenChild(script)
function callback(res) {
console.log(res)
}
```
当然jQuery也支持jsonp的实现
```
$.ajax({
url: 'http:www.baidu.cn/login?',
type: 'GET',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: 'callback',
data: {
username: 'renzhen'
}
})
```
【jsonp的优点】
*它不像XMLHTTPRequest对象实现的AJAX请求那样受到同源策略的限制
*它的兼容性更好,在更加古老的浏览器都可以运行,不需要XMLHttpRequest 或ActiveX的支持
*并且在请求完毕后可以通过调用callback的方式回传结果
【jsonp的缺点】
*它支持GET请求不支持POST等其他类型的HTTP请求
*它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题
跨域资源共享CORS
CORS(Cross-Orign Resource Sharing )跨域资源共享,定义了必须在发给我你跨域资源时,浏览器与服务器应该如何沟通,CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定 请求或响应是应该成功还是失败,目前,所有浏览器都支持该功能,IE浏览器不能低于IE10,整个CORS通信过程,都是浏览器自动完成,不需要用户参与,对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头部信息,有时还会多出一次附加请求但用户不会察觉
上面是概念引用,你要记住下面几点
【CORS 需要浏览器和后端同时支持,IE 8 和 9 需要通过XDomainRequest 来实现】
【浏览器会自动进行CORS通信,实现CORS通信的关键是后端,只要后端实现了CORS就实现了跨域】
【服务端设置Access-Control-Allow-Origin就 可以开启CORS 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源】
请求分为 简单请求 非简单请求
【简单请求】:(满足下面两个条件之一就属于简单请求)
条件1: 使用下列方法之一 GET HEAD POST
条件2: Content-Type 的值仅限于下列三者之一
text/plain multipart/form-data applicatio/x-www-form-urlencoded
请求中的任意 XMLHTTPRequestUpload 对象可以使用XMLHTTPRequest.upload属性访问
【复杂请求】: 不符合以上条件的请求就肯定是复杂请求了,复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为 ‘预检’请求,该请求时option方法的,通过改请求来指导服务器是否允许跨域请求
直接上例子,查看一个完整的复杂请求,介绍CORS请求的字段
```
// server2.js
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] // 设置白名单
app.use(function (req, res, next){
let origin = req.headers.orign
if (whitList.includes(origin)) {
// 设置那个源可以访问我
res.setHeader('Access-Control-Allow-Origin', orign)
// 允许携带哪个头访问我
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许返回的头
res.setHeader('Access-Control-Exposs-Headers', 'name')
if (req.method === 'OPTIONS') {
res.end() // OPTIONS 请求不作任何处理
}
}
next()
})
app.put('getData', function(req,res) {
console.log(req.headers)
res.setHeader('name', 'jw') // 返回一个响应头 后台需要设置
res.end('renzhen')
})
app.get('/getData', function(req, res) {
console.log(req.headers)
res.end('renzhen')
})
app.use(express.static(__dirname))
app.listen(4000)
```
上述代码 有 http://localhost:3000/index.html 向 http://localhost: 4000/ 跨域请求 正如我们上面所说 后端是实现CORS通信的关键
【与JSONP对比】
*JSONP 只能实现GET请求,而CORS支持所有类型的HTTP请求
*使用CORS 开发者可以使用普通的XMLHttpRequest发起请求和获取数据,比起JSONP中更好的错误处理
*JSONP 主要被老的浏览器支持,他们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS
WebSocket 协议跨域
WebSocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同事也是跨域的一种解决方案
WebSocket和HTTP 都是应用层协议,都基于TCP协议,但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket的server与client 都能主动向对方发送或接受数据,同时 WebSocket在建立连接时需要借助HTTP协议,连接建立好了之后,client 与 server 之间的双向通信就与HTTP 无关了
下边这个例子
本地文件socket.html 向 localhost:3000发生数据和接受数据
```
<script>
let socket = new WebSocket('ws://localhost:3000')
socket.onopen = function () {
socket.send('renzhen')
}
socket.onmessage = function (e) {
console.log(e.data)
}
</script>
// 后端部分
// server.js
let WebSocket = require('ws') // 记得安装ws
let wss = new WebSocket.Server({port:3000})
wss.on('connection', function(ws) {
ws.on('message', function (data) {
console.log(data)
ws.send('renzhen')
})
})
```
这是因为原生WebSocket API 用起来不方便,它很好地封装了webSocket接口
提供简单的灵活的接口,也对不支持WebSocket的浏览器提供了向下兼容
Nginx代理跨域
17. XSS攻击
17.1 什么是XSS攻击
XSS全称是Cross Site Scripting,为了与除上述区分,故简称XSS 跨站脚本
XSS是指黑客往HTML文件中或者DOM中注入恶意脚本,从而用户浏览页面时利用恶意的脚本对用户攻击的一种手段
注入恶意脚本可以完成这些事情
1:获取Cookie
2. 监听用户行为,比如输入账号密码后之间发给黑客服务器
3. 网页中生成浮窗广告
4. 修改DOM伪造登入表单
XSS 攻击有三种方式
*存储型XSS攻击
*反射型XSS攻击
*基于DOM的XSS攻击
17.1.1存储型XSS攻击
如图'存储型XSS攻击'
从图上看,存储型XSS攻击攻击大致步骤如下
1.首先黑客利用站点漏洞将恶意js代码提交到网站数据库中
2.然后用户向网站请求恶意js脚本页面
3.用户浏览页面,恶意脚本会将用户Cookie上传恶意服务器
常见场景:在评论区提交一份恶意脚本代码,前后端未做好转义工作,内容传到服务器
在页面渲染的时候就会直接执行,相当于一段未知的js代码,存储型XSS攻击
思考::如何前后端转义 恶意代码是什么样的
17.1.2 反射型XSS攻击
反射型XSS攻击指的是恶意脚本作为 网络请求的一部分 随后网站又把恶意的js脚本返回用户,当恶意js脚本在用户页面中被执行,黑客就可利用该脚本做一些恶意操作
举例子
http://suibian.com?query=<script>alert('xss攻击')</script>
如上,服务器拿到后解析参数query,最后将内容返回给浏览器,浏览器将这些内容作为HTML的一部分解析,发现是js脚本直接执行这样就被XSS攻击了
这也是 反射型名字的由来,将恶意脚本作为参数,通过网络请求,最后经过服务器,在反射到HTML文档中,执行解析
主要注意的就是,【服务器不会存储这些恶意的脚本,这也算是和存储型XSS攻击的区别吧】。
17.1.3 基于DOM的XSS攻击
基于DOM的XSS攻击是不牵涉到页面Web服务器的,具体来说,黑客通过各种手段将恶意脚本注入到用户的页面中,在数据传输的时候劫持网络数据包
常见的劫持手段有
WIFI路由器劫持
本地恶意软件
17.2 阻止XSS攻击策略
以上讲述XSS攻击原理,都有一个共同点,恶意脚本在浏览器执行
针对三种不同XSS攻击,有以下三种解决方法
17.2.1 对输入脚本进行过滤或转码
对用户输入的信息过滤或转码
比如上边那个,转码后: <
17.2.2 利用CSP
该安全策略的实现基于一个称作COntent-Security-Policy的HTTP首部、
可以移步MDN 有更加规范的解释
CSP,即是浏览器的内容安全策略,它的核心大概就是服务器决定浏览器加载哪些资源,具体来说有几个功能
*限制加载其它域下的资源文件,这样即使黑客插入了一个js文件,这个js文件也是无法被加载的
*禁止向第三方提交数据,这样用户数据也不会外泄
*提供上报机制,能帮助我们及时发现XSS攻击
*禁止执行内联脚本和未授权的脚本
17.2.3 利用HTTPOnly
由于很多XSS攻击都是盗用Cookie的,因此还可以通过使用HTTPOnly 属性来保护我们的Cookie安全,这样的话,js便无法读取Cookie的值,这样也很好地防范XSS攻击
通常服务器可以将某些Cookie设置为HTTPOnly 标志,HTTPOnly是服务器通过HTTP 响应头来设置,下面是打开Google ,HTTP响应头的中的一段
set-cookie: NID=189=M8l6-z41asXtm2uEwcOC5oh9djkffOMhWqQrlnCtOI
18-Apr-2020 06:52:22 GMT
17.3 总结
XSS攻击 是指浏览器中执行恶意脚本,然后拿到用户的信息进行操作,主要分为存储型,反射型,稳当型,防范的措施包括
*对输入内容过滤或者转码,尤其是类似于,<script> <img> <a> 标签
*利用CSP
*Cookie的HTTPOnly属性
除了以上策略之外,我们还可以通过添加验证码防止脚本冒充用户提交危险操作,而对于一些不受信任的输入,还可以限制其输入长度,这样可以增大XSS攻击的难度
18. CSRF攻击
18.1 CSRF 英文全称 Cross-site request forgery 所以又称为 '跨站请求伪造',是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求,简单来讲,【CSRF攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些事情】
一般情况下,点开一个诱导你的链接,黑客会在你不知道的时候做哪些事情
1) 自动发起GET请求
黑客网页里面有这样一段代码
<img src="http://bank.example/withdraw?amount=10000&for=hacker">
在受害者访问含有这个img的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker发出一次HTTP请求
bank.example就会收到包含受害者登录信息的一次跨域请求
2) 自动发起POST请求
黑客网页中有一个表单,自动提交的表单
<form action="http:bank.example/withdraw" method=POST>
<input type='hidden' name='account' value='xiaoming' />
<input type='hidden' name='account' value='10000' />
<input type='hidden' name='for' value='hacker' />
</form>
<script>document.form[0<.submit()/script>
访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作
同样也会携带相应的用户的cookie信息,让服务器误以为是一个正常的用户在操作,让各种恶意操作变为可能
3) 引诱用户点击链接
这种需要诱导用户去点击链接才会触发,这类的情况比如在论坛里发布照片,照片嵌套了恶意链接,或者是以广告形式去诱导
比如 <a href='http://test.com/csrf/withdraw.php?amoun=10000&for=hacker' target"_blank">重磅消息!!!<a/>
点击后 自动发送get请求,接下来和自动发起GET请求部分同理
以上三种情况就是CSRF攻击原理,跟XSS对比的话,CSRF攻击并不是需要将恶意代码注入HTML中,而是跳转新的页面,利用服务器的验证漏洞 和 用户之前的登录状态 来模拟用户进行操作
18.2 防护策略
其实我们可以想到,黑客只能借助受害者的 cookie 骗取服务器的信任,但是黑客并不能凭借拿到【cookie】 也看不到【cookie】的内容,对于服务器返回的结果,由于浏览器【同源策略】的限制,黑客也无法进行解析
这就是告诉我们,我们要保护的对象是哪些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行 CSRF 的保护,而保护的关键 是 【请求中房屋黑客所不能伪造的信息】
**用户操作限制-验证码机制
方法: 添加验证码来识别是不是用户主动去发送这个请求,由于一定强度的验证机器无法识别,因此危险网站不能伪造一个完整的请求
1. 验证来源站点
在服务器端验证请求来源的站点,由于大量的CSRF攻击来自第三方站点,因此服务器跨域禁止来自第三放站点的请求,主要通过HTTP请求头的两个Header
Origin Header
Referer Header
这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容
服务器可以通过解析这两个Header的域名,确定请求的来源域
其中 【Origin】包含域名信息,而 【Referer】包含了具体的URL路径
在某些情况下,这两者都是可以伪造的,通过AJAX中自定义请求头即可,
2. 利用Cookie的SameSite属性
可以看看MDN对此的解释
SameSite 可以设置为三个值,strict lax 和None
1. 在Strict 模式下,浏览器完全禁止第三方请求携带Cookie,比如请求sanyuan.com网站,只能在sanyuan.com域名当中请求才能携带Cookie 在其网站请求都不能
2. 在Lax模式就宽松一点了,但是只能在get方法提交表单,或者 a标签发送get请求的情况下可以携带Cookie其它情况均不能
3. 在None模式下,Cookie将在所有上下文中发送即允许跨域发送
3. 【CSRF TOKEN】
前面讲到CSRF的另一个特征是,攻击者无法直接窃取到用户信息(Cookie,Header网站内容等)仅仅是冒用Cookie中的信息
那么我们可以使用Token 在不涉及XSS的前提下,一般黑客很难拿到Token
可以看看这个文章,讲Token是怎么操作的
https://zhuanlan.zhihu.com/p/63061864
Token(令牌)作为Web领域验证身份是一个不错的选择,当然了,JWT有兴趣可以了解
Token步骤如下:
第一步:将CSRF Token输出到页面中」
首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了(XSS可能会获取Cookie),否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。
第二步:页面提交的请求携带这个Token
对于GET请求,Token将附在请求地址之后,这样URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上:
<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>
这样,就把Token以参数的形式加入请求了。
第三步:服务器验证Token是否正确
当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。、
参考链接
https://juejin.cn/post/6850037270729359367#heading-4
https://juejin.cn/post/6844903497599549453
https://juejin.cn/post/6844903986504417293
https://juejin.cn/post/6844903767226351623#heading-0
https://time.geekbang.org/column/article/138844
*/