angularjs 中 controller as 引入的意义
24 September 2015

这又是一篇历史遗留博文。

去年学习angularjs时controller用的都是$scope,在$scope下进行数据双向绑定,那时的angular版本号已记不清了,今年做项目时(angular v1.3.13),看到官网文档里的用法是ng-controller = "controllerName as XXX",然后controller.js中用了类似下面的代码:

var vm = this;
this.avalue = 'XXX';

觉得好奇为什么会引入这种用法,于是研究了一下。

$scope和this究竟是什么

v1.2.0之前的angular:

// <div ng-controller="MainCtrl"></div>
app.controller('MainCtrl', function ($scope) {
  $scope.title = 'Some title';
});

v1.2.0之后的:

app.controller('MainCtrl', function () {
  var vm = this;
  vm.title = 'Some title';
});

让我们回忆一下js中的构造函数和实例,也就是“类”:

var myClass = function () {
  this.title = 'Class title';
}
var myInstance = new myClass();

现在我们可以使用myInstance示例来访问myClass的属性和方法。

在angular中:

// we declare as usual, just using the `this` Object instead of `$scope`
app.controller('MainCtrl', function () {
  var vm = this;
  vm.title = 'Some title';
});

<div ng-controller="MainCtrl as main">
  // MainCtrl doesn't exist, we get the `main` instance only
  {{ main.title }}
</div>

app.controller('MainCtrl',function(){})就是在定义一个名称为MainCtrl的构造函数,MainCtrl as main就是在实例化,生成MainCtrl的实例main,然后就可以在main中访问MainCtrl里定义的变量和函数。

但是,Controller as的语法是将controller绑定了当前的$scope,而不是都绑向同一个$scope变量。

注意事项

一些scope的监听器和方法,例如:$watch, $broadcast, $on等,只能在$scope下调用。

controller as应用方式

除上面的方式外,还有:

  • directive:

      app.directive('myDirective', function () {
        return {
          restrict: 'EA',
          replace: true,
          scope: true,
          template: [].join(''),
          controllerAs: '', // woohoo, nice and easy!
          controller: function () {}, // we'll instantiate this controller "as" the above name
          link: function () {}
        };
      });
    
  • $routeProvide

      app.config(function ($routeProvider) {
        $routeProvider
        .when('/', {
          templateUrl: 'views/main.html',
          controllerAs: '',
          controller: ''
        })
        .otherwise({
          redirectTo: '/'
        });
      });
    
  • ui-rooter

      app.config(function ($routeProvider) {
         $stateProvider
         .state('tab.history', {
           url: '/history',
           views:{
             'personInfo':{
               templateUrl: 'history.html',
               controller: 'historyCtrl as history'
             }
           }
         })
      })
    

controller as让继承关系的变量之间更清晰可读

感受下:

  • 老版引用同名的各自的变量:

      <div ng-controller="MainCtrl">
        {{ title }}
        <div ng-controller="AnotherCtrl">
          {{ title }}
          <div ng-controller="YetAnotherCtrl">
            {{ title }}
          </div>
        </div>
      </div>
    
  • 新版引用同名的各自的变量:

      <div ng-controller="MainCtrl as main">
        {{ main.title }}
        <div ng-controller="AnotherCtrl as another">
          {{ another.title }}
          <div ng-controller="YetAnotherCtrl as yet">
            {{ yet.title }}
          </div>
        </div>
      </div>
    

在感受下

  • 老版引用父变量

      <div ng-controller="MainCtrl">
        {{ title }}
        <div ng-controller="AnotherCtrl">
          Scope title: {{ title }}
          Parent title: {{ $parent.title }}
          <div ng-controller="YetAnotherCtrl">
            {{ title }}
            Parent title: {{ $parent.title }}
            Parent parent title: {{ $parent.$parent.title }}
          </div>
        </div>
      </div>
    
  • 新版引用父变量

    {{ main.title }}
    Scope title: {{ another.title }} Parent title: {{ main.title }}
    Scope title: {{ yet.title }} Parent title: {{ another.title }} Parent parent title: {{ main.title }}

controller as 方式解决了父子$scope带来的混乱

具体问题可以看AngularJS: “Controller as” or “$scope”?

$scope方式下:

<div ng-controller="ParentController">  
  ParentController: <input type="text" ng-model="foo" />
  <div ng-controller="ChildController">
    ChildController: <input type="text" ng-model="foo" />
  </div>
</div> 

<script>
	app  
	  .controller('ParentController', function ($scope) {
	    $scope.foo = "bar";
	  })
	  .controller('ChildController', function ($scope) { /*empty*/ });
</script>

会产生父变字变,子边父不变的现象。这是由于原型链的缘故,当一个对象的原型链上有属性foo,你再给这个对象赋上一个属性foo,并不会它本来原型链上的属性foo。只是新建的一个foo,在原型链的更近端,访问这个属性时不会在去寻找更远处的foo属性。

这就相当于:

var obj1 = {  
  someProp: 'obj1 property!',
  someMethod: function () {
    alert('obj1 method!');
  }
};
var obj2 = Object.create(obj1);  
obj2.someProp = 'obj2 property!';  

因此在父子继承关系下的属性必须明确指出对象已防止混乱。譬如我们要访问父对象的foo属性应该:

<div ng-controller="ParentController">  
  ParentController: <input type="text" ng-model="foo" />
  <div ng-controller="ChildController">
    ChildController: <input type="text" ng-model="$parent.foo" />
  </div>
</div>

<script>
	app  
	  .controller('ParentController', function ($scope) {
	    $scope.foo = "bar";
	  })
	  .controller('ChildController', function ($scope) { /*empty*/ });
</script>

也可以借助对象传引用的方式来变态实现(个人觉得这个方法实在是曲线救国,但是controller as正是利用了这个原理):

<div ng-controller="ParentController">  
  ParentController: <input type="text" ng-model="obj.foo" />
  <div ng-controller="ChildController">
    ChildController: <input type="text" ng-model="obj.foo" />
  </div>
</div> 

<script>
	app  
	  .controller('ParentController', function ($scope) {
	    $scope.obj = {
	      foo: "bar"
	    };
	  })
	  .controller('ChildController', function ($scope) { /*empty*/ });
</script>  

使用controller as方式可以简化代码,让关系更清晰:

<div ng-controller="ParentController as parent">  
  ParentController: <input type="text" ng-model="parent.foo" />
  parent.foo: {{ parent.foo }}
  <div ng-controller="ChildController as child">
    ChildController: <input type="text" ng-model="parent.foo" />
    parent.foo: {{ parent.foo }}
  </div>
</div> 

<script>
	app  
	  .controller('ParentController', function () {
	    this.foo = "bar";
	  })
	  .controller('ChildController', function () { /*empty*/ });
</script>

controller as的实质

上面的代码已经可以推测实质了,controller as只是语法糖,

app.controller('MyController', function () {  
  this.someValue = "Hello!";
});

等价于:

app.controller('MyController', function ($scope) {  
  $scope.myController = this;
  this.someValue = "Hello!";
}

除了在父子contrllor之间,controllerAs的写法能比较方便的切换父子对象,其他的只要遵守不把变量直接挂在$scope上,而是挂在$scope的对象上,那么继续用$scope也未尝不可,就是代码量上多一些 objCtrl as obj和this.namej和this.namej和this.name = ‘lili’也是angularjs自己内部创建了obj的对象挂在$scope上,即:$scope.obj = { name=’lili’ }

以上是我初次接触controller as时的想法,当初并不当回事。但是后来我将所有业务代码都用了controller as方式,语法糖也是很必要的,当代码量大的时候,语法糖节省的是你的时间,提高的是你的效率,理清的是你的思路。

黄金外链

Digging into Angular’s “Controller as” syntax

AngularJS: “Controller as” or “$scope”?