謹慎使用全局變量

背景

之因此寫這篇文章,是由於有同事使用全局變量不當致使了bug。因此在解釋標題以前,首先說一下業務背景。前端

很簡單,就是有一個頁面能夠辦理某個業務,這個業務又分爲兩種類型,能夠隨意切換類型。發現問題的過程是,頁面初始化時默認是A類型,因此此時前端會按照A類型傳參調用後臺大概3個接口,咱們暫且稱做接口1,接口2和接口3吧。其中接口3的請求參數依賴接口1和接口2的響應參數,接口1和接口2的返回數據會展現到前端,而後調用接口3時將從接口1和接口2的返回參數中拿數據傳遞給接口3,而後將接口3返回的數據展現,到此頁面初始化加載完成。java

由下面頁面草圖能夠看出,接口1,2,3都依賴於類型來完成對應的邏輯處理,在接口調用上確定是先調接口1,2(兩者沒有前後順序),而後調接口3。以後在從A類型切換至B類型時又會從新按B類型從新加載一遍接口1,2,3,展現B類型對應的數據。 ajax

問題排查

大概的業務規則就是這樣的,很簡單。可是在測試中發現,當頁面初始化時,迅速切換到B類型,前端彈出一個錯誤窗口「系統錯誤,缺乏必要參數」,偶現的問題但能夠穩定復現。後端

通過排查分析發現是前端接口調用順序問題,具體點就是調用接口3時,沒有拿到須要的數據(接口3的邏輯大體是經過前端傳的參數1和參數2取接口1和接口2放在緩存的數據,緩存的Key和類型有關) 從表象上看就是在調用接口3時,接口1或接口2尚未被調用,致使接口3從緩存拿不到須要的數據。api

帶着這樣的疑問去查看前端代碼,看接口的調用順序是否是真的有問題,結果發現前端調用的順序是沒有問題的。那問題是出在哪裏呢?緩存

經過排查前端代碼,發現一個問題,前端設置了一個全局變量來記錄當期的業務類型(如A類型、B類型),調用接口1,2,3傳遞業務類型時就是傳遞的這個全局變量。看到這也許你就能想明白爲何說謹慎使用全局變量了,這個問題正是由於全局變量的使用不當致使的。安全

分析緣由

咱們來一塊兒分析下究竟是如何致使的吧。網絡

上述也提到了初始化時快速切換到B類型,那麼前端的這個記錄當前業務類型的全局變量是什麼時候改變其值的呢?多線程

沒錯,正是在切換業務類型時記錄當前業務類型A或B。當初始化默認是A類型時,接口會這樣調用A類型:接口1(A)->接口2(A)->接口3(A),當切換到B類型時觸發一系列接口調用,和A類型也同樣,B類型:接口1(B)->接口2(B)->接口3(B)這樣調用。測試

關鍵就是在切換到B類型時,可能會存在這樣的問題,接口1,2正常調用,即傳遞的業務類型都是A,但剛好在調用接口3前,切換到了業務類型B類型,那麼此時記錄當前業務類型的全局變量隨之變爲B,那麼此時本來初始化的時候的接口3拿到的業務類型就由預期的A變成了B,而在此以前接口1,2都是按A類型傳遞的參數,故後臺存儲的數據是A類型的,但此時由於全局變量的變化,接口3傳遞的業務類型就又A變爲B,故在接口3的業務邏輯裏,按業務類型B去緩存取數據時是取不到,後端校驗參數時就會報錯「系統錯誤,缺乏必要參數」。

看到這,你是否是以爲這有點像java的多線程共享變量?多線程共享變量也會引起這樣的問題,當一個線程正在使用某一變量時,忽然被別的線程修改了,致使該線程拿到了髒數據。解決辦法是,線程獨享資源的操做權,操做完畢其餘線程纔有權限讀取該資源,同一時間只有一個線程才能修改共享變量,即多個線程間相對於該資源是互斥的關係,java中多用鎖來保證操做的安全性。

那在這個問題中,怎麼類比呢?咱們能夠把選中A類型時要走的一系列接口比做A線程;把B類型要走的一系列接口比做B線程,這兩個線程執行的流程、方法同樣,只是須要的參數的具體值是不同的,A、B線程各自執行三個步驟每一個步驟都會取共享變量做爲參數傳遞給後臺。再把切換類型要改變當前業務類型(biz_type)這一操做記做C線程。那大體就是,A、B線程讀 biz_type ,C線程修改 biz_type 。這就能夠理解成三個線程共享一個變量,在頁面上切換業務類型能夠看作線程的輪轉,因此不加以控制不免會發生錯誤。

問題解決

弄懂了發生問題的緣由以後怎麼來解決呢?其實解決起來也簡單,正如標題所說[謹慎使用全局變量],問題的根源就是使用了全局共享變量,致使在A線程還沒走完時C線程修改了 biz_type 的值,從而致使線程A的三個步驟拿到的 biz_type 的值不相同,進而致使後臺根據類型取緩存數據時拿不到,最終報錯。因此,想要解決該問題,最關鍵的就是從這個全局變量着手,經查看前端代碼而知:在切換類型時,根據當前選中的類型傳遞相應的參數,當選中時咱們就能知道是哪一種類型了,因此咱們就能清楚的去調用接口傳遞相應的類型字段,而不是先對全局變量賦值,再在接口裏自行去取全局變量。

修改前:

var biz_type = 'A';//定義全局變量,默認爲A業務類型
//change radio
function changeRadio(){
    if(#('#bizType_A').is(':checked')){
        biz_type = 'A';//修改變量值
        api_1();
    }else{
        biz_type = 'B';//修改變量值
        api_1();
    }   
}
//function1
function api_1(){
    //get biz_type
    //send ajax with biz_typ
    if(data.success){
        api_2();
    }else{
        alert(data.msg);
    }
}
//function2
function api_2(){
    //get biz_type
    //send ajax with biz_typ
    if(data.success){
        api_3();
    }else{
        alert(data.msg);
    }
}
//function3
function api_3(){
    //get biz_type
    //send ajax with biz_type
    if(data.success){
        jump_to_success();
    }else{
        alert(data.msg);
    }
}
複製代碼

修改後:

//change radio
function changeRadio(){
    if(#('#bizType_A').is(':checked')){
        api_1('A');//參數傳遞
    }else{
        api_1('B');//參數傳遞
    }   
}
//function1
function api_1(biz_type){
    //send ajax with biz_typ
    if(data.success){data.
        api_2(biz_type);
    }else{
        alert(data.msg);
    }
}
//function2
function api_2(biz_type){
    //send ajax with biz_typ
    if(data.success){
        api_3(biz_type);
    }else{
        alert(data.msg);
    }
}
//function3
function api_3(biz_type){
    //send ajax with biz_type
    if(data.success){
        jump_to_success();
    }else{
        alert(data.msg);
    }
}
複製代碼

修改後使用參數傳遞的方式,這樣能夠保證一套流程走下來,拿到的 biz_type 值同樣。 另外,能夠經過控制切換的方式保證A線程沒走完時不容許修改 biz_type 的值,不容許執行B線程,即當A類型下的流程沒走完時切換不了類型。能夠經過標誌位來斷定A流程是否走完,進而斷定是否能夠切換到B類型上。

總結

不過這個問題不大,後端作了參數的校驗,可是爲了提高用戶體驗這個問題必定是要解決的。這實際上是前端開發人員一個小小的疏忽致使的,當前端在寫代碼時他確定不會預見到會發生這樣的問題,他確定不會想到全局變量會致使這樣的問題,更不會想到用戶在頁面沒初始化完成時就切換類型。但這些對於一個初出茅廬的前端開發來講,情有可原,權當是積累經驗了。切記能傳參的儘可能不要用全局變量,若是必需要用,那就一點要把握好不要出現相似問題。

出問題不可怕,在問題中成長,積累經驗,纔是最重要的。

【end】 封圖來源於網絡,侵權聯繫刪除。

相關文章
相關標籤/搜索