原帖:http://stackoverflow.com/questions/14994391/thinking-in-angularjs-if-i-have-a-jquery-background
最佳回复:http://stackoverflow.com/a/15012542
Question
假设我已经熟悉了如何使用jQuery来开发客户端应用,我现在打算使用AngularJS。请描述一下有那些思维模式方面的东西需要转变吗?下面是举出一些具体的问题,用来帮助你回答我的这个问题:
- 我应该以何种不同的方式来架构和设计客户端web应用?最大的不同点是什么?
- 我应该停止使用哪些东西;又应该开始使用哪些东西来替代?
- 服务端有没有什么需要考虑或者说需要约束的地方?
PS:我不想详细对比jQuery和AngularJS之间的不同点。
Answer
1.不要预先设计页面,然后再用DOM操作去修改它
在jQuery里面,你会先设计好一个页面,然后再让它变成动态的。这是因为jQuery本身就是以混合使用的思路来设计的。基于这个简单的前提,jQuery目前已经变得臃肿不堪。
但是在AngularJS的世界中,你心中首先必须有整体架构,然后从零开始构建应用。而不是一开始的时候就去想:“我已经有了这样一块DOM结构,我想让它做×××”,你必须首先思考你到底要完成什么功能,然后再开始动手,然后再设计你的应用,最后再去设计你的视图。
2.不要混合使用jQuery和AngularJS
类似地,不要一开始就抱有这样的想法:jQuery可以实现X、Y和Z,所以我只要在上面再覆盖一层AngularJS,把模型和控制器加上即可。当你刚开始使用AngularJS的时候,这种想法实在诱人,这也是为什么我总是建议AngularJS新手彻底抛弃jQuery的原因,直到他们习惯了以“Angular风格”去做事为止。
在 这里,以及在邮件列表里面,我看到过很多这种精心设计的解决方案,其中包含150或者200行代码的jQuery插件,然后他们再用一大堆回调函 数和$apply把这些插件粘到AngularJS上,这种做法非常复杂而且令人困惑不已;但是,他们最终居然能把这货弄跑起来了!这里的问题在于,在大 多数情况下,只需要很少的AngularJS代码就可以把这些jQuery插件重写一遍,然后所有事情都会突然变得简洁明了起来。
底线是:在解决问题的过程中,首先“Think in AngularJS”(以AnguarJS的方式思考问题);如果你想不到解决方案,请求助于社区;如果在尝试了所有这些方法之后还找不到简单的解决方案,然后再求助于jQuery。但是,不要让jQuery变成绊脚石,否则你永远无法真正掌握AngularJS。
3. 永远根据架构去思考
首先你要知道,单页面结构也是应用。它不是网页。所以我们需要有服务端开发者思想加上客户端开发者思想。 我们必须考虑如何将我们的应用拆分为独立,可扩展,可测试的组件。
那么你要怎么做呢?你如何做到利用“angualrjs思想”呢?这里有一些普遍的原则,与jQuery作为比照。
视图是“正式记录”
在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();
当我们只是看着视图的时候,不会立刻看出它的功能。对于小应用而言,这样是没问题的。但是对于大型的应用,情况就一下子变得令人困惑并且难以维护。
但是在Angular.js中,视图是基于视图的功能的正式记录。我们的ul是像下面这样声明的:
<ul class="main-menu" dropdown-menu> ... </ul>
这两者其实做了同样的事情,但是在Angular.js的版本中,任何看到这个模板的人都知道将要发生什么。不 论何时,开发团队里有任何新的开发人 员加入,她可以一眼看出有一个叫做dropdownMenu的指令作用在视图上;她根本不需要凭直觉猜测或者研究下代码才找到正确的答案。视图本身就告诉 我们将会发生什么了。清晰多了。
angualrjs的新手经常会问这样一个问题: 我如何找到某一类所有的链接并且给它们添加一个指令呢?当看到我们回复的时候小伙伴都震惊了:压根别去这样做。但是劝你不要这样做的原因是,这样做就像是 一半jQuery,一半angulrjs,而这真心很糟。这里的问题是,开发者想在angualrjs的情境中使用jQuery方式。而这绝对不会玩得 转。视图是正式记录。超出指令的范围(这点下文会谈论更多),你绝不要去改变DOM。而且指令是应用在视图中的,目的自然也一目了然。
记住:不要先设计再修饰。你必须先进行架构,然后再考虑设计。
数据绑定
这是Angular.js目前最酷的特性之一,并且秒杀我前文提到的各种需要的DOM操作。不需要你自己动手,Angular.js将自动更新你的视图有木有!
在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可不是很遵守这个规矩。但是angualrjs是。
在jQuery中, 测试的唯一方法是用示例页面来独立地创建组件,针对该页面我们的测试可以实施dom操作。于是我们就不得不分离地开发一个组件然后将其集成进我们的应用。多么不方便啊!
那么多时间啊,当我们使用jQuery开发时,我们选择使用迭代开发代替测试驱动的开发。可以谁又能怪我们呢?
但是因为我们有关注点分离,我们能够在Angular.js里反复使用测试驱动开发。举个例子,我们想要一个超简单的指令来指示在菜单中我们目前的路径是什么。我们可以在视图中这样声明我们想获取的:
<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来解决的时候,在你将手伸向$的时候,试试能不能在Angular.js的规范内解决。如果你不知道,就问!
十有八九最佳的方法是不需要jQuery的,而当你选择用jQuery的时候反而会导致更多的工作呢。
原文/转自:Think in AngularJS :对比 jQuery 和 AngularJS 的不同思维模式
相关推荐
Think Perl 6: How to Think Like a Computer Scientist by Laurent Rosenfeld English | 8 May 2017 | ASIN: B0716P9W11 | 466 Pages | AZW3 | 1.02 MB Want to learn how to program and think like a computer ...
If you’re a student studying computer science or a software developer preparing for technical interviews, this practical book helps you learn and review the most important ideas in software ...
《Think in C++》是C++编程领域的一部经典教程,由知名程序员Bruce Eckel编写。这本书分为两卷,第一卷主要介绍C++的基础知识,包括语法、控制结构、类和对象等;第二卷则深入探讨了C++的高级特性,如模板、异常处理...
《Think in C++》是一本深受程序员喜爱的C++学习书籍,由世界级编程大师Bruce Eckel撰写。这本书以其深入浅出、易于理解的讲解方式,为读者提供了全面掌握C++语言的基础和进阶知识。高清的文字版PDF使得阅读更加舒适...
使用简单的Python程序而不是复杂的数学对贝叶斯统计进行介绍。
《Think in C++》是C++编程领域的一本经典著作,由Bruce Eckel撰写,以其深入浅出的讲解和丰富的实例著称。这本书全面覆盖了C++语言的基础和高级特性,旨在帮助读者不仅掌握语法,更能理解C++的设计哲学和编程思想。...
think in C++(中文名:C++编程思想)think in C++(中文名:C++编程思想)think in C++(中文名:C++编程思想)think in C++(中文名:C++编程思想)think in C++(中文名:C++编程思想)think in C++(中文名:C++编程思想)...
《Think in C++》是C++编程领域里一本广受欢迎的教材,由知名程序员和教育家Bruce Eckel撰写。这本书以其深入浅出的讲解方式,系统地介绍了C++语言,尤其是其核心特性——面向对象编程(OOP)的思想。下面我们将详细...
《Think in C#》是一本深受程序员喜爱的C#编程语言教程,由知名软件开发者、教育家Miguel de Icaza和Gary McLean Hall等人共同编写。这本书深入浅出地介绍了C#语言的核心概念和技术,是.NET 2005框架下的经典学习...
《Think in C++》是C++编程领域里一本广受欢迎的教材,由知名程序员和教育家Bruce Eckel编写。这本书以其深入浅出的讲解方式,帮助无数初学者和有经验的程序员更好地理解和运用C++语言。现在提供的资源是英文版,...
《Think in C#》是一本深入探讨C#编程语言的书籍,旨在帮助程序员建立起对C#的深刻理解和熟练运用。C#(读作"C sharp")是由微软公司开发的一种面向对象的编程语言,广泛应用于Windows平台上的软件开发,尤其是与...
总之,《Think in C++编程详解》不仅涵盖了C++的基础知识,还深入到高级主题,旨在培养读者的编程思维和实践能力。无论是初学者还是希望提升C++技能的开发者,都能从中受益。通过系统学习并实践书中的例子,读者将...
《Think in C++》是一本深受C++初学者欢迎的编程教材,由知名程序员和教育家Bruce Eckel撰写。这本书以其清晰的论述和丰富的实例,为读者提供了深入理解C++语言的基础。C++是一种强大的、面向对象的编程语言,它在...
总的来说,《Think in Python》中英文对照版是Python初学者的宝贵资源,它能帮助学习者建立起扎实的Python基础,并逐步提升编程思维和技能。在阅读和实践的过程中,不断挑战自己,理解并掌握Python的魅力,从而在...
《Think in Java 3》是一本深受欢迎的Java编程指南,由Bruce Eckel撰写,它以其详尽的解释和丰富的示例闻名。这本书是许多Java初学者和专业开发者的重要参考资料,旨在帮助读者深入理解Java语言的核心概念和机制。...
在PHP开发过程中,可能会遇到“Fatal error Class 'Think' not found in runtime.php”的错误提示,这通常是由于类加载问题导致的。在这个问题中,我们主要关注的是PHP的类自动加载机制以及Linux系统的文件系统特性...