自去年开始,我将 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(); }
发送缓存
// 通过路由传递缓存 $scope.submit = function () { var queryParam = angular.extend({ 'cache_key': AppCache.key }, $routeParams); $location.search(queryParam); }
解决内存泄露
在
$routeChangeSuccess
事件中清理缓存避免在控制器中创建缓存(解除闭包)
清理过期缓存
$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; }]); });
作者:裕波