Think in AngularJS:对比jQuery和AngularJS的不一样思惟模式
大漠穷秋前端
导言
stackoverflow上有一我的问了一个问题:若是我有jQuery背景,我应该如何切换到AngularJS的思惟模式?web
有一个回复很是经典,得到了两千多票。ajax
为了让国内开发者也能领略到其中的核心思想,现把这个问题和答案翻译出来供你们参考。编程
Question
假设我已经熟悉了如何使用jQuery来开发客户端应用,我如今打算使用AngularJS。请描述一下有那些思惟模式方面的东西须要转变吗?下面是举出一些具体的问题,用来帮助你回答个人这个问题:json
- 我应该以何种不一样的方式来架构和设计客户端web应用?最大的不一样点是什么?
- 我应该中止使用哪些东西;又应该开始使用哪些东西来替代?
- 服务端有没有什么须要考虑或者说须要约束的地方?
PS:我不想详细对比jQuery和AngularJS之间的不一样点。架构
Answer
1.不要预先设计页面,而后再用DOM操做去修改它
在jQuery里面,你会先设计好一个页面,而后再让它变成动态的。这是由于jQuery自己就是以混合使用的思路来设计的。基于这个简单的前提,jQuery目前已经变得臃肿不堪。app
可是在AngularJS的世界中,你心中首先必须有总体架构,而后从零开始构建应用。而不是一开始的时候就去想:“我已经有了这样一块DOM结构,我想让它作×××”,你必须首先思考你到底要完成什么功能,而后再开始动手,而后再设计你的应用,最后再去设计你的视图。框架
2.不要混合使用jQuery和AngularJS
相似地,不要一开始就抱有这样的想法:jQuery能够实现X、Y和Z,因此我只要在上面再覆盖一层AngularJS,把模型和控制器加上便可。当你刚开始使用AngularJS的时候,这种想法实在诱人,这也是为何我老是建议AngularJS新手完全抛弃jQuery的缘由,直到他们习惯了以“Angular风格”去作事为止。ide
在这里,以及在邮件列表里面,我看到过不少这种精心设计的解决方案,其中包含150或者200行代码的jQuery插件,而后他们再用一大堆回调函 数和$apply把这些插件粘到AngularJS上,这种作法很是复杂并且使人困惑不已;可是,他们最终竟然能把这货弄跑起来了!这里的问题在于,在大 多数状况下,只须要不多的AngularJS代码就能够把这些jQuery插件重写一遍,而后全部事情都会忽然变得简洁明了起来。函数
底线是:在解决问题的过程当中,首先“Think in AngularJS”(以AnguarJS的方式思考问题);若是你想不到解决方案,请求助于社区;若是在尝试了全部这些方法以后还找不到简单的解决方案,而后再求助于jQuery。可是,不要让jQuery变成绊脚石,不然你永远没法真正掌握AngularJS。
3.保持以架构的角度思考
首先要明确一点,单页面应用是一种应用,它们不是web页面。因此,咱们须要像服务端开发者那样去思考,而不是像客户端开发者那样思考。咱们必须思考如何把咱们的应用切分红独立的、可扩展的、可测试的组件。
那么,你怎么才能作到这一点呢?你应该如何以AngularJS的方式思考问题呢?下面是一些基本的原则,与jQuery作个对比。
假设有一个叫作“官方记录”(official record)的视图
在jQuery里面,咱们会用编程的方式来修改视图,咱们会像下面这样用ul标签来定义一个下拉列表:
<ul class="main-menu"> <li class="active"> <a href="#/home">Home</a> </li> <li> <a href="#/menu1">Menu 1</a> <ul> <li><a href="#/sm1">Submenu 1</a></li> <li><a href="#/sm2">Submenu 2</a></li> <li><a href="#/sm3">Submenu 3</a></li> </ul> </li> <li> <a href="#/home">Menu 2</a> </li> </ul>
在jQuery里面,咱们的应用逻辑中会像下面这行代码同样来建立这个下拉列表:
$('.main-menu').dropdownMenu();
若是咱们仅仅看视图代码,咱们没法马上发现它有什么功能。对于小型的应用来讲,这样作还算能够。可是对于大型应用来讲,很快就会变得混乱并难以维护。
然而,在AngularJS中,视图是一种功能,它是基于视图的“官方记录”。咱们的ul声明看起来就像下面这样:
<ul class="main-menu" dropdown-menu> ... </ul>
两种实现方式的效果彻底相同,可是在AngularJS的版本中,每个看到模板的人都知道它在作什么。 无论何时开发团队有新人进来,她看到这种代码以后就会当即明白,存在一个叫作dropdownMenu的指令,这个指令负责操控这个视图。她凭直觉就能够知道答案,而没有必要查看任何代码。视图自己已经告诉了咱们这里会发生什么。这样就更加清晰了。
AngularJS新手常常会问这样的问题:我怎么才能找出某种类型的全部超连接,而后在上面加上指令呢?咱们会这样回答他:你不该该这么作。而后 他老是一副惊呆了样子。你不该该这么作的缘由是:这是一种半jQuery半AngularJs式的思惟方式,这不科学。用这种方式思考问题永远得不到很好 的结果。你看到的应该是“官方记录”。除了指令以外(不仅包括如下代码),你永远、永远、永远不该该去修改DOM。同时,指令会用在视图上,这样一来思路就清晰了。
记住:不要先设计,而后编写标签。你必须先架构,而后去设计。
数据绑定
到目前为止,这是AngularJS最赞的特性,利用这一特性能够省掉我在上一小节中提到的大量DOM操做代码。AngularJS会自动为你刷新视图,因此不须要你本身去作这件事!在jQuery中,咱们会响应事件而后刷新页面内容。示例以下:
$.ajax({ url: '/myEndpoint.json', success: function ( data, status ) { $('ul#log').append('<li>Data Received!</li>'); } });
对应的视图代码以下:
<ul class="messages" id="log"> </ul>
这种方式除了会把注意点混在一块儿以外,还有我在前面所提到的思惟模式问题。可是,更加剧要的一点是,这样作咱们必须手动引用并更新DOM节点。同 时,若是咱们想删掉一个log对象,咱们必须针对DOM从新进行编码。这样一来咱们如何脱离DOM来测试这些逻辑呢?同时,若是咱们要修改显示效果又应该 怎么作呢?
这样有点儿乱,代码既琐碎又脆弱。可是在AngularJS中,咱们能够这样作:
$http( '/myEndpoint.json' ).then( function ( response ) { $scope.log.push( { msg: 'Data Received!' } ); });
而后咱们的视图代码是这样的:
<ul class="messages"> <li ng-repeat="entry in log">{{ entry.msg }}</li> </ul>
对于上面所提的删除log对象这个问题,咱们能够把视图写成这样:
<div class="messages"> <div class="alert" ng-repeat="entry in log"> {{ entry.msg }} </div> </div>
这里咱们用Bootstrap的alert块替换了无序列表。而且咱们永远不须要修改控制器代码!同时更重要的是,不管什么时候或者何地更新了log对象,视图都会自动**刷新。高贵优雅!
虽然我没有在这里展现,其实数据绑定操做是双向的。因此,在视图中也能够编辑log信息,只要这样作便可:<input ng-model="entry.log"/>。另外还有更多惊喜。
区分数据模型层
在jQuery中,DOM有相似数据模型的意味。可是在AngularJS中,咱们有一个独立的数据模型层,咱们能够按照本身的想法管理它,它和视 图层彻底独立。对于前面例子中的数据绑定操做来讲,这一点颇有用,而且保持了注意点分离的原则,同时还能够引入更强的测试功能。在其它不少解答中都提到了 这一点,因此这里我就再也不赘述了。
注意点分离
以上全部一切都是为了实现这样一个远大的目标:让你的注意点保持分离。你的视图的角色是展现“官方记录”所能进行的全部操做(绝大部分);你的数据 模型用来表明你的数据;你还有一个service层用来执行可复用的任务;你进行DOM操做并把指令混入到视图中;最后你再用controller把全部 东西粘到一块儿。在其它不少回复里面都提到了这一点,我惟一想要补充的一点是关于测试方面,在下面的小节中我会来讨论它。
依赖注入
用来帮助咱们实现注意点分离的特性就是依赖注入。若是你是从服务端语言转过来的(例如从Java或者PHP),你可能对这个概念已经至关熟悉,可是若是你是一个前端仔,从jQuery转过来的,你可能会以为这种概念很愚蠢、不少余、并且很装逼。但事实并不是如此。
从大的层面上讲,DI意味着你能够自由地声明组件,而后在其它组件中,你能够请求所声明组件的实例,而后你就能够得到它。你没有必要知道加载顺序、文件路径,以及诸如此类的东西。这种概念的强大能量可能不是那么显而易见,这里我只举一个(通用的)例子:测试。
比方说在咱们的应用中,根据应用的状态,咱们须要经过一个REST API请求一个服务端的存储实现,以及本地的存储实现。当对咱们的controller进行测试的时候,咱们并不想和服务端进行通信,毕竟咱们正在测试的是controller而不是其它东西。咱们能够仅仅添加一个虚拟的同名service做为前面所说的自定义组件,而后注射器将会保证controller可以自动得到这个虚拟的服务,咱们的controller不会知道它们之间有什么不一样,也没有必要知道。
关于测试再多说一点...
4.测试驱动开发---永远
这里的内容是关于架构方面的,实际上应该属于第三小节,可是这块内容极其重要,因此我把它独立成了一个单独的小节。
在你所见过、用过,或者写过的全部jQuery插件中,它们有多少个带有完整的测试用例?不是不少,由于jQuery不是太鸟这个原则。可是AngularJS很是看重这一点。
在jQuery中,惟一可以进行测试的方式一般是在一个sample/demo页面上建立独立的组件,经过这个页面咱们能够进行DOM操做相关的测 试。因此,这样一来咱们必须独立开发一个组件,而后再把它集成到咱们的应用中去。好麻烦!在使用jQuery进行开发的时候,消耗的时间太多了,这是由于 咱们选择了迭代的方式,而不是选择测试驱动开发的方式。如此一来,谁又能责怪咱们呢?
可是,在AngularJS中,因为咱们分离了注意点,因此咱们能够用迭代的方式进行测试驱动开发!例如,比方说咱们须要一个超级简单的指令,用来在菜单中显示当前的路由是什么。咱们能够这样在视图中声明所须要的东西:
<a href="/hello" when-active>Hello</a>
好,如今咱们来编写一个单元测试:
it( 'should add "active" when the route changes', inject(function() { var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope ); $location.path('/not-matching'); expect( elm.hasClass('active') ).toBeFalsey(); $location.path( '/hello' ); expect( elm.hasClass('active') ).toBeTruthy(); }));
咱们运行这个单元测试,确认它是否会失败。而后咱们再来编写指令:
.directive( 'whenActive', function ( $location ) { return { scope: true, link: function ( scope, element, attrs ) { scope.$on( '$routeChangeSuccess', function () { if ( $location.path() == element.attr( 'href' ) ) { element.addClass( 'active' ); } else { element.removeClass( 'active' ); } }); } }; });
如今,咱们的测试运行经过了,而且菜单的行为符合了咱们的预期。这样一来,咱们的开发既是可迭代的,也是测试驱动的。碉堡了。
5.从概念上说,指令并不是打包好的jQuery
你常常会听到“只能在指令中操做DOM”之类的言论。这是必须的。请慎重对待这一原则。
咱们再来稍微深刻一点...
有一些指令只是用来装饰一下视图里面已经存在的内容(想一想ngClass),有时候也会直接进行一些DOM操做,而后就没有而后了。可是,像 “widget”(小组件)这样带有模板的指令,它一样须要遵照注意点分离的原则。也就是说,模板自身一样须要保持很强的独立性,独立于link和 controller函数的具体实现。
AngularJS内置了完整的工具,让实现这一点很是容易;咱们可使用ngClass指令来动态更新CSS样式类;ngBind能够用来作双向 数据绑定;ngShow和ngHide能够以编程的方式来显示或者隐藏元素;诸如此类还有不少。咱们也能够导入咱们本身所编写的指令。换句话说,咱们能够 实现各类绚丽的效果而不须要进行DOM操做。进行的DOM操做越少,指令测试起来就越容易、设置样式就越容易、在将来修改起来也会越容易、而且可复用性和 可分发性也会更好。
我看到不少AngularJS新手把指令当成容纳各类jQuery代码的场所。换句话说,他们的想法是:“既然我不能在控制器里面作DOM操做,那我就把DOM操做相关的代码放到指令里面好了”。这种作法确实是好一些了,可是一般仍是是错误的。
思考一下咱们在第三小节里面所编写的logger应用。即便咱们把相关的操做放到了指令里面,咱们仍是用一种“AngularJS的方式”来实现了 它。它仍然没有作任何DOM操做!在不少状况下DOM操做是必须的,可是这种状况比你想象的要少得多!当你在应用里面的任何地方进行DOM操做以前,请问 问本身,是否是真的必需要这样作。颇有可能存在更好的实现方式。
下面是一个小例子,用来讲明我常常看到的一种模式。咱们须要一个开关型的按钮。(注意:这个例子的代码有点装逼,而且有点冗长,只是为了用来表明更加复杂一些的例子,这些例子一般是以与此相同的方式来解决的。)
.directive( 'myDirective', function () { return { template: '<a class="btn">Toggle me!</a>', link: function ( scope, element, attrs ) { var on = false; $(element).click( function () { if ( on ) { $(element).removeClass( 'active' ); } else { $(element).addClass( 'active' ); } on = !on; }); } }; });
这段代码里面有不少错误的地方。
第一,jQuery历来就不是必须的。咱们这里要实现的东西实际上彻底不须要jQuery!
第二,即便咱们已经在页面上引入了jQuery,也没有必要在这里去使用;对于没有使用jQuery的项目,咱们能够简单地使用angular.element,这样一来咱们的组件一样可以很好地运行。
第三,假设这里必须使用jQuery咱们的指令才能运行,jqLite(angular.element)老是会自动使用jQuery,若是jQuery已经加载了话!因此咱们不须要使用$,咱们只要使用angular.element就能够了。
第四,与第三点相似,jqLite元素没有必要使用$来进行包装,传递给link函数的element已是一个jQuery元素了!
还有第五点,这一点咱们在前面的小节中没有提到,那就是咱们为何要把模板相关的内容混合在咱们的代码逻辑里面?
以上指令能够重写成下面这样(即便对于很是复杂的状况一样能够改写!),改写以后代码极其简单:
.directive( 'myDirective', function () { return { scope: true, template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>', link: function ( scope, element, attrs ) { scope.on = false; scope.toggle = function () { scope.on = !$scope.on; }; } }; });
再说一次,模板相关的内容位于template中,因此你(或者你的用户)能够简单地切换它,从而能够知足任何须要的样式要求,同时永远不须要去修改代码逻辑。可复用性---嘭!
这样改写以后还会带来其它好处,好比测试---这是必须的!无论模板里面是什么内容,指令内部的API永远不须要修改,这样一来重构就很是简单了。你能够随意修改模板的内容而没有必要去理睬指令。同时不管你修改了什么内容,你的测试依然可以运行经过。
w00t!
好吧,若是指令并不是jQuery函数之类的集合,那么它们是什么呢?实际上指令是HTML扩展。若是HTML没法作到你想实现的某件事情,你就本身编写一个指令,而后再去使用这个指令,好像它就是HTML的一部分同样。
换句话说,若是AngularJS没有内置支持某件事情,请思考一下你的团队应该怎么样去实现它,参照ngClick,ngClass等指令的作法。
小结
不要使用jQuery。最好不要引入它。它只会拖你的后腿。当你遇到一个问题,而这个问题你知道如何使用jQuery去解决,那么在你使用$以前, 请思考一下如何以AngularJS的方式去解决它。若是你不知道,去问别人!最好的解决方式十有八九不须要使用jQuery,若是你用jQuery的方 式来解决,最终会给你带来更多工做量。
其它相关内容:
一、OReilly的《AngularJS》已由电子工业出版社出版
http://damoqiongqiu.iteye.com/blog/1965167
二、《AngularJS》5个实例详解Directive(指令)机制
http://damoqiongqiu.iteye.com/blog/1917971
三、AngularJS表单基础
http://damoqiongqiu.iteye.com/blog/1920191
四、AngularJS Form 进阶:远程校验和自定义输入项
http://damoqiongqiu.iteye.com/blog/1920993
五、AngularJS:在Windows上安装Yeoman
http://damoqiongqiu.iteye.com/blog/1885371
六、对比Angular/jQueryUI/Extjs:没有一个框架是万能的
http://damoqiongqiu.iteye.com/blog/1922004
七、使用JsTestDriver实现JavaScript单元测试
http://damoqiongqiu.iteye.com/blog/1924415
八、JavaScript单元测试系列二:将Jasmine集成到JsTestDriver