谨慎使用全局变量

背景

之因此写这篇文章,是由于有同事使用全局变量不当致使了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】 封图来源于网络,侵权联系删除。

相关文章
相关标签/搜索