1.事件机制
事件触发的三个阶段:
- window往事件触发处传播,遇到注册的捕获事件会触发
- 传播到事件触发处时触发注册的事件
- 从事件触发处往window传播,遇到注册的冒泡事件会触发
事件触发一般来说会按照上述顺序执行,但是如果给一个body中的子节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。
node.addEventListener('click',event => {
console.log('冒泡')
},false)
node.addEventListener('click',event => {
console.log('捕获')
},true)
上述代码会先打印冒泡
注册事件
通常使用addEventListener来注册事件,该函数的第三个参数useCapture可以是布尔值也可以是对象,对于布尔值来说默认是false,即表示冒泡,如果设置为true则表示捕获。对于对象参数,有以下几个属性:
- capture:布尔值,和useCapture作用一样
- once:布尔值,值为true表示该回调只会调用一次,调用后会移除监听
- passive:布尔值,表示永远不会调用preventDefault(阻止浏览器默认事件)
一般来说如果我们只希望事件触发在目标上,可以使用stopPropagation
来阻止事件的进一步传播,既可以阻止事件冒泡,也可以阻止事件捕获;stopImmediatePropagation
同样可以实现阻止事件,但是还可以阻止该事件目标指向别的注册事件(addEventListener可以绑定多个事件,设置了该参数,其他绑定的事件就不会执行)。
事件代理
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上。可以利用事件冒泡,对父节点设置一个事件,在父节点中判断是哪个子元素发生事件并进行操作。优点:
- 节省内存
- 不需要给子节点注销事件
2.跨域
什么是跨域:如果协议、域名和端口有一个不同就是跨域,Ajax请求就会失败
为什么浏览器要使用同源策略:主要是用来防止CSRF攻击的,CSRF利用用户的登录态发起恶意请求。
请求跨域了,但是请求还是发出去了,只是被浏览器拦截了,因此跨域并不能完全阻止CSRF,跨域是为了阻止用户读取到另一个域名下的内容,Ajax可以获取响应,浏览器认为不安全所以拦截了请求。
解决跨域的几种方法:
(1)JSONP
利用<script>
标签没有跨域限制的漏洞,通过该标签指向一个需要访问的地址并提供一个回调函数来接收数据。
<script src='http://domian/api?param1=a&callback=jsonp'></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
优缺点:JSONP兼容性好,但是只限于GET请求
当遇到多个JSONP请求的回调函数名相同时,可以自己封装一个JSONP:
function jsonp(url,jsonpCallback,success) {
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript'
window[jsonCallback] = function(data) {
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxxx',callback,function(value)) {
console.log(value)
}
(2)CORS
CORS需要浏览器和后端同时支持。IE8和9需要通过XDomainRequest来实现。浏览器会自动进行CORS通信,实现CORS通信的关键是后端。服务端设置Access-Control-Allow-Origin
就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。通过该方式解决跨域问题会在发送请求时出现两种情况,分别为简单请求和复杂请求:
触发简单请求:
(1)使用GET、HEAD、POST请求
(2)Content-Type的值仅限于text/plain
、multipart/form-data
、application/x-www-form-urlencoded
请求中的任意XMLHttpRequestUpload
对象均没有注册任何事件监听器;
tip:XMLHttpRequestUpload
可以使用XMLHttpRequest.upload
属性访问
触发复杂请求:
不符合简答请求的就是复杂请求
(3)document.domain
该方法只能用于二级域名相同的情况下,比如a.test.com
和b.test.com
只需要给页面添加document.domian='test.com'
表示二级域名相同就可以实现跨域
(4)postMessage
该方式通常用于获取页面中的第三方页面数据,一个页面发送消息,另一个页面判断来源并接收消息
window.parent.postMessage('message','http://test.com')
//接收消息
var mc = new MessageChannel
mc.addEventListener('message',event => {
var origin = event.origin || event.originalEvent.origin
if(origin === 'http://test.com') {
console.log('验证通过')
}
})
3.存储
特性 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否者一直存在 |
数据存储大小 | 4K | 5M | 5M | 无限 |
与服务端通信 | 每次都会携带在header中,对于请求性能影响 | 不参与 | 不参与 | 不参与 |
由上图可知:cookie
已不建议用于存储。如果没有大量数据存储需求,可以使用localStorage
和sessionStorage
。对于不怎么改变的数据尽量使用localStorage
存储,否则可以用sessionStorage
存储
对于cookie
来说,我们需要注意:
属性 | 作用 |
---|---|
value | 如果用于保存用户登录状态,应该将该值加密,不能使用明文的用户标志 |
http-only | 不能通过JS访问Cookie,减少XSS攻击 |
secure | 只能在协议为HTTPS的请求中携带 |
same-site | 规定浏览器不能在跨域请求中携带Cookie,减少CORF攻击 |
Service Worker
Service Worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用Service Worker的话,传输协议必须是HTTPS,因为其涉及请求拦截,所以必须使用HTTPS保证安全。Service Worker实现缓存一般分为三个步骤:首先要先注册Service Worker,然后监听到install
事件后就可以缓存需要的文件,那么下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求。
//index.js
if(navigator.serviceWorker) {
navigator.serviceWorker
.register('sw.js')
.then(function(registration) {
console.log('server worker注册成功')
})
.catch(function(err) {
console.log('service worker 注册失败')
})
}
//sw.js
//监听'install'事件,回调中缓存所需文件
self.addEventListener('install',e => {
e.waitUntil(
caches.open('my-cache').then(function(cache) {
return cache.addAll(['./index.html','./index.js'])
})
)
})
//拦截所有请求事件
//如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener('fetch',e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if(response) {
return response
}
console.log('fetch source')
})
)
})
4.浏览器缓存机制
缓存的优点:
- 减少了不必要的数据传输,节省带宽
- 减少服务器的负担,提升网站性能
- 加快了客户端加载网页的速度
- 用户体验友好
缺点:
- 资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕。
按缓存位置分类
有优先级(依照下列顺序),只有当下列缓存都没有命中时才会请求网络:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 网络请求
(1).Service Worker
与浏览器其他内建缓存机制不同,其可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存、并且缓存是持续性的。其不会被清除除非手动调用cache.delete(resource)或者容量超过限制。
如果Service Worker没有命中,会向后去寻找,但是不管我们是从Memory Cache中还是从网络请求中获取数据,浏览器都会显示我们是从Service Worker中获取的内容
(2).Memory Cache
Memory Cache是将数据存储在内存中,读取速度快,但是缓存持续时间短,会随进程的释放而释放
(3).disk cache
也称HTTP cache,最长用的,强制缓存,对比缓存以及Cache-Control
都为该类。数据存储在硬盘中,因此是持久存储,读取速度相对慢,但是存储数据量大,可以跨域,是根据HTTP Header中的字段来判断哪些资源需要缓存
(4).Push Cache
是HTTP2中的内容,缓存时间很短暂,只在会话(session)中存在,一旦会话结束就会被释放。
(5).网络请求
所以缓存都没有被命中就会发起网络请求。
按照缓存策略划分
(1).强制缓存
当客户端请求后,会先访问缓存数据库是否存在,如果存在则直接返回,不存在则请求真的服务器,响应后在写入缓存数据库。强制缓存直接减少请求数,是提升最大的缓存策略。
可以通过设置HTTP Header实现:
Expires(HTTP/1)
Expires: Thu, 10 Nov 2021 08:45:11 GMT
表示资源会在Thu, 10 Nov 2021 08:45:11 GMT
后过期,但是,Expires受限于本地时间,如果修改了本地时间,可能会导致缓存失效;写法复杂,容易写错
Cache-Control(HTTP/1.1)
优先级高于Expries
指令 | 作用 |
---|---|
public | 表示所有内容可以被客户端和代理服务器缓存 |
private | 表示响应只可以被客户端缓存 |
max-age=30 | 最大有效时间为30秒,过期后需要重新缓存 |
s-maxage=30 | 覆盖max-age,作用一样,只在代理服务器中有效 |
must-revalidate | 如果超过了max-age的时间,在成功向原始服务器验证之前,缓存不能用该资源响应后续请求 |
no-cache | 虽然字面意思是不要缓存,但是资源还是被缓存了只是缓存后立即失效,下次会发起请求验证资源是否过期 |
no-store | 真正意义上的不缓存,不缓存任何响应 |
max-stale=30 | 30秒内即使缓存过期也是用该缓存 |
min-fresh=30 | 希望在30秒内获取最新的响应 |
Cache-Control可以在请求头或者响应头中设置,并且可以组合使用多种指令:
总结:自从HTTP/1.1开始,Cache-control开始取代Expires。Cache-control是一个相对时间,即使客户端时间发生改变,相对时间也不会随之改变,可以保证服务器和客户端的时间一致性,且Cache-control的可配置性比较强大。
(2)对比缓存(协商缓存)
当强缓存失效(超过规定时间时)就需要使用对比缓存,有服务器决定缓存内容是否失效。
对比缓存在请求数上和没有缓存是一致的,在响应体体积上的节省是它的优化点
对比缓存有两种字段,Last-Modified
和ETag
,可以通过设置两种HTTP Header实现:
(1)Last-Modified&If-Modified-Since
Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
Last-Modified表示本地文件最后修改日期,If-Modified-Since会将Last-Modified的值发送给服务器,如果有更新就会返回新的资源,否则返回304。
缺点:
- Last-Modified只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源。
- 如果本地打开缓存文件,计时没有对文件进行修改也会造成Last-Modified被修改,服务端不能命中缓存导致发送相同的内容
(2)ETag和If-None-Match
基于上述字段的问题,HTTP/1.1版本新增该字段。ETag存储的是文件的特殊标识(一般是hash生成的),服务器存储这文件的ETag字段,之后的流程和Last-Modified一致,只是Last-Modified和If-Modified-Since被ETag和If-None-Match替代了。ETag的优先级高于If-Modified
tip:如果没有设置缓存策略,浏览器会采用一个启发式算法,通常会取响应头中的Date减去Last-Modified值的10%作为缓存时间
具体应用场景下的缓存策略
(1)频繁变动的资源
首先使用Cache-Control: no-cache使浏览器每次都请求服务器,然后配合ETag或者Last-Modified来验证资源是否有效。虽然不能节省请求数量,但是可以显著减少响应数据大小
(2)代码文件
因为HTML文件一般不缓存或缓存时间很短,所以特指HTML之外的代码文件。因为一般都会使用工具打包文件,那么我们可以对文件名进行哈希处理,只有当代码秀阿贵后才会生成新的文件名,基于此,可以给代码文件设置缓存有效期为一年Cache-Control: max-age=31536000,这样只有当HTML文件中引入的文件名发生改变才会去下载最新的代码文件。