跨域

跨域本质是浏览器基于同源策略的一种安全手段

同源策略

浏览器的安全功能,协议、域名、端口一致则是同源,非同源的限制:

  • 无法通过 XMLHttpRequest 或 fetch 发起跨域请求
  • 无法访问不同源的文档的 DOM
  • 无法读取其他域的 Cookie、Storage

同源策略豁免情况

下面的情况可以不受同源策略影响:

  • <script src="CDN地址">:因为绕过了同源策略,所以需要完全信任脚本来源才引入
  • <img src="跨域地址">
  • <link href="跨域CSS">
  • <video src="跨域视频">

简单请求

  • 使用下列方法之一:GETPOSTHEAD
  • 标头字段自定义的只能有:AcceptAccept-LanguageContent-TypeContent-LanguageRange
  • Content-Type 所指定的媒体类型的值仅限于下列三者之一:text/plainmultipart/form-dataapplication/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 属性设置为 StrictLax,防止跨站请求携带 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 种方案:

  1. 浏览器原生:<img loading="lazy">,最简单,性能最好,缺点:阈值固定,无法自定义
  2. JS 监听滚动:监听 scroll 事件,频繁回流、性能差
  3. 现代方案:IntersectionObserver,性能好,自定义强

地址栏输入 URL 敲下回车后发生了什么

  1. 浏览器解析 URL
  2. DNS 解析(见 域名解析过程
  3. 建立 HTTP 连接(见 TCP三次握手
  4. 发送 HTTP 请求
  5. 接收 HTTP 响应
  6. 页面渲染(见 浏览器渲染过程

事件循环

JavaScript 是单线程的语言,它只有一个线程来执行代码,这意味着它一次只能执行一个任务。所有任务(同步/异步)都在主线程执行,事件循环是实现单线程非阻塞的机制

为什么 Javascript 要是单线程的

主要原因是 JS 最初的应用场景和核心目标:在浏览器中处理用户交互和操作 DOM,如果是多线程的,那么会产生冲突(如同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?)。单线程可以避免多线程带来的复杂性,比如线程同步、资源竞争等问题

任务分类

JavaScript 执行的异步代码分为宏任务和微任务

  • 宏任务(MacroTask):setTimeoutsetIntervalsetImmediate(Node.js)、I/O 操作、UI 渲染
  • 微任务(MicroTask):Promise.then/catch/finallyMutationObserverprocess.nextTick(Node.js)

事件循环流程

  1. 所有任务都会在主线程上执行,形成一个执行栈,执行整个脚本的同步代码
  2. 遇到异步任务,放入任务队列(宏任务和微任务队列)
  3. 执行完执行栈的同步代码
  4. 执行所有微任务,直到微任务队列清空
  5. 再执行一个宏任务
  6. 重复步骤 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(增加特殊人群的可访问性)
  • 注释清晰
  • 符合直觉
  • 可扩展