接口出现问题前端页面就会直接崩溃?
在前端开发中:当接口出现问题,页面就会直接崩溃。在JS开发应用中导致页面崩溃的多种边界情况:解构赋值、数组方法调用、遍历对象数组、使用对象方法、async/await捕获异常失败、JSON.parse解析报错、API兼容、内存溢出等。
解构失败报错
不做任何处理直接将后端返回的数据进行解构
const handlerData = (data) => {
const {user} = data
const {id,name} = user
}
handlerData({}) // TypeError: Cannot destructure property 'id' of 'user' as it is undefined.
解构赋值的规则是:只要等号右边的值不是对象或数组,就先将其转化为对象(装箱)。由于undefined、null无法转换为对象,所有对他们进行解构赋值就会报错。
所以当data为undefined、null的时候,上述代码就会报错。
这个时候给data附一个默认值,但是会依然报错。
const handlerData = (data) => {
const {user = {}} = data
const {id,name} = user
}
handlerData({user: null}) // TypeError: Cannot destructure property 'id' of 'user' as it is null.
ES6内部使用严格相等运算符(===)判断一个变量是否有值。所以当一个对象的属性值不严格等于undefined,默认值是不会生效的。
当props.data为null的时候,那么 const {id,name} = user 就会报错!
正确处理方式
/* 正确处理 */
const handlerData = (data) => {
const {user} = data
const {id,name} = user || {}
}
handlerData({user: null})
数组方法调用错误
从接口返回的数据直接当成数组来用
const handlerData = (data) => {
const {userList} = data
const newList = userList.map(item => item.name)
}
handlerData({userList: null}) // TypeError: Cannot read properties of null (reading 'map')
handlerData({userList: 123}) // TypeError: userList.map is not a function
正确处理方式
const handlerData = (data) => {
const {userList} = data
let newList = null
// 判断是否是数组
if(Array.isArray(userList)){
newList = userList.map(item => item.name)
}
console.log(newList); // [ '王五', '李四' ]
}
handlerData({userList: [{name: "王五"}, {name: "李四"}]})
遍历对象数组报错
遍历对象数组时也要注意null或undefined的情况
const handlerData = (data) => {
const {userList} = data
const newList = userList.map(item => item.name)
}
handlerData({userList: [null, undefined]}) // TypeError: Cannot read properties of null (reading 'name')
一旦数组中的某一项值为null或者undefined,那么item.name必然会出错!
/* 使用可选链:? */
const handlerData = (data) => {
const {userList} = data
const newList = userList.map(item => item?.name)
}
handlerData({userList: [null, undefined]})
但是如果是以下情况的话不推荐使用可选链?:
const handlerData = (data) => {
const {userList} = data
const newList = userList.map(item =>`用户id是${item?.id},用户名字是${item?.name},用户年龄是${item?.age}`)
}
handlerData({userList: [null, undefined]})
?可选链操作符,虽然好用但是也不能滥用。item?.id会被编译成
item === null || item === void 0 ? void 0: item.id
,滥用导致编译后的代码size体积变大。
正确处理方式:当可选链?操作较多的时候,选择使用或运算赋默认值
const handlerData = (data) => {
const { userList } = data
const newList = userList.map(item => {
const { id, name, age } = item || {}
return `用户id是${id},用户名字是${name},用户年龄是${age}`
}
)
}
handlerData({ userList: [null, undefined] })
使用对象方法报错
null、undefined无法被转化为对象,因此对其使用对象的方法就会报错
const handlerData = (data) => {
const { user } = data
const newList = Object.entries(user)
}
handlerData({ user: null }) // TypeError: Cannot convert undefined or null to object
正确处理方式:
const handlerData = (data) => {
const { user } = data
/* 使用|| */
const newList = Object.entries(user || {})
}
handlerData({ user: null })
/**
* 判断给定值类型或获取给定值的类型名称
* @param {*} val-要判断类型的值
* @param {string} [type] - 可选,指定的类型名称
* @returns {string|boolean} -如果提供了type类型,返回一个布尔值表示val是否属于该类型;如果没有提供type,返回val的类型名称。
*
* @example
* 获取类型名称
* console.log(judgeDataType(56)); // number
* console.log(judgeDataType('yu')); // string
*
* @example
* 判断是否为指定的值
* console.log(judgeDataType(78, "number")); // true
*/
function judgeDataType(val, type){
const dataType = Object.prototype.toString.call(val).slice(8,-1).toLowerCase()
return type ? dataType === type : dataType
}
const handlerData = (data) => {
const { user } = data
// 判断user是否是对象
if(judgeDataType(user, "object")){
const newList = Object.entries(user)
}
}
handlerData({ user: null })
async/await报错未捕获
const List = () => {
let loading
const getData = async () => {
loading = true
const res = await fectchListData()
loading = false
}
getData()
}
正确处理方式:
const List = () => {
let loading
const getData = async () => {
try{
loading = true
// fectchListData()可能会报错,页面就会一直在加载,因此需要捕获异常,改变页面的加载状态
const res = await fectchListData()
loading = false
}catch(err){
loading = false
}
}
getData()
}
JSON.parse解析报错
const handlerData = (data) => {
const { userStr } = data
const user = JSON.parse(userStr)
}
handlerData({ userStr: 'tdecuy' }) // SyntaxError: Unexpected token d in JSON at position 1
正确处理方式:
const handlerData = (data) => {
const { userStr } = data
try{
const user = JSON.parse(userStr)
}catch(err){
console.log(`${userStr}不是一个有效JSON字符串`); // gceyu不是一个有效JSON字符串
}
}
handlerData({userStr: "gceyu"})
动态导入某个模块失败报错
const loadModule = async () => {
const module = await import('./index.js')
module.getNum() // 34
}
loadModule()
如果导入的模块存在语法错误、网络或者跨域问题、文件不存在、循环依赖、甚至文件很大导致内存不足、模块内的运行错误等都有可能阻塞后续代码的执行。
正确处理方式:
const loadModule = async () => {
try{
const module = await import('./index.js')
module.getNum() // 34
}catch(err){
console.log(err);
}
}
API兼容性报错
fetch('/api/data').then(response => response.json()).then(data => {
console.log(data);
}).catch(err => {
console.log(err);
})
低版本的Node不支持fetch,需要更高兼容性的场景使用axios。
内存溢出崩溃
滥用内存缓存可能会导致内存溢出
/* 情况九: 内存溢出崩溃 */
const cache = {}
function addToCache(key, value){
cache[key] = value
// 没有清理机制,缓存会无限增长
}
- 避免闭包持有大对象的引用
- 记得清楚定时器和事件监听器
- 避免深度递归超出栈内存
上述列举的js在运行时可能会出现的发生的错误而导致应用崩溃的一些边界情况,在书写代码的时候要额外注意!
边界场景的容错一定要做,原则上不相信任何的外部输入数据的存在性和类型!!!