AngularJS 服务(Service)
AngularJS 服务 (Service) 学习笔记服务Service是 AngularJS 中用于封装业务逻辑、数据访问和通用功能的组件。它是实现关注点分离Separation of Concerns和依赖注入Dependency Injection的核心机制。一、服务的核心概念1. 什么是服务定义单例 JavaScript 对象或构造函数用于在应用的不同部分之间共享数据和逻辑。特性单例模式整个应用中只创建一个实例所有注入该服务的组件共享同一份数据。生命周期应用启动时创建应用销毁时销毁。可测试性易于进行单元测试可轻松 Mock。用途数据访问API 调用。业务逻辑处理。工具函数日期格式化、字符串处理。事件总线发布/订阅。2. 服务 vs 控制器 vs 过滤器组件职责生命周期实例数量Controller视图逻辑、初始化 Scope随 DOM 创建/销毁多个每个视图一个Service业务逻辑、数据共享应用生命周期单例全局唯一Filter数据格式化按需调用无状态纯函数二、创建服务的五种方式AngularJS 提供了多种创建服务的方式核心区别在于实例化方式和适用场景。1.factory最常用推荐原理调用工厂函数返回其返回值作为服务实例。适用需要复杂初始化逻辑、返回对象或函数时。app.factory(UserService,function($http,$q){varservice{};varusers[];service.getAllUsersfunction(){return$http.get(/api/users).then(function(res){usersres.data;returnusers;});};service.getUserByIdfunction(id){return$q(function(resolve,reject){varuserusers.find(uu.idid);user?resolve(user):reject(Not found);});};returnservice;// 返回服务对象});2.service构造函数风格原理使用new关键字调用构造函数this指向新实例。适用面向对象风格需要继承或原型方法时。app.service(ProductService,function($http){this.products[];this.fetchProductsfunction(){return$http.get(/api/products).then(function(res){this.productsres.data;}.bind(this));};this.addProductfunction(product){this.products.push(product);};});注意this关键字在方法中指向服务实例但需注意作用域问题。3.provider最灵活可配置原理通过$get方法返回服务实例可在模块配置阶段config进行配置。适用需要在应用启动前配置服务参数如 API 基础 URL。app.provider(ConfigService,function(){varbaseUrlhttp://localhost:8080;// 配置阶段调用this.setBaseUrlfunction(url){baseUrlurl;};// 运行时调用this.$getfunction($http){return{getData:function(){return$http.get(baseUrl/data);}};};});// 在 config 块中配置app.config(function(ConfigServiceProvider){ConfigServiceProvider.setBaseUrl(http://api.example.com);});4.value简单值原理直接注入一个值字符串、数字、对象。适用常量配置、简单数据。app.value(API_URL,http://api.example.com);app.value(VERSION,1.0.0);app.value(DEFAULT_CONFIG,{timeout:5000});5.constant常量不可修改原理类似value但可在config块中使用且不可被修改。适用应用级常量。app.constant(APP_NAME,MyApp);app.constant(MAX_RETRY,3);app.config(function(APP_NAME,MAX_RETRY){console.log(APP_NAME);// 可用});三、服务的依赖注入服务可以注入其他服务、过滤器、内置服务如$http,$q。1. 隐式注入不推荐app.factory(MyService,function($http,$q){...});// 压缩后参数名丢失导致注入失败2. 数组注解法推荐app.factory(MyService,[$http,$q,function($http,$q){// 逻辑代码}]);3.$inject属性functionMyService($http,$q){// 逻辑代码}MyService.$inject[$http,$q];app.factory(MyService,MyService);四、服务的应用场景1. 数据访问层API 封装app.factory(ApiService,function($http){varbaseUrl/api;return{get:function(url,params){return$http.get(baseUrlurl,{params:params});},post:function(url,data){return$http.post(baseUrlurl,data);},put:function(url,data){return$http.put(baseUrlurl,data);},delete:function(url){return$http.delete(baseUrlurl);}};});// 在控制器中使用app.controller(UserCtrl,function($scope,ApiService){ApiService.get(/users).then(function(res){$scope.usersres.data;});});2. 状态管理共享数据app.factory(CartService,function(){varcart[];return{addItem:function(item){cart.push(item);},removeItem:function(id){cartcart.filter(ii.id!id);},getItems:function(){returncart;},clear:function(){cart[];}};});3. 工具函数库app.factory(UtilsService,function(){return{formatDate:function(date){// 格式化逻辑},validateEmail:function(email){return/^[^\s][^\s]\.[^\s]$/.test(email);},debounce:function(func,wait){// 防抖逻辑}};});4. 事件总线发布/订阅app.factory(EventService,function($rootScope){return{emit:function(event,data){$rootScope.$emit(event,data);},broadcast:function(event,data){$rootScope.$broadcast(event,data);},on:function(event,callback){return$rootScope.$on(event,callback);}};});五、服务与控制器通信1. 控制器注入服务app.controller(MyCtrl,function($scope,UserService){$scope.users[];UserService.getAllUsers().then(function(users){$scope.usersusers;});$scope.addUserfunction(user){UserService.addUser(user).then(function(){$scope.users.push(user);});};});2. 服务间通信服务之间可以直接注入其他服务。避免循环依赖A 依赖 BB 依赖 A。3. 服务与指令通信app.directive(myDirective,function($scope,DataService){return{link:function(scope,element,attrs){vardataDataService.getData();// 使用数据}};});六、最佳实践1. 保持服务无状态或最小状态服务应专注于逻辑避免存储大量状态。状态应存储在模型或$rootScope不推荐中。2. 返回 Promise异步操作应返回$q或$http的 Promise。避免在回调中直接操作 DOM 或 Scope。// ✅ 好service.getDatafunction(){return$http.get(/api/data);};// ❌ 坏service.getDatafunction(callback){$http.get(/api/data).then(function(res){callback(res.data);});};3. 错误处理在服务中统一处理错误或让调用方处理。service.getDatafunction(){return$http.get(/api/data).then(function(res){returnres.data;}).catch(function(error){console.error(Error:,error);throwerror;// 重新抛出让调用方处理});};4. 模块化将服务按功能拆分到不同模块。// services/user.jsangular.module(myApp.services,[]).factory(UserService,...);// services/product.jsangular.module(myApp.services).factory(ProductService,...);// app.jsangular.module(myApp,[myApp.services]);5. 单元测试服务易于测试因为不依赖 DOM 或 Scope。describe(UserService,function(){varUserService,$httpBackend;beforeEach(module(myApp));beforeEach(inject(function(_UserService_,_$httpBackend_){UserService_UserService_;$httpBackend_$httpBackend_;}));it(should fetch users,function(){$httpBackend.expectGET(/api/users).respond([{id:1}]);UserService.getAllUsers().then(function(users){expect(users.length).toBe(1);});$httpBackend.flush();});});七、常见问题与解决方案问题原因解决方案循环依赖A 服务依赖 BB 依赖 A重构逻辑提取公共服务或使用$inject延迟加载数据不同步多个控制器修改同一服务数据使用$watch或事件通知机制内存泄漏服务中持有 DOM 引用或定时器在$destroy事件中清理资源无法注入服务未注册或名称错误检查模块依赖和注册名称异步数据未更新未在$apply上下文中更新 Scope使用 Promise 或$timeout八、总结AngularJS 服务的核心要点单例模式全局唯一实例共享数据。创建方式factory最常用、service构造函数、provider可配置、value/constant常量。依赖注入使用数组注解法或$inject属性。职责分离业务逻辑在 Service视图逻辑在 Controller格式化在 Filter。异步处理返回 Promise避免回调地狱。测试友好易于 Mock 和单元测试。通过合理使用服务可以构建出结构清晰、易于维护和测试的 AngularJS 应用。