自去年开始,AngularJS 引入到项目中,并逐渐推动公司产品核心模块进行重构,提升产品稳定性与开发效率。在前端架构演进的过程中最艰难的不是理解 API,而是思维方式被颠覆的无助,所有繁杂的事务都被高度抽象化,以前 WEB 富 UI 开发最头疼的表现部分放到现在几乎不费吹灰之力,前端工程师重心将由 UI 转向数据模型的构建。在这个过程中,小伙伴经常会遇到跨页数据传递这个经典问题,于是我在公司给组里同事做了一次分享,以下是概要:
业务场景
- 跨页选中操作
- 分步骤操作
问题
- Angular @R_251_404@面后,控制器实例被注销
- Angular 没有提供跨页传递临时数据的特性
可选方案
A. 超级单页
使用同一个控制器与同一份实例,不使用路由
缺点
B. URL传递数据
通过 URL 查询参数传递数据
缺点
D. Ng Service 缓存
使用 Angular Service 构建内存缓存
权衡后,采用此方案。
实践遇到的问题
唯一性难以保证
- 可能使用未清理的缓存引起 BUG
内存泄露
- 过期的缓存得不到清理
- 缓存会连同控制器一起被 Ng Service 持有(闭包的缘故)
基于路由缓存设计
保证唯一性
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; }]); });