AngularJS 控制器
·
AngularJS 控制器 (Controller) 学习笔记
控制器是 AngularJS 应用的核心组件之一,负责初始化应用状态、定义行为逻辑,并作为视图(HTML)和模型(Scope)之间的桥梁。
一、控制器的基本概念
1. 什么是控制器?
- 定义:JavaScript 构造函数,用于初始化
$scope对象。 - 作用:
- 设置初始状态(数据)。
- 定义行为(方法)。
- 响应视图中的用户交互。
- 生命周期:当 AngularJS 遇到
ng-controller指令时创建,当 DOM 元素被销毁时销毁。
2. 定义控制器的两种方式
方式 A:全局注册(推荐用于简单应用)
var app = 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) {
var vm = this; // 使用 vm (ViewModel) 作为 this 的别名
vm.user = { name: "John", age: 25 };
vm.save = function() {
$http.post('/api/save', vm.user);
};
});
<!-- 视图中使用 -->
<div ng-controller="UserCtrl as userCtrl">
<input ng-model="userCtrl.user.name">
<button ng-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 属性(推荐,代码整洁)
function MyCtrl($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;
};
});
// 控制器 A
app.controller('CtrlA', function($scope, SharedService) {
$scope.update = function() {
SharedService.updateData("New Data");
};
});
// 控制器 B
app.controller('CtrlB', function($scope, SharedService) {
$scope.$watch(function() {
return SharedService.data;
}, function(newVal) {
$scope.sharedData = newVal;
});
});
七、最佳实践
1. 保持控制器轻量
- 控制器只负责视图逻辑。
- 业务逻辑、数据访问放入 Service。
- DOM 操作放入 Directive。
// ❌ 糟糕的控制器
app.controller('BadCtrl', function($scope, $http) {
$scope.fetchData = function() {
// 复杂的业务逻辑
// 直接操作 DOM
document.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 应用。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)