1 cookie和localSrorage、session、indexDB 的区别
从上表可以看到, cookie 已经不建议⽤于存储。如果没有⼤量数据存储需求的话,可以使⽤ localStorage 和 sessionStorage
。对于不怎么改变的数据尽量使⽤ localStorage 存储,否则可以⽤ sessionStorage 存储。
对于 cookie ,我们还需要注意安全性
2 怎么判断⻚⾯是否加载完成?
- Load 事件触发代表⻚⾯中的 DOM , CSS , JS ,图⽚已经全部加载完毕。
- DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待CSS , JS ,图⽚加载
3 如何解决跨域
因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端⼝有⼀个不同就是跨域, Ajax 请求会失败。
我们可以通过以下⼏种常⽤⽅法解决跨域的问题
JSONP
JSONP 的原理很简单,就是利⽤ <script> 标签没有跨域限制的漏洞。通过<script> 标签指向⼀个需要访问的地址并提供⼀个回调
函数来接收数据当需要通讯时
<script src="http://domain/api?param1=a¶m2=b&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[jsonpCallback] = function(data) {
success && success(data);
};
document.body.appendChild(script);
}
jsonp(
"http://xxx",
"callback",
function(value) {
console.log(value);
}
);
CORS
- ORS 需要浏览器和后端同时⽀持。 IE 8 和 9 需要通过 XDomainRequest 来实现。
- 浏览器会⾃动进⾏ CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了CORS ,就实现了跨域。
- 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS 。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有⽹站都可以访问资源。
document.domain
- 该⽅式只能⽤于⼆级域名相同的情况下,⽐如 a.test.com 和 b.test.com 适⽤于该⽅式。
- 只需要给⻚⾯添加 document.domain = ‘test.com’ 表示⼆级域名都相同就可以实现跨域
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('验证通过')
}
});
4 什么是事件代理
如果⼀个节点中的⼦节点是动态⽣成的,那么⼦节点需要注册事件的话应该注册在⽗节点上
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
- 事件代理的⽅式相对于直接给⽬标注册事件来说,有以下优点
- 节省内存
- 不需要给⼦节点注销事件
5 Service worker
Service workers 本质上充当Web应⽤程序与浏览器之间的代理服务器,也可以在⽹络可⽤时作为浏览器和⽹络间的代理。它们旨在
(除其他之外)使得能够创建有效的离线体验,拦截⽹络请求并基于⽹络是否可⽤以及更新的资源是否驻留在服务器上来采取适当的
动作。他们还允许访问推送通知和后台同步API
⽬前该技术通常⽤来做缓存⽂件,提⾼⾸屏速度,可以试着来实现这个功能
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function(registration) {
console.log("service worker 注册成功");
})
.catch(function(err) {
console.log("servcie 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");
})
);
});
打开⻚⾯,可以在开发者⼯具中的 Application 看到 Service Worker 已经启动了
6 浏览器缓存
缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提⾼⽹⻚的整体加载速度。
- 通常浏览器缓存策略分为两种:强缓存和协商缓存。
强缓存
实现强缓存可以通过两种响应头实现: Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求, state code 为 200
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Expires 是 HTTP / 1.0 的产物,表示资源会在 Wed , 22 Oct 2022 08:41:00 GMT 后过期,需要再次请求。并且 Expires 受
限于本地时间,如果修改了本地时间,可能会造成缓存失效。
- Cache-control: max-age=30
- Cache-Control 出现于 HTTP / 1.1 ,优先级⾼于 Expires 。该属性表示资源会在30 秒后过期,需要再次请求。
协商缓存
- 如果缓存过期了,我们就可以使⽤协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304 。
- 协商缓存需要客户端和服务端共同实现,和强缓存⼀样,也有两种实现⽅式
Last-Modified 和 If-Modified-Since
- Last-Modified 表示本地⽂件最后修改⽇期, If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该⽇期后资源是否有更新,有更新的话就会将新的资源发送回来。
- 但是如果在本地打开缓存⽂件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1出现了 ETag
ETag 和 If-None-Match
ETag 类似于⽂件指纹, If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发
送回来。并且ETag 优先级⽐ Last-Modified ⾼
选择合适的缓存策略
对于⼤部分的场景都可以使⽤强缓存配合协商缓存解决,但是在⼀些特殊的地⽅可能需要选择特殊的缓存策略
- 对于某些不需要缓存的资源,可以使⽤ Cache-control: no-store ,表示该资源不需要缓存
- 对于频繁变动的资源,可以使⽤ Cache-Control: no-cache 并配合 ETag 使⽤,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。
- 对于代码⽂件来说,通常使⽤ Cache-Control: max-age=31536000 并配合策略缓存使⽤,然后对⽂件进⾏指纹处理,⼀旦⽂件名变动就会⽴刻下载新的⽂件
7 浏览器性能问题
重绘(Repaint)和回流(Reflow)
- 重绘和回流是渲染步骤中的⼀⼩节,但是这两个步骤对于性能影响很⼤。
- 重绘是当节点需要更改外观⽽不会影响布局的,⽐如改变 color 就叫称为重绘
- 回流是布局或者⼏何属性需要改变就称为回流。
- 回流必定会发⽣重绘,重绘不⼀定会引发回流。回流所需的成本⽐重绘⾼的多,改变深层次的节点很可能导致⽗节点的⼀系列回流。
所以以下⼏个动作可能会导致性能问题:
- 改变 window ⼤⼩
- 改变字体
- 添加或删除样式
- ⽂字改变
- 定位或者浮动
- 盒模型
很多⼈不知道的是,重绘和回流其实和 Event loop 有关。
- 当 Event loop 执⾏完 Microtasks 后,会判断 document 是否需要更新。- 因为浏览器是 60Hz 的刷新率,每 16ms 才会更新⼀次。
- 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和scroll 事件也是⾄少 16ms 才会触发⼀次,并且⾃带节流功能。
- 判断是否触发了 media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执⾏ requestAnimationFrame 回调
- 执⾏ IntersectionObserver 回调,该⽅法⽤于判断元素是否可⻅,可以⽤于懒加载上,但是兼容性不好
- 更新界⾯
- 以上就是⼀帧中可能会做的事情。如果在⼀帧中有空闲时间,就会去执⾏requestIdleCallback 回调。
减少重绘和回流
使⽤ translate 替代 top
<div class="test"></div>
<style>
.test {
position: absolute;
top: 10px;
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
setTimeout(() => {
// 引起回流
document.querySelector('.test').style.top = '100px'
}, 1000)
</script>
-
使⽤ visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
-
把 DOM 离线后修改,⽐如:先把 DOM 给 display:none (有⼀次 Reflow ),然后你修改 100 次,然后再把它显示出来
-
不要把 DOM 结点的属性值放在⼀个循环⾥当成循环⾥的变量
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
- 不要使⽤ table 布局,可能很⼩的⼀个⼩改动会造成整个 table 的重新布局 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使⽤requestAnimationFrame
- CSS 选择符从右往左匹配查找,避免 DOM 深度过深
- 将频繁运⾏的动画变为图层,图层能够阻⽌该节点回流影响别的元素。⽐如对于 video标签,浏览器会⾃动将该节点变为图层。
CDN
静态资源尽量使⽤ CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使⽤多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie
使⽤ Webpack 优化项⽬
- 对于 Webpack4 ,打包项⽬使⽤ production 模式,这样会⾃动开启代码压缩
- 使⽤ ES6 模块来开启 tree shaking ,这个技术可以移除没有使⽤的代码
- 优化图⽚,对于⼩图可以使⽤ base64 的⽅式写⼊⽂件中
- 按照路由拆分代码,实现按需加载