文档章节

{{AngularJS精华}}有jQuery背景的开发者如何建立起AngularJS的思维模式?

顽Shi
 顽Shi
发布于 2014/05/31 18:03
字数 4195
阅读 1642
收藏 42
点赞 0
评论 0

本文由 机器human 编译自 Mark Rajcok 在 StackOverflow 的同名问答题《How do I “think in Angular.js” if I have a jQuery background?》。下面是 Mark Rajcok 的提问:

“我可以熟练使用jQuery进行客户端应用的开发,但是现在我希望开始使用Angular.js。哪位能描述一下这个过程中必要的模式变化吗?希望您的答案能够围绕下面这些具体的问题:

    1. 我如何对客户端web应用进行不同方式的架构和设计?它们之间最大的区别是什么?(译者注:指jQuery和Angular.js)

    2. 有什么是我不该做或者不该使用的;而又有什么是我应该做或者应该使用的呢?

    3. 有没有一些服务端的考量/约束呢?

我在寻找的就是一个关于jQuery和Angular.js之间的详细的比较。”

下面是来自 Josh David Miller 的最佳回答:

 

1. 绝不要先设计你的页面,然后用DOM操作去改变它

在jQuery中,你会先设计一个页面,然后让它变得动态化。这是因为jQuery是为了扩展而设计的,并在这个前提下变得越来越臃肿。

但是在Angular.js中, 你必须从一开始就在脑子里挂着架构的弦。不要一开始就想着“我有这样一个DOM,我想让它做X”, 你必须从你要完成的目标开始思考,然后设计你的应用, 最后才是设计你的视图。

 

2. 不要用 Angular.js 扩展 jQuery

类似地,不要一开始就带着这样的想法:jQuery可以完成X,Y,Z,所以我只要在其上为模型和控制器添加Angular.js就行了。在起步阶段这确实很容易勾引你,这也是为什么我总是推荐Angular.js新手根本不要使用jQuery,至少要在他们习惯了“angular 方式”之后。

我在这里(译者注:指stackoverflow)和邮件列表上看到过很多开发者,他们用150或者200行代码的jQuery插件,然后利用一堆让人困惑的复杂的回调和$apply与Angular.js粘合起来建立这些详尽的解决方案;最终确实可以跑起来! 但是其实这个问题在大多数情况下,我们可以用一小段Angular.js代码来重写jQuery插件即可,而这种方式会让一切刹那间简单明了可理解。

我觉得这类问题的底线是:当你在解决问题时,首先利用“Angular.js思想”去做;如果你不能想出一个方案,那么就在社区里询问;如果还是没有简单的解决方法,那么再请随意使用jQuery吧。但是注意,千万别让jqeury成为你的拐杖,不然你将永远无法真正精通Angular.js。

 

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节点。并且如果我们想要删除一个日志,我们不得不再次对dom编程操作。我们怎样才能抛开dom来测试逻辑呢?还有,如果我们希望改变展现呢?

真是让人凌乱。。。

但是在Angular.js中,我们可以这样做:

$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>

但是考虑到刚才提到的问题,我们的视图看上去可以是这样的:

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div>
</div>

现在,替换掉了无序列表,我们使用Bootstrap警告框。同时我们根本不需要改变控制器代码!更重要的是,不论日志何时或者如何更新,视图也会跟着改变。自动的!漂亮!

虽然我没有在这里演示出来,但是数据绑定是双向的。所以这些日志信息同样可以在视图中被编辑,就像这样:

<input ng-model="entry.msg" />

是不是更开心了?

不同的模型层

在jQuery中,dom有点像模型。但是在angualrjs中,我们有一个分离的模型层, 而这个模型层可以让我们用任何方式管理,完全独立于视图。这对于上面说的数据绑定很有帮助, 还可以维护关注点分离,并且引入更多的可测试性。其它的答案提到了这点,所以我这里就不再赘述了。

关注点分离

以上所有的这些把我们带入了这样的主题:保持你的关注点分离。你的视图表现的像记录什么会发生(大部分情况)的正式记录;你的模型表现你的数据;你有一个服务层来执行可重用的任务;你执行dom操作并通过指令扩展你的视图;并且你用控制器来组合这些。这些同样已经在其它答案中提到,我在这里唯一还要提出的一个事情就是可测试性,我会在下文的另一节里专门讨论。

依赖注入

依赖注入是让我们实现关注点分离的方法。如果你是一个服务器端的开发者(从java到php),你可能对这个概念已经非常熟悉了,但是如果你是一个来自jQuery的客户端的朋友,那么你可能会认为这个概念是傻浅挫。但是它可不是:)

从一个更广的观点来看, 依赖注入意味着你可以非常自由的声明组件,然后你可以通过任意其它组件,呼叫一个它的实例,然后授权。

你不需要知道载入顺序,或者文件位置,或者其它类似的东西。这种强大的力量可能不会立刻显现,但是我这里会提供一个(通常)的例子:测试。

比如在我们的应用中,需要一个通过REST API,同时也依赖于应用状态,本地存储实现了服务器端存储的服务。当在我们的控制器上跑测试的时候,我们不希望与服务器端通讯-毕竟我们在测试控制器。我们能够仅仅添加一个与我们原始组件同名的mock服务,注入器将确保我们的控制器自动获取伪造对象–我们的控制器不会也不需要知道它们的区别。

那么既然提到测试……

 

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”并有一个模板,

那它同样要遵守关注点分离原则。也就是说,这个模板也应该与它在链接和控制器函数里的实现保持最大的独立。

Angular.js自带着一套工具让这件事变得简单; 使用ngClass我们能够动态的更新类;ngBind允许双向的数据绑定;ngShow和ngHide以编程的方式显示或隐藏一个元素;还有更多–包括我们自己写的那些。换句话说, 我们可以不用DOM操作来实现所有的酷炫的事儿。 dom操作越少,指令越容易测试,它们也更容易样式化,在未来它们也更容易改变,并且也变得更加可重用和可分发。

我看到很多Angular.js开发新手将指令当做放置一堆jQuery的地方。换句话说, 他们认为:“既然我不能在控制器里做dom操纵,那么我就把这段代码放到指令里”。当然这看上去好多了,但是通常这仍然是错的。

想一下在第三节里我们编写的日志记录。即使我们将其放到一个指令里,我们仍然希望用“anggualjs方式”来做这件事情。这仍然没有做任何dom操作!在很多情况下dom操作是必须的,但是这种情况其实比你想的少得多!在你在你的应用中到处使用dmo操作之前,问问你自己,这真的是必须的吗。可能有更好的方法呢。

这里用一个简单的例子来展示我们经常会看到的一个模式。我们需要一个切换按钮。(注意:这个例子有那么一点人为设计的技巧并且有点啰嗦,但是很多更复杂的情况其实也完全可以根据这个例子的方式来解决。)

.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,也没有理由一定要在这里使用它;我们可以简单的使用angular.element,而且就算在一个没有jQuery的项目中我们的组件仍然可以工作。第三,即使我们假设为了让这个指令工作,jQuery是必须的,如果jqury被加载,那么jqLite(angler.element)一定会使用jQuery。所以我们不需要使用$(译者注: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;
            };
        }
    };
});

再次强调,模板的那些代码都是在模板里的,所以你或者你的使用者能够很方便的将其换成一个符合任何需要的样式,同时逻辑不被改变。这就是重用性-赞!

当然,还有其它很多好处–比如测试 – 这很容易! 不论什么在模板中,指令的内部API绝不会被接触,所以重构就变得容易。你可以在不接触指令的情况下随意改变你的模板。并且不论你怎么变,你的测试仍可以通过。

耶!

所以如果指令不只是一组jQuery风格的函数,那么它们是什么呢?指令其实就是html的扩展。如果html不能完成你希望它做的一些事情,你就写一个指令来做,并且当它是html的一部分来使用。

换句话说, 如果Angular.js一下子无法完成手头的工作,想想看你的团队是否能用ngClick、ngClass等来搞定它。

 

总结

别用jQuery。甚至都别引用它。它会阻碍你。当你有个问题已经知道怎么用jQuery来解决的时候,在你将手伸向$的时候,试试能不能在Angular.js的规范内解决。如果你不知道,就问!

十有八九最佳的方法是不需要jQuery的,而当你选择用jQuery的时候反而会导致更多的工作呢。

本文转载自:http://blog.jobbole.com/46589/

共有 人打赏支持
顽Shi
粉丝 272
博文 81
码字总数 92387
作品 0
普陀
程序员
StackOverFlow精彩问答赏析:有jQuery背景的开发者如何建立起AngularJS的思维

【编辑注】本文来自StackOverFlow上How do I “think in AngularJS” if I have a jQuery background?一题中得票最高的回答。该回答得票超过3000次,回答者Josh David Miller是活跃于开源社区...

玛雅牛
2014/01/04
0
0
作为一个jQuery的老用户,如何适应AngularJS的编程思想?

Don't design your page, and then change it with DOM manipulations In jQuery, you design a page, and then you make it dynamic. This is because jQuery was designed for augmentatio......

tommyfok
2014/08/01
0
0
angularJS之站在jQuery的肩膀上

jQuery:用更少的代码,实现更强悍的功能 托互联网日新月异发展的福,浏览器变成了人们接入互联网的入口,而JavaScript 这个曾经的小语种,终于成功地站到了舞台的中央,唤起了开发者的兴趣。...

笔阁
2015/03/16
0
0
AngularJS中的$http.post与jQuery.post的区别

很多时候我们需要用ajax提交post数据,angularjs与jq类似,也有封装好的post。 但是jQuery的post明显比angularjs的要简单一些,人性化一些。 AngularJS: $http.post('do-submit.php',myDat...

tommyfok
2014/07/06
0
7
整合jQuery Mobile+AngularJs 经验谈

两者都是不错的JS编程框架,但是各自用途不同 1. jQuery Mobile提供了不错的图形空间,以及依赖于jQuery本身,提供了不少空间操作的API 2. Angular没有啥控件,但是是一个很好的JS的MVC框架,...

工程师楚门
2014/03/12
0
19
angular.js,backbone.js,ember.js区别

1 backbone. 对于初学者来说,我非常建议首先学习backbone 而不是jQuery. 因为jQuery提供的功能是操作DOM和Ajax数据传输. 而Backbone的功能和目的仅仅是提供一个javascript的MVC 实际针对Web...

造化玉碟
2014/04/05
0
2
angular的post请求,SpringMVC后台接收不到参数值的解决方案

这是我后台SpringMVC控制器接收isform参数的方法,只是简单的打出它的值: @RequestMapping(method = RequestMethod.POST)@ResponseBodypublic Map<String, Object> save(@RequestParam(val......

Big_BoBo
2014/01/09
0
15
AngularJS 2014-10-22

前台代码起来越多,需要用一种更高效方式整合 MVC只是手段,终极目标是模块化和复用 第一部分:快速上手 1.1 感受AngularJS的4大核心特性 1.2 搭建开发、调试、测试环境 第二部分:基本概念和...

jayronwang
2014/10/22
0
0
对比AngularJS/jQueryUI/Extjs:没有一个框架是万能的

AngularJS不能做什么?对比Angular/JSjQueryUI/Extjs 框架就好比兵器,你得明白你手里拿的是屠龙刀还是倚天剑,刀法主要是砍,剑法主要是刺。对于那些职业喷子和脑残粉,小僧送你们两个字:呵...

zero2hero
2015/04/16
0
4
Angularjs的$apply及其优化使用

今天,我们要聊得是Angularjs中的小明星$apply。当我们数据更新了,但是view层却没反应时,总能听到有人说,用apply吧,然后,懵懂无知的我们,在赋值代码后面加了$scope.$apply(),然后就惊喜...

北辰狼月
07/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

TensorFlow 线性分类

构造直线 z = 2 * x - 3 * y + 4 x0*w0+x1*w1+b=0 x1=-x0* w0/w1-b/w1 斜率 k= -w0/w1 截距 -b/w1 随机生成数据,加入一定的偏差,用直线将二维平面分为两部分 使用线性模型拟合参数 损失函数...

阿豪boy
6分钟前
0
0
翻译冒泡排序测试

翻译一个冒泡排序: var a = [1,3,2,4,6,5];var f = 0;var n = a.length ;for( var i =1; i<= n; i++) { for( var j = n-1 ; j >= i; j --) { if(a[j] < a[j+1]) { ......

钟元OSS
8分钟前
0
0
maven父、子级版本号同时修改

命令: mvn versions:set -DnewVersion=0.0.2-SNAPSHOT

沉默的懒猫
9分钟前
0
0
Spring boot中的异常处理之注解响应

Controller层 return patientRepository.findById(id) .orElseThrow(() -> new NotFoundException(String.format("Patient %d not found", id))); Exception类 @ResponseS......

亚林瓜子
10分钟前
0
0
webpack文档翻译_001

概念 webpack是一个为现代JavaScript应用的打包工具(a static module bundler)。 当webpack处理应用时,在其内部,会生成一个依赖图(dependency graph),这个依赖图可以映射到项目里的每一个...

DennisHill
11分钟前
5
0
vim 行首/行尾 批量操作

批量插入 行首插入 :%s/^/your_word/ 行尾插入:%s/$/your_word/ 按键操作 注释:ctrl+v 进入列编辑模式,向下或向上移动光标,把需要注释的行的开头标记起来,然后按大写的I(shift + i),再插入...

温子寒
11分钟前
0
0
Java语言学习(十二):多线程

Java中给多线程编程提供了内置的支持,多线程是多任务的一种特别形式,它使用了更小的资源开销。这里需要知道两个术语及其关系:进程和线程。 进程:进程是系统进行资源分配和调度的一个独立...

海岸线的曙光
17分钟前
0
0
mysql源码阅读相关文章

https://www.jianshu.com/p/e739afb8fe31

writeademo
31分钟前
0
0
CentOS7 安装MySQL8

1. 从官网拿到本地YUM源的安装包,并安装本地YUM源 2. 通过`yum install -y`命令进行MySQL的安装 3. 启动服务,并配置开机自启 4. 获取初始化密码,登录MySQL 5. 修改密码策略,然后自行设置roo...

小致dad
37分钟前
0
0
史上最简单的 IntelliJ IDEA 教程

我不是作者,我只是内容的搬运工。 传送门

颖辉小居
39分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部