关于防御性编程
- 你是否遇到过,接口请求失败或者返回数据错误,导致系统白屏
- 或者前端自身写的代码存在一些缺陷,导致整个系统不够健壮,从而导致系统白屏
常见的问题与防范
最常见的问题
- 访问了null或者undefined的属性
null.a
Uncaught TypeError: Cannot read properties of null (reading 'a')
- 防御方案:使用可选链?.,可选链会在遇到空值时返回undefined
const obj = {}
console.log(obj?.a?.b)
前端接口层面的错误机制捕获
使用单例模式,将所有的axios请求都用一个函数封装一层。统一可以在这个函数中catch捕获接口调用时候的未知错误
单例模式保证一个类只有一个实例,并提供一个全局访问点来获取它。
request(config: AxiosRequestConfig) {
return new Promise((resolve, reject) => {
this.instance
.request(config)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
}
前端复杂异步场景导致的错误
在前端项目中,直接修改导出的对象会导致状态不可控,尤其是在复杂异步场景中,你很难判断数据在某个时刻的值到底是什么、是被谁改的。通过封装 setter 函数,可以显式地记录每次修改的 key 和 value,从而实现「单向数据流」的状态管理方式。这种方式不仅提升了可读性与可维护性,还更利于 debug 和扩展,是一种更加稳健的防御性编程策略。
问题背景
// test.js
export const obj = {
a: 1,
b: 2
}
// 使用 obj
import { obj } from './test.js'
obj.a = 3
这段代码的问题是
- obj是一个导出的全局对象
- 任何文件都能直接修改它的属性
- 多个模块、异步任务可能都在读写它
- 导致你无法跟踪它的状态是怎么变的,在哪变的,什么时候变得
解决方案:
// test.js
export const obj = {
a: 1,
b: 2
}
export function setObj(key, value) {
console.log(key, value)
obj[key] = value
}
- 修改行为统一管理,数据更可控
- 日志追踪更方便
扩展
这体现了单向数据流的应用
所有的数据改动都走一个方向,一个入口,不允许乱改
这也是为什么像React、Vue、Redux等框架都鼓励 “不可变数据 + action” 的方式来改变状态
前端专注前端
在涉及登录态、权限控制、敏感信息等安全相关场景中,前端应遵循职责分离的设计理念,尽量避免直接处理或解析敏感数据,仅负责数据的安全转发和展示控制。鉴权逻辑应统一由后端完成,前端通过接口响应结果进行视图判断,从而降低安全风险,提升系统的可维护性与稳定性
页面做到可降级
对于一些刚上新的业务,或者有存在风险的业务模块,或者会调取不受信任的接口,例如第三方的接口,这个时候就要做一层降级处理,例如接口调用失败后,剔除对应模块的展示,让用户无感知的使用
巧用loading和disabled
- 在用户触发操作(比如点击按钮、提交表单、发起请求)后,及时给按钮或控件加上 loading 状态或 disabled 状态。
- 确保不让用户进行重复操作,防止业务侧出现 bug
为什么这样做
- 防止重复提交或者点击
- 明确交互状态
- 减轻后端压力
慎用innerHTML
-
innerHTML 是直接把字符串插入 DOM 的方法;
-
如果插入的字符串中包含 script、<img οnerrοr=…、a href=“javascript:…” 等恶意代码,浏览器就会执行它;
-
这些代码一旦被执行,可能导致 XSS 攻击。
什么是XSS攻击?
- XSS(Cross-Site Scripting)跨站脚本攻击,是指黑客在页面中注入恶意脚本代码,从而获取用户信息、劫持登录态、跳转钓鱼页面等。
比如:
innerHTML = '<img src="x" onerror="alert(document.cookie)">'
用户打开这个页面时就会弹出自己的 cookie,一旦被攻击者获取,可能会导致账户被盗、信息泄露等严重后果。