常見的8個(gè)前端防御性編程方案
關(guān)于前端防御性編程
我們大多數(shù)情況可能遇到過,后端的由于同時(shí)請(qǐng)求人數(shù)過多,或者數(shù)據(jù)量過大,又或者是因?yàn)楫惓?dǎo)致服務(wù)異常,接口請(qǐng)求失敗,然后前端出現(xiàn)白屏或者報(bào)錯(cuò) 還有一種情況,是前端自身寫的代碼存在一些缺陷,整個(gè)系統(tǒng)不夠健壯,從而會(huì)出現(xiàn)白屏,或者業(yè)務(wù)系統(tǒng)異常,用戶誤操作等 那么,就出現(xiàn)了前端防御性編程
常見的問題和防范
1.最常見的問題:
uncaught TypeError: Cannot read property 'c' of undefined
出現(xiàn)這個(gè)問題最根本原因是:
當(dāng)我們初始化一個(gè)對(duì)象obj為{}時(shí)候,obj.a這個(gè)時(shí)候是undefined.我們打印obj.a可以得到undefined,但是我們打印obj.a.c的時(shí)候,就會(huì)出現(xiàn)上面的錯(cuò)誤。js對(duì)象中的未初始化屬性值是undefined,從undefined讀取屬性就會(huì)導(dǎo)致這個(gè)錯(cuò)誤(同理,null也一樣)
如何避免?
js和ts目前都出現(xiàn)了一個(gè)可選鏈概念,例如:
const obj = {};
console.log(obj?.b?.c?.d)
上面的代碼并不會(huì)報(bào)錯(cuò),原因是
?.遇到是空值的時(shí)候便會(huì)返回undefined.
2.前端接口層面的錯(cuò)誤機(jī)制捕獲
前端的接口調(diào)用,一般都比較頻繁,我們這時(shí)候可以考慮使用單例模式,將所有的axios請(qǐng)求都用一個(gè)函數(shù)封裝一層。統(tǒng)一可以在這個(gè)函數(shù)中catch捕獲接口調(diào)用時(shí)候的未知錯(cuò)誤,偽代碼如下:
function ajax(url,data,method='get'){
const promise = axios[method](url,data)
return promise.then(res=>{
}).catch(error){
//統(tǒng)一處理錯(cuò)誤
}
}
那么只要發(fā)生接口調(diào)用的未知錯(cuò)誤都會(huì)在這里被處理了
3.錯(cuò)誤邊界(Error Boundaries,前端出現(xiàn)未知錯(cuò)誤時(shí),展示預(yù)先設(shè)定的UI界面)
以React為例
部分 UI 的 JavaScript 錯(cuò)誤不應(yīng)該導(dǎo)致整個(gè)應(yīng)用崩潰,為了解決這個(gè)問題,React 16 引入了一個(gè)新的概念 —— 錯(cuò)誤邊界。
錯(cuò)誤邊界是一種 React 組件,這種組件可以捕獲并打印發(fā)生在其子組件樹任何位置的 JavaScript 錯(cuò)誤,并且,它會(huì)渲染出備用 UI,而不是渲染那些崩潰了的子組件樹。錯(cuò)誤邊界在渲染期間、生命周期方法和整個(gè)組件樹的構(gòu)造函數(shù)中捕獲錯(cuò)誤。
使用示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能夠顯示降級(jí)后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同樣可以將錯(cuò)誤日志上報(bào)給服務(wù)器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定義降級(jí)后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
注意
錯(cuò)誤邊界無(wú)法捕獲以下場(chǎng)景中產(chǎn)生的錯(cuò)誤:
事件處理(了解更多) 異步代碼(例如 setTimeout 或 requestAnimationFrame 回調(diào)函數(shù)) 服務(wù)端渲染 它自身拋出來(lái)的錯(cuò)誤(并非它的子組件)
4.前端復(fù)雜異步場(chǎng)景導(dǎo)致的錯(cuò)誤
這個(gè)問題可能遠(yuǎn)不止這么簡(jiǎn)單,但是大道至簡(jiǎn),遵循單向數(shù)據(jù)流的方式去改變數(shù)據(jù),例如:
//test.js
export const obj = {
a:1,
b:2
}
//使用obj
import {obj} from './test.js';
obj.a=3;
當(dāng)你頻繁使用這個(gè)obj對(duì)象時(shí),你無(wú)法根據(jù)代碼去知道它的改變順序(即在某個(gè)時(shí)刻它的值是什么),而且這里面可能存在不少異步的代碼,當(dāng)我們換一種方式,就能知道它的改變順序了,也更方便我們debug
例如:
//test.js
export const obj = {
a:1,
b:2
}
export function setObj (key,value) {
console.log(key,value)
obj[key] = value
}
這樣,我們就做到了
5.前端專注“前端”
對(duì)于一些敏感數(shù)據(jù),例如登錄態(tài),鑒權(quán)相關(guān)的。前端應(yīng)該是盡量做無(wú)感知的轉(zhuǎn)發(fā)、攜帶(這樣也不會(huì)出現(xiàn)安全問題)
6.頁(yè)面做到可降級(jí)
對(duì)于一些剛上新的業(yè)務(wù),或者有存在風(fēng)險(xiǎn)的業(yè)務(wù)模塊,或者會(huì)調(diào)取不受信任的接口,例如第三方的接口,這個(gè)時(shí)候就要做一層降級(jí)處理,例如接口調(diào)用失敗后,剔除對(duì)應(yīng)模塊的展示,讓用戶無(wú)感知的使用
7.巧用loading和disabled
用戶操作后,要及時(shí)loading和disabled確保不讓用戶進(jìn)行重復(fù),防止業(yè)務(wù)側(cè)出現(xiàn)bug
8.慎用innerHTML
容易出現(xiàn)安全漏洞,例如接口返回了一段JavaScript腳本,那么就會(huì)立即執(zhí)行。此時(shí)腳本如果是惡意的,那么就會(huì)出現(xiàn)不可預(yù)知的后果,特別是電商行業(yè),尤其要注意
