在前端如何保护共享对象

什么是共享对象

被屡次使用到的同一个对象即为共享对象

好比咱们用标准的es模块来写一个导出单位转换的模块前端

//converter module
export default {
    cmToIn(){
        //convert logic
    }
}

当咱们在其它模块中使用该模块时,converter便是共享对象,内存中只有一份,无论它被import了多少次。es6

同理,上面展现的是通用方法的对象集合,在前端项目里,咱们也会把一些所谓写死的数据集中封装在某个模块里,方便后期的修改,好比咱们实现一个constant常量模块,咱们把一些项目中使用的,后期可能会修改的数据放进去segmentfault

//constant
export default {
    dateFormatter:'YYYY-MM-DD',
    reports:{
        productId:'123',
        productName:'456'
    }
}

这里仅示意一下后端

为何要保护共享对象

防止共享的对象被意外修改致使线上故障

原则上这些通用的模块,咱们不会,也不会有意的在咱们业务代码中去修改里面的数据,尤为像常量这样的模块,若是要修改的话,咱们确定修改常量这个模块。缓存

可是,凡事总有意外,好比说咱们有这样一个场景:根据后端返回的用信息,以及前端写死的一些常量,来判断某个用户能不能展现某个报表,咱们指望的代码多是这样的工具

import Constant from './constant';//引入咱们前面定义的constant模块
//...其它略
export default View.extend({
    render(){
        //...其它逻辑略
        if(Constant.reports.productId==user.reportProductId){
            //....
        }
    }
});

注意上述代码中的if语句,若是错写成:if(Constant.reports.productId=user.reportProductId),两个等号的比较写成了一个等号的赋值。测试

若是自测的时候,用户接口里user.reportProductId返回的正好也是123,那么先赋值,再作if判断,成立,作为开发者会错误的觉得这里的逻辑没问题。固然,正常状况下也要测试下用户接口里user.reportProductId返回不是123的状况,这时候或许就能发现问题。ui

若是上述问题没有测试出来,阴差阳错的上线以后,这个问题对于大型单页应用是致命的,若是某个用户的reportProductId456,访问了写错的页面后,由于被意外的修改了constant中的reports.productId,会致使后续其它模块在读取时再也不是最初的123而出问题prototype

如何保护共享对象

const

const关键字声明的仅防止变量被从新赋值,没法防止对象修改

Object.freeze

能够防止被修改,可是若是对象嵌套时,被嵌套的对象依然能够被修改,须要开发者对要 freeze的对象递归遍历进行 freeze。最重要的一点是,当我修改一个 freeze对象时,虽然修改不成功,但也没有任务失败的提示,在前述场景中,咱们仍是但愿开发者在修改一个不容许的被修改的对象时能及时给出相应的提示。

Proxy

es6新增的代理操做对象的方法

Proxy相关的文章很是多,这里就再也不详细说,咱们借助Proxy来实现一个Safeguard方法来保护咱们的共享对象代理

const Safeguard = o => {
    let build = o => {
        let entity = new Proxy(o, {
            set() {
                throw new Error('readonly');
            },
            get(target, property) {
                let out = target[property];
                if (target.hasOwnProperty(property) &&
                    (Array.isArray(out) ||
                        Object.prototype.toString.call(out) == '[object Object]')) {
                    return build(out);
                }
                return out;
            }
        });
        return entity;
    };
    return build(o);
}

这里简化了代码,你能够根据本身的须要去调整相应的实现逻辑

使用

const user=Safeguard({
    name:'行列',
    address:{
        city:'hz'
    }
});

这个user对象只能读,不能写,当开发者尝试写入新数据时,会抛出错误提示开发者

使用场景

地址栏解析对象

在单页应用中,咱们须要把地址栏中的字符串地址解析成对象,方便咱们使用。

好比/path/name?a=b&c=d,咱们可能解析成这样的对象

{
    path:'/path/name',
    params:{
        a:'b',
        c:'d'
    }
}

若是你统计过你的单页应用,会发现固定的用户老是只访问某些页面,咱们能够在用户访问某个页面时,临时的把地址栏中的这个地址字符串解析一遍,也能够把解析结果存起来,当用户再访问这个页面时,不须要解析,把存起来的结果拿出来使用便可

关于这一块我曾经写过Magix.Cache,详细的来讲明该如何智能的缓存哪些须要的信息

对于缓存后的地址栏信息对象,它就是一个共享对象,要确保它不能被开发者写入新的值,就可使用前面咱们定义的Safeguard方法来进行保护

缓存的接口数据

在单页应用开发中,有些数据须要后端提供,可是后端提供的这些数据可能在很长一段时间内都不会被修改,好比省市数据,前端不必在每次须要使用这种数据时都请求一次,因此前端能够把该接口的数据缓存下来,来节省请求

对于这样的数据对象,也须要保护,简言之,只要是共用的对象,均须要防止它被意外的修改

关于上线

前面咱们聊到的Safeguard方法,在我看来是不必发布到线上的,只要开发阶段存在便可。只要保证在开发中没有对共享对象的写入操做,那么发布到线上时确定也没有写入操做,这时候这个保护Safeguard方法就是多余的。

如何在开发时保护,而发布到线上时去掉呢?

咱们可使用uglify这个代码压缩工具的global_defs配置。好比在开发阶段这样定义

if (typeof DEBUG == 'undefined') window.DEBUG = true;
//...

const user={
    name:'行列',
    address:{
        city:'hz'
    }
}

if(DEBUG){
    user=Safeguard(user);
}

而后在压缩时:

uglify({
    compress: {
        global_defs: {
            DEBUG: false
        }
    },
    output: {
        ascii_only: true
    }
});

那么这样压缩出来的代码就不包含DEBUG相关的语句了

固然, Safeguard跟随上线也没有什么大问题,最后这个“关于上线”这块只是想作更深刻的探讨,若是 Safeguard要上到线上,注意 Proxy的兼容便可
相关文章
相关标签/搜索