原文: http://www.cnblogs.com/whitewolf/p/3493362.html
这篇国外的文章也非常好: http://codetunnel.io/angularjs-controller-as-or-scope/
有些人觉得即使这样我们的controller还是不够POJO,以及对于coffescript爱好者不足够友好,所以在angular在1.2给我带来了一个新的语法糖这就是本文将要说的controller as的语法糖,修改上面的demo将会变成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
angular.module( "app" ,[]) .controller( "demoController" ,[ function (){ this .title = "angualr" ; }]) <div ng-app= "app" ng-controller= "demoController as demo" > hello : {{demo.title}} ! </div> |
这里我们可以看见现在controller不再有$scope的注入了,感觉controller就是一个很简单的平面的JavaScript对象了,不存在任何的差别了。再则就是view上多增加了个demoController as demo,给controller起了一个别名,在此后的view模板中靠这个别名来访问数据对象。
或许看到这里你会问为什么需要如此啊,不就是个语法糖而已,先别着急,我们会在后边分析$scope和他的差别。在此之前我们先来看看angular源码的实现这样才会有助于我们的分析:
下面是一段来自angular的code:在1499行开始(行数只能保证在写作的时候有效)
1
2
3
4
5
|
if (directive.controllerAs) { locals.$scope[directive.controllerAs] = controllerInstance; } |
如果你希望看更完全的code请猛击这里https://github.com/angular/angular.js/blob/c7a1d1ab0b663edffc1ac7b54deea847e372468d/src/ng/compile.js.
从上面的代码我们能看见的是:angular只是把controller这个对象实例以其as的别名在scope上创建了一个新的对象属性。靠,就这么一行代码搞定!
___________________________________________________________________________________________________________________________
原文: http://pinkyjie.com/2015/02/09/controller-as-vs-scope/
用$scope还是用controller as
AngularJS中在处理controller时提供了两种语法。
- 第一种是,在DOM中使用
ng-controller="TestController"
,这样在定义controller时需要将model绑定到$scope上。 - 另一种是,在DOM中使用
ng-controller="TestController as test"
,这样其实是将model直接绑定到controller的实例上。
在AngularJS的官方Get Started以及各种文档中,多推荐第一种方式,导致很多人可能都不知道原来还有第二种方式,我也是最近看一篇文章时才注意到这个。那么这两种方式各有什么优劣势呢?在现实的开发中到底更推荐哪种方式呢?今天就来探究一下!
controller as方式
$scope方式就不详细说了,大家应该最常用这种吧,看下面这段简单的代码。
对应版本的controller as方式如下:
在controller as方式中,可以给controller起别名,上面的例子中别名是ctrl
。对比这两个例子,可以明显的看到controller as有两个不同的地方:
- 在HTML中,所有的绑定都需要写别名,即需要使用点运算符
ctrl.
- 在JS中,controller的定义可以抛开
$scope
了,也就是说controller可以不依赖$scope
了。
下面就从这两个区别出发去谈谈controller as的好处。
所有model都需要绑定在ctrl
上
首先有必要澄清下,这个别名是怎么实现的呢?使用AngularJS在Chrome上的调试插件AngularJS Batarang可以很清楚的看出来。安装好插件后打开上面的例子,右击页面“审查元素”打开Chrome的DevTools,在Elements标签里选中<div ng-controller="scopeController as ctrl" class="ng-scope">
这一行,然后点击右边的$scope标签(就是和Styles,Computed在一行的,看不到的话点击右边的小箭头),结果就是这个DOM元素所对应的$scope
,如下图:
原来别名ctrl
就是定义在$scope
上的一个对象,这就是controller的一个实例,所有在JS中定义controller时绑定到this
上的model其实都是绑定到$scope.ctrl
上的,看到这里你想到了什么?是不是和上篇文章AngularJS中scope基于原型链的继承里的$scope.data
有异曲同工之妙。所以,使用controller as的一大好处就是原型链继承给scope带来的问题都不复存在了,即有效避免了在嵌套scope的情况下子scope的属性隐藏掉父scope属性的情况。
可以发现,无论定义controller时有没有直接依赖
$scope
,DOM中的scope是始终存在的。即使使用controller as,双向绑定还是通过$scope
的watch以及digest来实现的。
另外,使用别名还有一个显而易见的好处:指代清晰。在嵌套scope时,子scope如果想使用父scope的属性,只需简单的使用父scope的别名引用父scope即可。比如下面这个例子,我们将上篇文章的例子用controller as重写。
这里我想让子scope里直接指向父scope的属性,只需在DOM绑定model时写上parent.myName
即可,简单明了,看代码的一下就懂了,也不用费劲去推到底这里指向的是哪个属性了。如果你的嵌套多达四五层,那这种写法的优势就一下子体现出来了。
controller的定义不依赖$scope
定义controller时不用显式的依赖$scope
,这有什么好处呢?仔细看定义,这不就是一个普通的函数定义嘛,对!这就是好处!例子中的ScopeController
就是所谓的POJO(Plain Old Javascript Object,Java里偷来的概念),这样的Object与框架无关,里面只有逻辑。所以即便有一天你的项目不再使用AngularJS了,依然可以很方便的重用和移植这些逻辑。另外,从测试的角度看,这样的Object也是单元测试友好的。单元测试强调的就是孤立其他依赖元素,而POJO恰恰满足这个条件,可以单纯的去测试这个函数的输入输出,而不用费劲的去模拟一个假的$scope
。
另外,还有一个比较牵强的好处:防止滥用$scope
的$watch
,$on
,$broadcast
方法。可能刚刚就有人想问了,不依赖$scope
我怎么watch一个model,怎样广播和响应事件。答案是没法弄,这些事还真是只有$scope
能干。但很多时候在controller里watch一个model是很多余的,这样做会明显的降低性能。所以,当你本来就依赖$scope
的时候,你会习惯性的调用这些方法来实现自己的逻辑。但当使用controller as的时候,由于没有直接依赖$scope
,使用watch前你会稍加斟酌,没准就思考到了别的实现方式了呢。
定义route时也能用controller as
除了在DOM中显式的指明ng-controller
,还有一种情况是controller的绑定是route里定义好的,那这时能使用controller as吗?答案是肯定的,route提供了一个controllerAs
参数:
1
2
3
4
5
6
|
$routeProvider
.when('/', {
templateUrl: 'partial/home.html',
controller: 'HomeCtrl',
controllerAs: 'home'
})
|
这样在模板里就可以直接使用别名home
啦。
结论
总结下来,个人觉得还是偏向于使用controller as的,当然有一点要澄清,使用contoller as并没有什么性能上的提升,仅仅是一种好的习惯罢了。
————————————————————————————————————————————————————————————————————————————————
AngularJS: "Controller as" or "$scope"?
10 JULY 2014
I just finished reading a blog post by John Papa. He talks about the trend of using Controller as someName
instead of injecting $scope
into your controller. I wanted to expound on his point, but first let's demonstrate this technique for those of you who haven't heard of it yet. It's a pretty simple feature, but I think it has even more useful implications than what John covered.
Traditionally you're probably used to doing something like this:
<div ng-controller="MainController">
{{ someObj.someProp }}
</div>
app.controller('MainController', function ($scope) {
$scope.someObj = {
someProp: 'Some value.'
};
});
With the new Controller as
technique you can now do something like this:
<div ng-controller="MainController as main">
{{ main.someProp }}
</div>
app.controller('MainController', function () {
this.someProp = 'Some value.'
});
John Papa points out in his post that the only real difference between the two is preference. He talks about the Controller as
technique as "syntactic sugar" that you can use if you want to. I mostly agree with him, but I also think this solves some of the more complicated problems I've run into with Angular before.
One such problem has to do with the way scopes inherit prototypically. In JavaScript when one object inherits from another prototypically, you are able to access all the properties and methods from the parent object.
var obj1 = {
someProp: 'obj1 property!',
someMethod: function () {
alert('obj1 method!');
}
};
var obj2 = Object.create(obj1);
obj2.someProp = 'obj2 property!';
You might think from the above code that someProp
was "changed" from "obj1 property!" to "obj2 property!" when obj2
was instantiated. However, you can't change properties on an object's prototype like that. All that code did was create a property called someProp
on obj2
that masks the value of the underlying someProp
on obj1
. If you run delete obj2.someProp
then someProp
won't be gone, it will revert to showing the value of obj1.someProp
. This is the way nested scopes work. Setting a property on a child scope does not change the property with the same name on the parent scope; it merely hides it.
There's a good reason why my example of the classic $scope
technique assigns someProp
to a new object called someObj
. If my parent scope has a property called foo
and I want to change it on my child scope, then foo
must be a property on an object on the parent scope. Since objects are passed by reference, changing a property on an object attached to the parent scope actually does modify that object's property; it's only the property representing the object itself that we would end up masking if we set it on our child scope.
Here's an example of what happens when you nest controllers and use scalar values on the $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>
app
.controller('ParentController', function ($scope) {
$scope.foo = "bar";
})
.controller('ChildController', function ($scope) { /*empty*/ });
Initially the child scope has no property called foo
. Instead it's reading from the inherited foo
property from the parent scope. This is why the child input updates when you change the parent input. However, once you modify the child input, it uses its value and updates foo
on the child scope. Because of the way prototypal inheritance works, the child foo
property is merely maskingthe parent foo
property. In other words, foo
on the parent scope remains unchanged while foo
on the child scope has been changed to a new value. Once you've modified the child input, modifying the parent input does nothing to the child one anymore because the child scope now has its own foo
property with a value.
The problem with scope inheritance is one I see newbies run into constantly on Stack. Traditionally, the fix is to do what I did in my first example and attach your scalar values to objects on the scope.
<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>
app
.controller('ParentController', function ($scope) {
$scope.obj = {
foo: "bar"
};
})
.controller('ChildController', function ($scope) { /*empty*/ });
You can see that the inputs properly update each other now. It just sucks that we have to use such a strange setup just to avoid this issue. Now with the addition of the new Controller as
technique we don't have to worry anymore! We can simply refer to the controller we wish to refer to and stop worrying about the subtleties of scope inheritance.
<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>
app
.controller('ParentController', function () {
this.foo = "bar";
})
.controller('ChildController', function () { /*empty*/ });
Not only does this help bypass this annoying inheritance issue, I think it makes the markup even cleaner than when we used $scope
. You can clearly see, even in the DOM managed by the child controller, that we're explicitly binding things to a value on the parent controller. We no longer have to do any of this $scope.$parent
nonsense or create complicated services and inject them all over. Now you can just simply refer to the controller and the value that you intended to refer to in the first place :D
It's important to keep in mind what Angular is actually doing with this new syntax. It's not some magical global variable that you can refer to from anywhere; it's just a variable that refers to that controller's execution contextand that variable is attached to $scope
behind the scenes.
Essentially, this:
app.controller('MyController', function () {
this.someValue = "Hello!";
});
Is no different from this:
app.controller('MyController', function ($scope) {
$scope.myController = this;
this.someValue = "Hello!";
}
It's just that the "as" syntax implicitly creates a namespace on the controller's scope, whereas you must create a namespace manually in the latter example. Don't believe me? Here's the proof :)
As you can see, this
and $scope.myController
are the same object in the above example. That gives you a big clue to what Angular is doing behind the scenes. It merely attached the controller variable to the $scope
, implicitly giving us a very readable and logical namespace for the values we'd like to expose. What that also means is that if we overrode the child scope with a property called myController
, it would still mask the myController
property implicitly defined on the parent scope. So as long as you aren't setting variables on your child scopes to the same value that you used in your "controller as someName" on the parent scope, the whole inheritance issue is kind of hidden from you and helps prevent you from making mistakes as easily.