“双向数据绑定?!”
林小凡盯着屏幕上的AngularJS教程,手指悬在键盘上方,像第一次见到火的原始人。
视频里,讲师演示着不可思议的魔法:
html
<input ng-model="name">
<p>Hello {{name}}!</p>
没有addEventListener
,没有document.getElementById
,甚至没有$()
——仅仅在HTML里加几个奇怪的属性,页面就自己动了起来。
“这……这不科学!”林小凡猛地灌了一口能量饮料,试图理解眼前的现象。
墨尘的消息适时弹出:
“欢迎来到2012年,jQuery杀手来了。”
第一节:MVC的震撼教育
林小凡接到的第一个Angular项目是改造一个员工管理系统。
当他打开前任留下的代码,映入眼帘的是一个诡异的文件结构:
/app
/controllers
- departmentCtrl.js
- employeeCtrl.js
/directives
- sortableTable.js
/services
- apiService.js
/views
- list.html
- detail.html
“这什么邪教组织?”他皱眉点开employeeCtrl.js
:
javascript
angular.module('hrApp')
.controller('employeeCtrl', function($scope, apiService) {
$scope.employees = [];
$scope.loadData = function() {
apiService.getEmployees().then(function(res) {
$scope.employees = res.data;
});
};
$scope.$watch('searchText', function(newVal) {
// 搜索逻辑
});
});
配套的HTML更诡异:
html
<div ng-controller="employeeCtrl">
<input ng-model="searchText">
<table>
<tr ng-repeat="emp in employees | filter:searchText">
<td>{{emp.name}}</td>
<td>{{emp.department}}</td>
</tr>
</table>
</div>
“ng-repeat
是什么巫术?!”林小凡试着修改$scope.employees
——
页面上的表格自动更新了,没有DOM操作,没有手动渲染。
他仿佛听到jQuery在角落啜泣。
第二节:脏检查的诅咒
当林小凡兴奋地给表格加上“薪资合计”功能时:
html
<tfoot>
<tr>
<td>总计</td>
<td>{{getTotalSalary()}}</td>
</tr>
</tfoot>
并在控制器添加:
javascript
$scope.getTotalSalary = function() {
return $scope.employees.reduce((sum, emp) => sum + emp.salary, 0);
};
页面突然变得卡顿如幻灯片。
“什么情况?!”他打开开发者工具,发现getTotalSalary()
每秒被执行数百次。
墨尘发来一个“我就知道”的表情:
“AngularJS的脏检查机制——它像偏执狂一样不断问你:‘数据变了吗?变了吗?变了吗?’”
解决方案是track by
和手动优化:
html
<tr ng-repeat="emp in employees track by emp.id">
“所以性能优化要靠玄学?”林小凡对着$applyAsync
文档发出灵魂质问。
第三节:指令系统的野望
项目需求突然增加——需要可复用的“星级评分”组件。
林小凡学着文档创建了第一个指令:
javascript
angular.module('hrApp')
.directive('starRating', function() {
return {
restrict: 'E',
scope: {
rating: '=',
max: '@'
},
template: `
<span ng-repeat="i in range track by $index">
{{$index < rating ? '★' : '☆'}}
</span>
`,
link: function(scope) {
scope.range = new Array(parseInt(scope.max));
}
};
});
使用时只需:
html
<star-rating rating="emp.score" max="5"></star-rating>
“这比jQuery插件优雅多了!”他刚感叹完,就发现IE11报错:
Expected identifier
——因为模板字符串(`)不被支持。
“所以还是要用.templateUrl
……”林小凡含泪重写。
第四节:依赖注入的魔法
最让林小凡震撼的是Angular的依赖注入系统:
javascript
angular.module('hrApp')
.controller('employeeCtrl', function($scope, $http, apiService, notificationService) {
// 这些服务会自动注入
});
直到他在代码深处发现这个:
javascript
// 生产环境配置
angular.module('hrApp')
.config(function($provide) {
$provide.decorator('$exceptionHandler', function($delegate) {
return function(exception, cause) {
// 把错误发到服务器
$delegate(exception, cause);
};
});
});
“连错误处理都能改?!”他仿佛看到框架作者邪恶的微笑,“这权力太大了……”
墨尘发来警告:
“小心依赖地狱——当你发现$scope.$parent.$parent.$ctrl
时,项目已经没救了。”
第五节:升级的代价
某天,技术总监突然宣布:
“我们要升级到Angular(2+)!”
林小凡兴奋地打开新文档,然后陷入呆滞:
- 没有
$scope
了(改用@Component
) - 指令语法全变了(
*ngFor
代替ng-repeat
) - 突然要学TypeScript和RxJS
最致命的是——
“不能平滑升级,必须重写!”
当他看到ngUpgrade
方案的复杂性后,终于理解了为什么很多公司选择:
“继续用AngularJS直到世界末日。”
终节:变革的火种
深夜,林小凡在旧项目中找到一个彩蛋。
某位前辈在app.js
顶部写道:
javascript
/*
* 2014年注:这个框架太激进了
* 但未来一定是组件化的天下
* ——留给后来者
*/
窗外,月光照在新打开的Angular(v16)文档上,Signal
、Hydration
等陌生词汇闪烁着微光。
他突然明白:AngularJS就像第一台蒸汽机——笨重、低效,但拉开了工业革命的序幕。
(第八章 完)
下一章预告:
第九章:React的闪电战——虚拟DOM的奇迹
“当Angular开发者第一次看到JSX时,他们以为Facebook疯了。”——某Reddit网友