在前端如何保护共享对象

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:

什么是共享对象

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

比如我们用标准的es模块来写一个导出单位转换的模块

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

当我们在其它模块中使用该模块时,converter即是共享对象,内存中只有一份,不管它被import了多少次。

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

//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的情况,这时候或许就能发现问题。

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

如何保护共享对象

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的兼容即可

目录
相关文章
|
3月前
|
前端开发 开发者
new操作符背后的秘密:揭开Web前端对象创建的神秘面纱!
【8月更文挑战第23天】在Web前端开发中,`new`操作符是创建对象实例的核心。本文以`Person`构造函数为例,通过四个步骤解析`new`操作符的工作原理:创建空对象、设置新对象原型、执行构造函数并调整`this`指向、判断并返回最终对象。了解这些有助于开发者更好地理解对象实例化过程,从而编写出更规范、易维护的代码。
39 0
|
24天前
|
JSON 前端开发 数据格式
前端的全栈之路Meteor篇(五):自定义对象序列化的EJSON介绍 - 跨设备的对象传输
EJSON是Meteor框架中扩展了标准JSON的库,支持更多数据类型如`Date`、`Binary`等。它提供了序列化和反序列化功能,使客户端和服务器之间的复杂数据传输更加便捷高效。EJSON还支持自定义对象的定义和传输,通过`EJSON.addType`注册自定义类型,确保数据在两端无缝传递。
|
2月前
|
前端开发 JavaScript
前端基础(十四)_Math对象
本文介绍了JavaScript中`Math`对象的常用方法,包括: 1. `Math.floor()`:向下取整,去掉小数部分。 2. `Math.ceil()`:向上取整,向上进一。 3. `Math.round()`:四舍五入,针对小数点后面第一位数字。 4. `Math.max()`:获取数字序列中的最大值。 5. `Math.min()`:获取数字序列中的最小值。 6. `Math.pow()`:计算某个数字的次方数。 7. `Math.sqrt()`:开根号。 8. `Math.random()`:生成一个0到1之间的随机数。
21 1
前端基础(十四)_Math对象
|
1月前
|
JSON 前端开发 数据格式
@RequestMapping运用举例(有源码) 前后端如何传递参数?后端如何接收前端传过来的参数,传递单个参数,多个参数,对象,数组/集合(有源码)
文章详细讲解了在SpringMVC中如何使用`@RequestMapping`进行路由映射,并介绍了前后端参数传递的多种方式,包括传递单个参数、多个参数、对象、数组、集合以及JSON数据,并且涵盖了参数重命名和从URL中获取参数的方法。
73 0
@RequestMapping运用举例(有源码) 前后端如何传递参数?后端如何接收前端传过来的参数,传递单个参数,多个参数,对象,数组/集合(有源码)
|
2月前
|
前端开发 JavaScript
前端基础(十五)_时间对象、字符串对象
本文介绍了JavaScript中时间对象的操作方法,包括获取和设置年、月、日、小时、分钟、秒等,以及如何格式化时间显示,同时提及了字符串对象的常用方法。
31 0
前端基础(十五)_时间对象、字符串对象
|
3月前
|
缓存 前端开发 Java
【前端学java】复习巩固-Java中的对象比较(15)
【8月更文挑战第11天】Java中的对象比较
33 1
【前端学java】复习巩固-Java中的对象比较(15)
|
2月前
|
前端开发 JavaScript
前端基础(十六)_数组对象
本文详细介绍了JavaScript中数组对象的创建和操作方法,包括数组的增删改查、排序、去重、迭代等常用操作。
18 0
|
3月前
|
前端开发 Java C++
超简单使用Vite+Vue3构建共享开发和分模块打包的前端项目
使用Vite和Vue3构建支持共享组件和分模块独立打包的前端项目的方法。
436 0
超简单使用Vite+Vue3构建共享开发和分模块打包的前端项目
|
3月前
|
开发框架 前端开发 JavaScript
在基于vue-next-admin的Vue3+TypeScript前端项目中,为了使用方便全局挂载对象接口
在基于vue-next-admin的Vue3+TypeScript前端项目中,为了使用方便全局挂载对象接口
|
3月前
|
JavaScript 前端开发 API
前端开发者的救赎:揭秘JQ对象与DOM元素的神秘转换术
【8月更文挑战第23天】在Web前端开发领域,jQuery(简称JQ)作为一款流行的JavaScript库,极大简化了HTML文档遍历、事件处理、动画及Ajax交互等操作。理解和掌握jQuery对象与DOM元素间的转换至关重要。
41 0