第八章:Angular的崛起——MVC的革新

“双向数据绑定?!”

林小凡盯着屏幕上的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)文档上,SignalHydration等陌生词汇闪烁着微光。

他突然明白:AngularJS就像第一台蒸汽机——笨重、低效,但拉开了工业革命的序幕。

第八章 完


下一章预告:
第九章:React的闪电战——虚拟DOM的奇迹
“当Angular开发者第一次看到JSX时,他们以为Facebook疯了。”——某Reddit网友