Angular 跨頁緩存設計

自去年開始,AngularJS 引入到項目中,並逐漸推進公司產品核心模塊進行重構,提高產品穩定性與開發效率。在前端架構演進的過程當中最艱難的不是理解 API,而是思惟方式被顛覆的無助,全部繁雜的事務都被高度抽象化,之前 WEB 富 UI 開發最頭疼的表現部分放到如今幾乎不費吹灰之力,前端工程師重心將由 UI 轉向數據模型的構建。在這個過程當中,小夥伴常常會遇到跨頁數據傳遞這個經典問題,因而我在公司給組裏同事作了一次分享,如下是概要:前端

業務場景

  • 跨頁選中操做
  • 分步驟操做

問題

  • Angular 跳轉頁面後,控制器實例被註銷
  • Angular 沒有提供跨頁傳遞臨時數據的特性

可選方案

A. 超級單頁

使用同一個控制器與同一份實例,不使用路由瀏覽器

缺點緩存

  • 無歷史記錄:不支持瀏覽器前進後退操做(體驗差)
  • 無URL:不支持收藏與分享地址(若是應用出BUG,客戶沒法提供 URL,致使售後成本變高)

B. URL傳遞數據

經過 URL 查詢參數傳遞數據安全

缺點前端工程師

  • 可能引發安全問題
  • 不支持複雜的數據模型(只支持String類型)

D. Ng Service 緩存

使用 Angular Service 構建內存緩存閉包

權衡後,採用此方案。架構

實踐遇到的問題

惟一性難以保證

  • 可能使用未清理的緩存引發 BUG

內存泄露

  • 過時的緩存得不到清理
  • 緩存會連同控制器一塊兒被 Ng Service 持有(閉包的緣故)

基於路由緩存設計

保證惟一性

  • 在連續操做的頁面 URL 中添加 cache key
  • 在控制器中根據 cache key 匹配緩存

cacheKey

// 取 URL 的 cache key
var cacheKey = $routeParams['cache_key'];

讀寫緩存優化

if (!AppCache.cache || AppCache.key !== cacheKey) {

    // 覆蓋 service 緩存
    AppCache.cache = createCache();
    AppCache.key = cacheKey || Date.now().toString();
}

發送緩存spa

// 經過路由傳遞緩存
$scope.submit = function () {
    var queryParam = angular.extend({
        'cache_key': AppCache.key
    }, $routeParams);

    $location.search(queryParam);
}

解決內存泄露

  • 在 $routeChangeSuccess 事件中清理緩存
  • 避免在控制器中建立緩存(解除閉包)

清理過時緩存prototype

$rootScope.$on('$routeChangeSuccess', function () {
    if ($routeParams['cache_key'] === undefined) {
        AppCache.cache = {};
    }
})

封裝 RouteCache 服務

高度抽象,屏蔽實現細節

API 設計(初版)

// 讀緩存
var routeCache = RouteCache(createCache);
var data = routeCache.getCache();
var cacheKey = routeCache.getKey();

// 經過路由傳遞緩存
$scope.submit = function () {
    var queryParam = angular.extend({
        'cache_key': cacheKey
    }, $routeParams);
    $location.search(queryParam);
}

API 設計(優化後)

// 讀緩存
var data = RouteCache(createCache);

// 經過路由傳遞緩存
$scope.submit = function () {
    var queryParam = angular
         .extend({}, data, $routeParams);
    $location.search(queryParam);
}

問題:如何作到 URL 只顯示 cache_key 而不暴露數據?

答案:使用原型繼承,angular.extend 不會拷貝原型。

RouteCache 內部:

data = createCache();
data = Object.create(data);
data['cache_key'] = cacheKey;

Object.create(data) 是 ECMA5 增長的方法,原理相似:

Object.create = function (object) {
    function F(){};
    F.prototype = object;
    return new F();
}

RouteCache 服務完整源碼

/*
 * 基於路由的緩存服務
 * 能夠將任何數據模型緩存在路由參數中,適合處理跨頁的數據傳遞
 *
 *  取緩存:
 *      $scope.data = RouteCache(cacheFactory);
 *  寫緩存:
 *      $location.search(
 *          angular.extend(
*               {},
 *              $routeParams,
 *              $scope.data
 *          )
 *      );
 *
 * @author  糖餅
 */
define(['./services'], function (services) {

    services.factory('RouteCache', ['$rootScope', '$routeParams', '$cacheFactory',
        function ($rootScope, $routeParams, $cacheFactory) {

        var cache = $cacheFactory('RouteCache');
        var ROUTE_KEY = '@cache_key';
        var TABLE_NAME = 'CACHE';


        /*
         * @param   {Function}  緩存工廠
         * @return  {Object}    繼承自緩存的對象
         */
        function Cache (cacheFactory) {

            var data = cache.get(TABLE_NAME);
            var routeKey = $routeParams[ROUTE_KEY];


            if (!data || cache.get(ROUTE_KEY) !== routeKey) {

                data = cacheFactory();

                // 繼承緩存
                data = Object.create(data);

                cache.put(TABLE_NAME, data);
                cache.put(ROUTE_KEY, routeKey || Date.now().toString());
            }


            data[ROUTE_KEY] = cache.get(ROUTE_KEY);

            return data;
        };


        // 自動清理緩存
        $rootScope.$on('$routeChangeSuccess', function () {
            if (typeof $routeParams[ROUTE_KEY] === 'undefined') {
                cache.removeAll();
            }
        });


        return Cache;
    }]);

});
相關文章
相關標籤/搜索