前端整体知识点
1.前端优化性能的方法
- 减少请求数量
- 减少资源大小
- 优化网络连接
- 优化资源加载
- 减少重绘回流
- 使用性能更好的 API
- webpack 优化
减少请求数量
- 减少重定向
尽量避免使用重定向,当页面发生了重定向,就会延迟整个 HTML 文档的传输。在 HTML 文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载,降低了用户体验,
如果一定要使用重定向的话,如 http 重定向到 https,要使用 301 永久重定向,而不是 302 临时重定向,因为如果使用 302 则每一次访问 http 都会重定向到 https 页面,而永久重定向在第一次从 http 重定向到 https 之后,每次访问 http,会直接返回 https 的页面
- 使用缓存
使用 cache-control 或 expires 这类强缓存的时候,缓存不过期的情况下不会向服务器发起请求。强缓存过期的时候,会使用 last-modified 或 etag 这类协商缓存向服务器发起请求,如果资源没有变化,则服务器返回 304 响应,浏览器继续从本地缓存加载资源,如果资源更新了,则服务器将更新后的资源发送到浏览器,并返回 200
- 避免使用空的 src 和 href
a 标签设置空的 href,会重定向到当前页面的地址;form 设置空的 method,会提交表单到当前页面的地址
减少资源大小
- html 压缩
html 代码压缩就是压缩在文本文件中有意义,但是在 html 中不显示的字符,包括空格,制表符
- css 压缩
css 压缩包括无效代码删除与 css 语义合并
- js 压缩与混乱
js 压缩与混乱包括无效字符及注释的删除、代码语义的缩减和优化、降低代码的可读性、实现代码的保护
- 图片压缩
优化网络连接
- 使用 CDN
CDN 是内容分发网络,它能够实时地根据网络流量和各个节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上,其目的是使用户可以就近的取得所需内容,解决网络拥挤的状况,提高网站的响应速度
- 使用 DNS 预解析
当浏览器访问一个域名的时候,需要解析一次 DNS,获得对应域名的 ip 地址,在解析过程中,按照浏览器缓存、系统缓存、路由器换算、DNS 缓存、域名服务器的顺序,逐步读取缓存,直到拿到 ip 地址
- 持久连接
使用 keep-alive 或者 persistent 来建立持久连接,降低了延时和连接建立的开销
优化资源加载
- 资源加载位置
通过优化资源加载位置,更改资源加载时机,使尽可能快地展示出页面内容,尽可能快地使用功能可用
- css 文件放在 head 中,先外链,后本页
- js 文件放在 body 底部,先外连,后本页
- 处理页面、处理页面布局的 js 文件放在 head 中,如 babel-polyfill.js 文件、flexible.js 文件
- body 中尽量不写 style 标签和 script 标签
- 资源加载时机
- 异步 script 标签 defer:异步加载,在 html 解析完成后执行。defer 的实际效果与将代码放在 body 底部类似 async:异步加载,加载完成后立即执行
- 模块按需加载 在 SPA 等业务比较复杂的系统中,需要根据路由来加载当前页面所需要的业务模块 按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积 webpack 提供了两类技术,优先选择的方式是使用符合 ECMAScript 提案的 import 语法,第二种就是使用 webpack 特定的 require.ensure
- 使用资源预加载 preload 和资源预读取 prefetch
- preload 让浏览器提前加载指定资源,需要执行时候再执行,可以加快当前页面的加载速度
- prefetch 告诉浏览器加载下一个页面可能会用到的资源,可以加速下一个页面的加载速度
- 资源懒加载与资源预加载
- 资源延迟加载也称为资源懒加载,延迟加载资源或符合某些条件的时候才加载某些资源
- 资源预加载是提前加载用户所需的资源,保证良好的用户体验
资源懒加载和资源预加载都是一种错峰操作,在浏览器忙碌的时候不能操作,浏览器空闲的时候再加载资源,优化了网络性能
减少重绘回流
性能更好的 API
- 用对选择器
- id 选择器(#myid)
- 类选择器(.myclassname)
- 标签选择器(div,h1,p)
- 相邻选择器(h1+p)
- 子选择器(ul > li)
- 后代选择器(li a)
- 通配符选择器(*)
- 属性选择器(a[rel=“external”])
- 伪类选择器(a:hover,li:nth-child)
- 使用 requestAnimationFrame 来替代 setTimeout 和 setInterval
希望在每一帧开始的时候对页面进行更改,requestAnimationFrame 就是告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,使用 setTimeout 或者 setInterval 来触发更新页面的函数,该函数可能在一帧的中间或者结束的时间点上调用,进而导致该帧后面需要进行的事情没有完成,引发丢帧
- 使用 IntersectionObserver 来实现图片可视区域的懒加载
传统的做法中,需要使用 scroll 事件,并调用 getBoundingClientRect 方法,来实现可视区域的判断,即使使用了函数节流,也会造成页面回流。使用 IntersectionObserver,则没有上述问题
webpack 性能优化
- 打包公共代码
使用 CommonChunkPlugin 插件,将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存区中供后续使用,这回带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中抽取出来,而不是每次访问一个页面的时候,都需要去加载一个很大的文件
webpack 4 将移除 CommonsChunkPlugin, 取而代之的是两个新的配置项 optimization.splitChunks 和 optimization.runtimeChunk
通过设置 optimization.splitChunks.chunks: “all” 来启动默认的代码分割配置项
- 动态导入和按需加载
webpack 提供了两种技术通过模块内联函数用来分离代码,优先选择的方式是 ECMAScript 提案的 import()语法,第二种则是使用 webpack 特定的 require.ensure
- 删除无用的代码
tree shaking 是一个术语,通常用于移除 Javascripy 上下文中的未引用代码,它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export,js 的 tree shaking 主要通过 uglifyjs 来完成,css 的 tree shaking 通过 purify css 来实现
- 长缓存优化
- 将 hash 替换成 chunkhash,这样当 chunk 不变的时候,缓存依然有效
- 使用 Name 而不是 id
每个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变
下面来使用两个插件解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建
- 公共代码内联
使用 html-webpack-inline-chunk-plugin 插件将 manifest.js 内联到 html 文件中
原因:
- 减少 HTTP 请求
每个 HTTP 请求都会带来一定的开销,包括 DNS 查询、TCP 连接建立、TLS 握手(如果使用 HTTPS)等。通过将 manifest.js
内联到 HTML 中,可以减少一次 HTTP 请求,从而减少这些开销,提高页面加载速度。
- 加快首屏渲染
manifest.js
通常包含 Webpack 的运行时代码和模块映射信息,这些信息对于正确加载和执行后续的 JavaScript 资源是必要的。将其内联到 HTML 中可以确保在 HTML 被解析时立即可用,从而加快了首屏渲染时间。
- 缓存优化
当 manifest.js
被内联到 HTML 中,它会随着 HTML 一起被缓存。这样,当 HTML 文件更新时,manifest.js
也会同步更新,避免了由于缓存问题导致的旧的 manifest.js
与新的 JavaScript 资源不匹配的问题。
- 避免阻塞渲染
外部 JavaScript 文件会阻塞 HTML 的解析和渲染,直到该文件被下载并执行完毕。将 manifest.js
内联到 HTML 中,可以避免这种阻塞,使浏览器能够更快地解析和渲染页面内容,从而提高用户体验。
- 提高代码执行的优先级
内联的脚本会在浏览器解析 HTML 时立即执行,这意味着 manifest.js
中的代码会在最早期就被执行。这对于 Webpack 的运行时代码来说非常重要,因为它需要尽早初始化,以便其他模块能够正常加载和执行。
- 更好的缓存控制
通过内联 manifest.js
,你可以更好地控制缓存策略。例如,如果你的 HTML 文件设置了适当的缓存策略(如较短的过期时间或使用 ETag),那么每次更新时都能确保用户获取到最新版本,而不必担心缓存问题导致的不一致性。
2.对跨域的理解
同源策略及跨域问题
同源策略是一套浏览器安全机制,当一个源的文档和脚本,与另一个源的资源进行通信时,同源策略就会对这个通信做出不同程度的限制。
简单来说,同源策略对 同源资源 放行,对 异源资源 限制
因此限制造成的开发问题,称之为跨域(异源)问题
同源和异源
源(origin) = 协议 + 域名 + 端口
例如:
https://study.duyiedu.com/api/movie
的源为 https://study.duyiedu.com
http://localhost:7001/index.html
的源为 http://localhost:7001
两个 URL 地址的源完全相同,则称之为同源,否则称之为异源(跨域)
跨域出现的场景
跨域可能出现在三种场景:
网络通信
- a 元素的跳转;加载 css、js、图片等;AJAX 等等
JS API
window.open
、window.parent
、iframe.contentWindow
等等
存储
WebStorage
、IndexedDB
等等
对于不同的跨域场景,以及每个场景中不同的跨域方式,同源策略都有不同的限制。
浏览器如何限制异源请求?
浏览器出于多方面的考量,制定了非常繁杂的规则来限制各种跨域请求,但总体的原则非常简单:
- 对标签发出的跨域请求轻微限制
- 对 AJAX 发出的跨域请求严厉限制
解决方案
CORS
CORS(Cross-Origin Resource Sharing)是最正统的跨域解决方案,同时也是浏览器推荐的解决方案。
CORS 是一套规则,用于帮助浏览器判断是否校验通过。
CORS 的基本理念是:
- 只要服务器明确表示允许,则校验通过
- 服务器明确拒绝或没有表示,则校验不通过
所以,使用 CORS 解决跨域,必须要保证服务器是「自己人」
请求分类
CORS 将请求分为两类:简单请求和预检请求。
对不同种类的请求它的规则有所区别。
所以要理解 CORS,首先要理解它是如何划分请求的。
简单请求
完整判定逻辑:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
简单来说,只要全部满足下列条件,就是简单请求:
- 请求方法是
GET
、POST
、HEAD
之一 - 头部字段满足 CORS 安全规范,详见 W3C
- 浏览器默认自带的头部字段都是满足安全规范的,只要开发者不改动和新增头部,就不会打破此条规则
- 如果有
Content-Type
,必须是下列值中的一个text/plain
multipart/form-data
application/x-www-form-urlencoded
预检请求(preflight)
只要不是简单请求,均为预检请求
对简单请求的验证
对预检请求的验证
- 发送预检请求
- 发送真实请求(和简单请求一致)
细节 1 - 关于 cookie
默认情况下,ajax 的跨域请求并不会附带 cookie,这样一来,某些需要权限的操作就无法进行
不过可以通过简单的配置就可以实现附带 cookie
// xhr
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// fetch api
fetch(url, {
credentials: "include",
});
这样一来,该跨域的 ajax 请求就是一个附带身份凭证的请求
当一个请求需要附带 cookie 时,无论它是简单请求,还是预检请求,都会在请求头中添加 cookie
字段
而服务器响应时,需要明确告知客户端:服务器允许这样的凭据
告知的方式也非常的简单,只需要在响应头中添加:Access-Control-Allow-Credentials: true
即可
对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。
另外要特别注意的是:**对于附带身份凭证的请求,服务器不得设置 ***Access-Control-Allow-Origin 的值为**。这就是为什么不推荐使用*的原因
细节 2 - 关于跨域获取响应头
在跨域访问时,JS 只能拿到一些最基本的响应头,如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。
Access-Control-Expose-Headers
头让服务器把允许浏览器访问的头放入白名单,例如:
Access-Control-Expose-Headers: authorization, a, b
这样 JS 就能够访问指定的响应头了。
JSONP
在很久很久以前...并没有 CORS 方案
在那个年代,古人靠着非凡的智慧来解决这一问题
虽然可以解决问题,但 JSONP 有着明显的缺陷:
- 仅能使用 GET 请求
- 容易产生安全隐患
- 恶意攻击者可能利用
callback=恶意函数
的方式实现XSS
攻击
- 容易被非法站点恶意调用
因此,除非是某些特殊的原因,否则永远不应该使用 JSONP
Proxy
代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击
方案一 -- webpack
如果是通过 vue-cli
脚手架工具搭建项目,我们可以通过 webpack
为我们起一个本地服务器作为请求的代理对象
通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果 web 应用和接口服务器不在一起仍会跨域
在 vue.config.js
文件,新增以下代码
amodule.exports = {
devServer: {
host: "127.0.0.1",
port: 8084,
open: true, // vue项目启动时自动打开浏览器
proxy: {
"/api": {
// '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
target: "http://xxx.xxx.xx.xx:8080", //目标地址,一般是指后台服务器地址
changeOrigin: true, //是否跨域
pathRewrite: {
// pathRewrite 的作用是把实际Request Url中的'/api'用""代替'^/api': ""
},
},
},
},
};
通过 axios
发送请求中,配置请求的根路径
axios.defaults.baseURL = "/api";
方案二 --服务端实现代理请求转发
此外,还可通过服务端实现代理请求转发
以 express
框架为例
var express = require('express');const proxy = require('http-proxy-middleware')const app = express()
app.use(express.../public/point(__dirname + '/'))
app.use('/api', proxy({ target: 'http://localhost:4000', changeOrigin: false}));
module.exports = app
方案三 --Nginx 反向代理
通过配置 nginx
实现代理
server {
listen 80;
# server_name www.josephxia.com;
location / {
root /var/www/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;}
location /api {
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}