Skip to content

浏览器 ​

特性cookielocalStoragesessionStorageindexDB
数据生命周期⼀般由服务器生成, 可以设置过期时间除非被清理,否则⼀直存在页面关闭就清理除非被清理, 否则⼀直存在
数据存储大小4K5M5M无限
与服务端通信每次都会携带在 header 中,对于请求性能影响不参与不参与不参与

从上表可以看到, cookie 已经不建议用于存储 。如果没有大量数据存储需求的话, 可以使用 localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储, 否则可以用 sessionStorage 存储。

对于 cookie ,我们还需要注意安全性

属性作用
value如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识
http-only不能通过 JS 访问 Cookie ,减少 XSS 攻击
secure只能在协议为 HTTPS 的请求中携带
same-site规定浏览器不能在跨域请求中携带 Cookie ,减少 CSRF 攻击

2. 怎么判断页面是否加载完成? ​

  1. Load 事件触发代表页面中的 DOM , CSS , JS , 图片已经全部加载完毕 。
  2. DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS , JS , 图片加载

3. 如何解决跨域 ​

因为浏览器出于安全考虑,有同源策略 。也就是说, 如果协议 、域名或者端⼝有⼀个不同就是跨域, Ajax 请求会失败。

我们可以通过以下⼏种常用方法解决跨域的问题

  1. JSONP

    JSONP 的原理很简单,就是利用 <script> 标签没有跨域限制的漏洞 。通过 <script> 标签指向⼀个需要访问的地址并提供⼀个回调函数来接收数据当需要通讯时

html
<script src=" http: / / domain/ api? param1 = a& param2 = b& callback= jsonp"></script>
<script>
  function jsonp(data) {
    console.log(data);
  }
</script>

JSONP 使用简单且兼容性不错,但是只限于 get 请求

在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的, 这时候就需要自己封装⼀个 JSONP , 以下是简单实现:

js
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);
});
  1. CORS

    • CORS 需要浏览器和后端同时支持 。 IE 8 和 9 需要通过 XDomainRequest 来实现。
    • 浏览器会自动进⾏ CORS 通信, 实现 CORS 通信的关键是后端 。只要后端实现了 CORS ,就实现了跨域。
    • 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS 。 该属性表示哪些域名可以访问资源, 如果设置通配符则表示所有网站都可以访问资源。
  2. document.domain

    • 该方式只能用于二级域名相同的情况下, 比如 a.test.com 和 b.test.com 适用于该方式。
    • 只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域
  3. postMessage

这种方式通常用于获取嵌⼊页面中的第三方页面数据 。⼀个页面发送消息, 另⼀个页面判断来源并接收消息

js
// 发送消息端
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. 什么是事件代理 ​

如果⼀个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上

html
<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

目前该技术通常用来做缓存文件,提高首屏速度, 可以试着来实现这个功能

js
// index.js
if (navigator.serviceWorker) {
  navigator.serviceWorker
    .register("sw.js")
    .then(function (registration) {
      console.log("service worker 注册成功");
    })
    .catch(function (err) {
      console.log("servcie worker 注册失败");
    });
}
js
// 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

js
Expires: Wed, 22 Oct 2018 08:41:00 GMT

Expires 是 HTTP / 1.0 的产物,表示资源会在 Wed , 22 Oct 2018 08:41:00 GMT 后过期, 需要再次请求 。并且 Expires 受限于本地时间, 如果修改了本地时间, 可能会造成缓存失效

js
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) ​

  1. 重绘和回流是渲染步骤中的⼀⼩节,但是这两个步骤对于性能影响很大。
  2. 重绘是当节点需要更改外观而不会影响布局的, 比如改变 color 就叫称为重绘
  3. 回流是布局或者⼏何属性需要改变就称为回流。
  4. 回流必定会发生重绘, 重绘不⼀定会引发回流 。回流所需的成本比重绘高的多, 改变深层次的节点很可能导致父节点的⼀系列回流。

所以以下几个动作可能会导致性能问题:

  • 改变 window 大⼩
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

很多人不知道的是,重绘和回流其实和 Event loop 有关

  • 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新 。- 因为浏览器是 60Hz 的刷新率,每 16ms 才会更新⼀次。
  • 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发⼀次, 并且自带节流功能。
  • 判断是否触发了 media query
  • 更新动画并且发送事件
  • 判断是否有全屏操作事件
  • 执行 requestAnimationFrame 回调
  • 执行 IntersectionObserver 回调,该方法用于判断元素是否可见, 可以用于懒加载上, 但是兼容性不好
  • 更新界面
  • 以上就是⼀帧中可能会做的事情 。如果在⼀帧中有空闲时间,就会去执行 requestIdleCallback 回调。

减少重绘和回流 ​

  1. 使用 translate 替代 top
html
<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>
  1. 使用 visibility 替换 display: none , 因为前者只会引起重绘,后者会引发回流( 改变了布局)
  2. 把 DOM 离线后修改, 比如:先把 DOM 给 display:none (有⼀次 Reflow ),然后你修改 100 次,然后再把它显示出来
  3. 不要把 DOM 结点的属性值放在⼀个循环里当成循环里的变量
js
for (let i = 0; i < 1000; i++) {
  // 获取 offsetTop 会导致回流, 因为需要去获取正确的值
  console.log(document.querySelector(".test").style.offsetTop);
}
  1. 不要使用 table 布局, 可能很⼩的⼀个⼩改动会造成整个 table 的重新布局 动画实现的速度的选择, 动画速度越快, 回流次数越多,也可以选择使用 requestAnimationFrame
  2. CSS 选择符从右往左匹配查找, 避免 DOM 深度过深
  3. 将频繁运行的动画变为图层, 图层能够阻止该节点回流影响别的元素 。比如对于 video 标签, 浏览器会自动将该节点变为图层。

CDN ​

静态资源尽量使用 CDN 加载, 由于浏览器对于单个域名有并发请求上限, 可以考虑使用多个 CDN 域名 。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同, 否则每次请求都会带上主站的 Cookie

使用 Webpack 优化项目 ​

  1. 对于 Webpack4 ,打包项目使用 production 模式, 这样会自动开启代码压缩
  2. 使用 ES6 模块来开启 tree shaking , 这个技术可以移除没有使用的代码
  3. 优化图片,对于⼩图可以使用 base64 的方式写⼊文件中
  4. 按照路由拆分代码, 实现按需加载