AngularJS 控制器 (Controller) 学习笔记
控制器是 AngularJS 应用的核心组件之一,负责初始化应用状态、定义行为逻辑,并作为视图(HTML)和模型(Scope)之间的桥梁。
一、控制器的基本概念
1. 什么是控制器?
- 定义:JavaScript 构造函数,用于初始化
$scope对象。 - 作用:
- 设置初始状态(数据)。
- 定义行为(方法)。
- 响应视图中的用户交互。
- 生命周期:当 AngularJS 遇到
ng-controller指令时创建,当 DOM 元素被销毁时销毁。
2. 定义控制器的两种方式
方式 A:全局注册(推荐用于简单应用)
varapp=angular.module('myApp',[]);// 定义控制器app.controller('MyController',function($scope,$http){$scope.message="Hello World";$scope.users=[];// 方法$scope.addUser=function(name){$scope.users.push({name:name});};});方式 B:模块内匿名定义(不推荐,难以测试)
angular.module('myApp',[]).controller('MyController',function($scope){$scope.data="Test";});二、控制器语法风格
1.$scope语法(传统)
app.controller('UserCtrl',function($scope,$http){$scope.user={name:"John",age:25};$scope.save=function(){$http.post('/api/save',$scope.user);};});- 优点:简单直观,适合小型应用。
- 缺点:容易混淆
$scope和this,在嵌套作用域中容易出错。
2.ControllerAs语法(推荐,AngularJS 1.3+)
app.controller('UserCtrl',function($http){varvm=this;// 使用 vm (ViewModel) 作为 this 的别名vm.user={name:"John",age:25};vm.save=function(){$http.post('/api/save',vm.user);};});<!-- 视图中使用 --><divng-controller="UserCtrl as userCtrl"><inputng-model="userCtrl.user.name"><buttonng-click="userCtrl.save()">保存</button></div>- 优点:
- 避免原型继承陷阱。
- 代码更清晰,易于阅读和维护。
- 便于单元测试(不依赖
$scope)。 - 支持 ES6 Class 语法。
3. 混合使用(不推荐)
// ❌ 避免混用app.controller('BadCtrl',function($scope){this.data="Bad";$scope.other="Worse";});三、依赖注入 (Dependency Injection)
控制器通过依赖注入获取服务(Services)、过滤器(Filters)等。
1. 隐式注入(不推荐,压缩后失效)
app.controller('MyCtrl',function($scope,$http){...});// 压缩后变成 function(a, b) { ... },Angular 无法识别参数名2. 数组注解法(推荐,生产环境标准)
app.controller('MyCtrl',['$scope','$http',function($scope,$http){// 逻辑代码}]);3. 显式$inject属性(推荐,代码整洁)
functionMyCtrl($scope,$http){// 逻辑代码}MyCtrl.$inject=['$scope','$http'];app.controller('MyCtrl',MyCtrl);四、控制器的作用域与继承
1. 作用域链
- 控制器创建一个新的
$scope对象。 - 子控制器继承父控制器的
$scope(原型链继承)。 - 注意:在子 Scope 中修改对象属性是安全的,但修改原始类型(字符串、数字)会创建新属性,破坏继承链。
// 父控制器app.controller('ParentCtrl',function($scope){$scope.data={value:10};// 对象$scope.count=5;// 原始类型});// 子控制器app.controller('ChildCtrl',function($scope){$scope.data.value=20;// ✅ 修改对象属性,影响父级$scope.count=10;// ❌ 创建新属性,不影响父级});2. 隔离作用域(指令中)
- 指令可以使用
scope: {}创建隔离作用域,不继承父控制器。 - 控制器通常不直接创建隔离作用域,而是通过指令实现。
五、控制器生命周期钩子
AngularJS 控制器没有像 Vue 那样的显式生命周期钩子,但可以通过以下方式模拟:
1. 初始化逻辑
直接在构造函数中执行。
app.controller('InitCtrl',function($scope,$http){// 初始化$scope.loadData=function(){$http.get('/api/data').then(function(response){$scope.data=response.data;});};// 立即执行$scope.loadData();});2. 销毁钩子
监听$destroy事件。
app.controller('DestroyCtrl',function($scope){$scope.$on('$destroy',function(){// 清理定时器、事件监听器等console.log('控制器销毁');});});六、控制器通信方式
1. 父子控制器通信
- 父传子:通过
$scope属性绑定。 - 子传父:通过
$scope.$emit或回调函数。
// 父控制器app.controller('ParentCtrl',function($scope){$scope.parentData="Parent";$scope.onChildEvent=function(data){console.log("收到子级事件:",data);};});// 子控制器app.controller('ChildCtrl',function($scope){$scope.sendToParent=function(){$scope.$emit('childEvent',"Hello Parent");};$scope.$on('childEvent',function(event,data){$scope.parentData=data;// 注意:这里需要处理作用域});});2. 兄弟控制器通信
- 通过
$rootScope或共享服务(Service)。 - 推荐:使用 Service 作为单例共享数据。
// 共享服务app.service('SharedService',function(){this.data="Shared Data";this.updateData=function(newData){this.data=newData;};});// 控制器 Aapp.controller('CtrlA',function($scope,SharedService){$scope.update=function(){SharedService.updateData("New Data");};});// 控制器 Bapp.controller('CtrlB',function($scope,SharedService){$scope.$watch(function(){returnSharedService.data;},function(newVal){$scope.sharedData=newVal;});});七、最佳实践
1. 保持控制器轻量
- 控制器只负责视图逻辑。
- 业务逻辑、数据访问放入Service。
- DOM 操作放入Directive。
// ❌ 糟糕的控制器app.controller('BadCtrl',function($scope,$http){$scope.fetchData=function(){// 复杂的业务逻辑// 直接操作 DOMdocument.getElementById('div').style.color='red';};});// ✅ 优秀的控制器app.controller('GoodCtrl',function($scope,DataService){$scope.data=[];$scope.fetchData=function(){DataService.getData().then(function(result){$scope.data=result;});};});2. 使用ControllerAs语法
- 提高代码可读性。
- 避免
$scope陷阱。 - 便于测试。
3. 避免在控制器中直接操作 DOM
- 使用
ng-class,ng-style,ng-show等指令。 - 复杂 DOM 操作使用Directive。
4. 错误处理
app.controller('ErrorCtrl',function($scope,$http){$scope.fetchData=function(){$http.get('/api/data').then(function(response){$scope.data=response.data;}).catch(function(error){$scope.error="加载失败: "+error.message;});};});5. 内存泄漏预防
- 在
$destroy事件中清理定时器、事件监听器。
$scope.$on('$destroy',function(){clearInterval($scope.timer);$scope.$off('customEvent');});八、常见陷阱与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 数据不更新 | 异步回调中未触发$apply | 使用$timeout或$scope.$apply() |
| 原型继承污染 | 直接修改原始类型属性 | 使用对象包裹数据 ($scope.data = {}) |
| 控制器重复初始化 | 指令重复使用ng-controller | 检查 HTML 结构,避免嵌套重复 |
| 内存泄漏 | 未清理定时器或事件监听 | 在$destroy事件中清理 |
| 测试困难 | 依赖$scope隐式注入 | 使用ControllerAs+ 数组注解法 |
九、总结
AngularJS 控制器的核心要点:
- 职责单一:只处理视图逻辑,业务逻辑交给 Service。
- 语法选择:优先使用
ControllerAs语法,避免$scope陷阱。 - 依赖注入:使用数组注解法或
$inject属性,确保代码可压缩。 - 通信机制:父子通过
$scope,兄弟通过 Service 或$rootScope。 - 生命周期:利用
$destroy事件清理资源,防止内存泄漏。 - 测试友好:保持控制器轻量,便于单元测试。
通过遵循这些最佳实践,可以构建出结构清晰、易于维护的 AngularJS 应用。