在前端如何保護共享對象

什麼是共享對象

被屢次使用到的同一個對象即爲共享對象

好比咱們用標準的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的兼容便可
相關文章
相關標籤/搜索