跨域
跨域本质是浏览器基于同源策略的一种安全手段
同源策略
浏览器的安全功能,协议、域名、端口一致则是同源,非同源的限制:
- 无法通过 XMLHttpRequest 或 fetch 发起跨域请求
- 无法访问不同源的文档的 DOM
- 无法读取其他域的 Cookie、Storage
同源策略豁免情况
下面的情况可以不受同源策略影响:
<script src="CDN地址">
:因为绕过了同源策略,所以需要完全信任脚本来源才引入<img src="跨域地址">
<link href="跨域CSS">
<video src="跨域视频">
简单请求
- 使用下列方法之一:
GET
、POST
、HEAD
- 标头字段自定义的只能有:
Accept
、Accept-Language
、Content-Type
、Content-Language
、Range
Content-Type
所指定的媒体类型的值仅限于下列三者之一:text/plain
、multipart/form-data
、application/x-www-form-urlencoded
针对简单请求,浏览器不会拦截。但是非简单请求,浏览器会发送 预检请求(OPTIONS
)
解决跨域方法
1.CORS (Cross-Origin Resource Sharing,跨域资源共享)
服务端设置响应头:Access-Control-Allow-Origin
,来允许或限制资源的跨域访问
2.代理服务器
在服务器端设置代理,将前端请求先发送到同源的后端服务器,然后由后端服务器转发请求到目标服务器
3.JSONP
通过创建 <script>
标签来请求跨域数据,然后服务器响应时将数据包装在回调函数中,实现跨域的原理是利用 script 标签没有跨域限制,通过 src 指向一个 URL,最后跟一个回调函数 callback(仅支持 GET 请求)
4.WebSocket
WebSocket 连接不受同源策略限制
安全问题
1.XSS (Cross-site scripting) 跨站脚本攻击
是一种代码注入攻击。攻击者将恶意代码植入到提供给其它用户使用的页面中,盗取存储在客户端的 cookie 或者用于识别客户端身份的敏感信息
根据攻击的来源,XSS 攻击可分为:
- 存储型:攻击者将恶意代码提交到目标网站的数据库中
- 反射型:攻击者构造出特殊的 URL,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
- DOM 型:攻击者构造出特殊的 URL,前端 JS 执行恶意代码
预防
- 对用户输入和提交的内容进行转义和过滤
- 在使用 .innerHTML、.outerHTML、document.write() 时要注意,不要把不可信的数据作为 HTML 插到页面上
- 设置
httpOnly
Cookie: 禁止 JavaScript 读取某些敏感 Cookie
2.CSRF (Cross-site request forgery) 跨站请求伪造
用户在已登录网站的情况下,攻击者通过引诱用户访问一个恶意网站或链接,发送伪造的请求到已登录的网站地址。由于浏览器会自动附带当前用户的身份验证信息(如 Cookies),目标应用程序会误以为该请求是用户自身发起的合法请求
预防
- 请求信息中携带 token
- 将 Cookie 的
SameSite
属性设置为Strict
或Lax
,防止跨站请求携带 Cookies
3.点击劫持
攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击
预防
设置 HTTP 响应头 X-Frame-Options
:
DENY
:禁止任何页面通过 iframe 嵌入当前页面SAMEORIGIN
:允许相同源的页面通过 iframe 嵌入当前页面
内容安全策略(CSP)
内容安全策略(CSP)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。无论是数据盗取、网站内容污染还是恶意软件分发,这些攻击都是主要的手段。
- 使用:通过设置 HTTP Header 的
Content-Security-Policy
- 原理:相当于白名单机制,指定网站允许加载的内容
性能优化
资源方面:
- 图片压缩
- gzip 压缩
代码方面:
- HTML 中的 JS 文件使用 defer 或 async 加载
- 减少全局引入资源(如字体文件),页面访问时再加载
- 按需引入第三方包代码
- CSS 合理使用选择器,不要嵌套太深
- 减少 DOM 操作
- 路由懒加载
- 采用 keep-alive 缓存组件
- 大数据使用懒加载/虚拟列表
- 使用预加载 prefetch 对将来可能用到的资源进行提前缓存
打包方面:
- tree-shaking 去除无用代码(usedExports、sideEffects)
- 使用 plugin 压缩代码(内置terser-webpack-plugin、css-minimizer-webpack-plugin、html-minimizer-webpack-plugin)
- 代码进行拆分分离(SplitChunksPlugin 插件)
- 进行并发构建(HappyPack 插件)
网络方面:
- HTTP 缓存
- 使用 CDN 存放资源,加速访问
性能指标
阶段 | 指标名称 | 英文缩写 | 是否核心 | 计算方式 / 含义 | 推荐阈值 | 优化策略 |
---|---|---|---|---|---|---|
加载 | 白屏时间 | FP (First Paint) | 浏览器第一次把像素画到屏幕 | ≤ 1.5 s | ||
首屏内容渲染 | FCP (First Contentful Paint) | 首屏第一个 DOM 元素绘制 | ≤ 1.8 s | |||
最大内容绘制 | LCP (Largest Contentful Paint) | √ | 首屏最大可见元素绘制 | ≤ 2.5 s | 图片压缩、懒加载、CDN | |
渲染 | 首次可交互 | FID (First Input Delay) | √ | 用户第一次点击到浏览器响应时间 | ≤ 100 ms | 代码分割 |
累积布局偏移 | CLS (Cumulative Layout Shift) | √ | 页面加载过程中意外抖动的总分 | ≤ 0.1 | 尺寸预留、字体预加载 | |
交互 | 可交互时间 | TTI (Time to Interactive) | 页面完全可交互且主线程空闲 | ≤ 3.8 s | ||
总阻塞时间 | TBT (Total Blocking Time) | 主线程被长任务阻塞的总时长 | ≤ 200 ms | |||
长期 | 累积长任务 | Long Tasks | 单次 JS 执行 > 50 ms 的任务数 | 越少越好 | ||
内存泄漏 | Memory Leak | 多次 GC 后堆内存持续增长 | 稳定即可 |
图片懒加载
有 3 种方案:
- 浏览器原生:
<img loading="lazy">
,最简单,性能最好,缺点:阈值固定,无法自定义 - JS 监听滚动:监听
scroll
事件,频繁回流、性能差 - 现代方案:
IntersectionObserver
,性能好,自定义强
地址栏输入 URL 敲下回车后发生了什么
事件循环
JavaScript 是单线程的语言,它只有一个线程来执行代码,这意味着它一次只能执行一个任务。所有任务(同步/异步)都在主线程执行,事件循环是实现单线程非阻塞的机制
为什么 Javascript 要是单线程的
主要原因是 JS 最初的应用场景和核心目标:在浏览器中处理用户交互和操作 DOM,如果是多线程的,那么会产生冲突(如同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?)。单线程可以避免多线程带来的复杂性,比如线程同步、资源竞争等问题
任务分类
JavaScript 执行的异步代码分为宏任务和微任务
- 宏任务(MacroTask):
setTimeout
、setInterval
、setImmediate
(Node.js)、I/O 操作、UI 渲染 - 微任务(MicroTask):
Promise.then/catch/finally
、MutationObserver
、process.nextTick
(Node.js)
事件循环流程
- 所有任务都会在主线程上执行,形成一个执行栈,执行整个脚本的同步代码
- 遇到异步任务,放入任务队列(宏任务和微任务队列)
- 执行完执行栈的同步代码
- 执行所有微任务,直到微任务队列清空
- 再执行一个宏任务
- 重复步骤 4~5,直到所有任务完成
请说明浏览器中的事件循环 (Event Loop)|ExplainThis
最常见的事件循环 (Event Loop) 面试题目汇整|ExplainThis
JS: 一战吃透Promise精修版 - 掘金
JS 读代码 | 前端面试派
Tip
await
后面的代码 settled 之后,会把它下面的代码放入微任务队列new Promise
内的代码是同步的,会直接执行。一个 promise 被 settled 时才会将它的 .then/catch 放入微任务队列.then
后面再接.then
会继续放入微任务队列末尾,一次执行完
内存泄漏
内存泄漏的场景:
- 闭包
- 声明多余的全局变量
- 使用定时器没有清除
- 元素被移除后,事件监听器没有移除
- DOM 引用没有释放
检测:
使用浏览器开发者工具的 Memory 面板和 performance 面板查看
前端监控怎么做
- 监听
Vue.config.errorHandler
,捕获 Vue 框架的错误 - 监听
window.onerror
事件,捕获全局的宏任务错误 - 监听
window.unhandledrejection
事件,捕获 promise 的报错 - 开源框架:Sentry
封装组件注意的问题
- 不要追求大而全,注意拆分复用
- 不要忽略 html 的原生能力(如 input 的 type 属性,img 的 lazy 属性,details 折叠展开功能,dialog 原生对话框)
- 尽量使用语义化的 html(增加特殊人群的可访问性)
- 注释清晰
- 符合直觉
- 可扩展