在jQuery中,你先设计一个页面,然后再使它变得动态。这是因为jQuery的设计初衷是增强性能,在此基础上得到了极大的发展。
但在AngularJS中,你必须从头开始考虑你的架构。你不能从“我有一个DOM部分,我想让它做X”这样的思路开始,而必须从你想要实现什么开始,然后设计你的应用程序,最后再去设计你的视图。
同样地,不要从jQuery已经具备X、Y和Z的功能出发,然后只需添加AngularJS来进行模型和控制器的操作。当你刚开始学习时,这种想法确实很诱人,这就是为什么我始终建议新的AngularJS开发者根本不要使用jQuery,至少要在他们习惯“Angular Way”之后再使用。
我看到许多开发者在这里和邮件列表上创建了使用 jQuery 插件的复杂解决方案,代码有 150 或 200 行,然后用一系列回调函数和 $apply 进行粘合,这些函数令人困惑和混乱;但他们最终使其工作!问题是,在大多数情况下,那个 jQuery 插件可以在 AngularJS 中以更少的代码重写,突然间一切变得易于理解和简单明了。那么,你如何做到这一点呢?如何“以AngularJS的方式思考”?以下是一些通用原则,与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>
$('.main-menu').dropdownMenu();
<ul class="main-menu" dropdown-menu>
...
</ul>
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
对于长这样的视图:
<ul class="messages" id="log">
</ul>
$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有点像模型。但在AngularJS中,我们有一个单独的模型层,可以完全独立于视图以任何我们想要的方式进行管理。这有助于上述数据绑定,保持关注点分离,并引入更大的可测试性。其他答案提到了这一点,所以我就不多说了。
所有上述内容都与这个主题联系在一起:保持你的关注点分离。你的视图作为官方记录,记录了应该发生的事情(在大多数情况下);你的模型代表你的数据;你有一个服务层来执行可重用的任务;你进行DOM操作并使用指令增强你的视图;你使用控制器将所有内容粘合在一起。其他回答中也提到了这一点,我唯一要补充的是与测试相关的事项,我将在下面的另一节中讨论。
帮助我们实现关注点分离的是依赖注入(DI)。如果你来自服务器端语言(从Java到PHP),你可能已经熟悉了这个概念,但如果你是一个来自jQuery的客户端人员,这个概念可能看起来既愚蠢又多余,甚至有些时髦。但它并不是。 :-)
从广义上来看,DI 意味着您可以非常自由地声明组件,然后从任何其他组件中请求一个实例,它就会被授予。您不必了解加载顺序、文件位置或任何类似的内容。这种能力可能不会立即显示出来,但我将提供一个(常见的)例子:测试。<a href="/hello" when-active>Hello</a>
好的,现在我们可以为不存在的 when-active
指令编写测试:
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' );
}
});
}
};
});
ngClass
,我们可以动态地更新类;ngModel
允许双向数据绑定;ngShow
和ngHide
可编程地显示或隐藏元素;还有很多其他的工具 - 包括我们自己编写的工具。换句话说,我们可以在不进行DOM操作的情况下实现各种神奇的功能。越少的DOM操作,指令就越容易测试,样式也更容易,未来修改时也更容易,而且它们更可重用和可分发。以下是一个快速示例,展示了我最常见到的模式。我们想要一个可切换的按钮。(注意:此示例有点牵强,为了表示解决完全相同的更复杂情况的方式而变得冗长。)
.directive( 'myDirective', function () {
return {
template: '<a class="btn">Toggle me!</a>',
link: function ( scope, element, attrs ) {
var on = false;
$(element).click( function () {
on = !on;
$(element).toggleClass('active', on);
});
}
};
});
angular.element
,并且我们的组件仍然能够在没有jQuery的项目中正常工作。angular.element
)将始终使用已加载的jQuery!因此,我们不需要使用$
- 我们可以直接使用angular.element
。$
包装 - 传递给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都不会受到影响,因此重构很容易。您可以随意更改模板而不触及指令。无论您做出什么更改,您的测试仍然通过。
w00t!
因此,如果指令不仅仅是类似于jQuery函数的集合,它们是什么呢?指令实际上是HTML的扩展。如果HTML不能满足您的需求,您可以编写指令来完成它,然后像使用HTML一样使用它。
换句话说,如果AngularJS不能直接完成某项任务,请考虑团队如何使其适应ngClick
、ngClass
等。
不要使用jQuery。甚至不要包含它。它会拖慢你的速度。当你遇到一个你认为已经知道如何在jQuery中解决的问题时,在伸手拿 $
之前,尝试想一想如何在AngularJS的限制范围内解决它。如果你不知道,就问!在20次尝试中,有19次最好的方法不需要jQuery,试图使用jQuery解决它会给你带来更多的工作。
在 jQuery 中,使用 选择器 查找 DOM 元素,然后绑定/注册事件处理程序。当事件触发时,该(命令式)代码执行以更新/更改 DOM。
在 AngularJS 中,您需要考虑的是 视图 而不是 DOM 元素。视图是包含 AngularJS 指令 的(声明式)HTML。指令为我们设置了幕后的事件处理程序,并提供了动态数据绑定。选择器很少使用,因此对 ID(和某些类型的类)的需求大大降低。视图与模型(通过作用域)相关联。视图是模型的投影。事件更改模型(即数据,作用域属性),并且投射这些模型的视图会“自动”更新。
在 AngularJS 中,思考模型,而不是包含数据的 jQuery 选定 DOM 元素。将视图视为这些模型的投影,而不是注册回调以操作用户所看到的内容。
jQuery使用不侵入式JavaScript - 行为(JavaScript)与结构(HTML)分离。
AngularJS使用控制器和指令(每个指令都可以有自己的控制器和/或编译和链接函数)来将行为从视图/结构(HTML)中移除。Angular还有服务和过滤器来帮助分离/组织您的应用程序。
另请参见https://dev59.com/MWYq5IYBdhLWcg3woB0W#14346528
设计AngularJS应用程序的一种方法:
即使你不了解JavaScript的原型继承,也可以用jQuery做很多事情。但是,如果你对JavaScript继承有很好的理解,在开发AngularJS应用程序时,你将避免一些常见的陷阱。建议阅读:在AngularJS中作用域原型/原型继承的细微差别是什么?
AngularJS和jQuery采用非常不同的思想。如果你来自jQuery,你可能会发现一些差异令人惊讶。Angular可能会让你很生气。
这是正常的,你应该坚持下去。Angular值得。
jQuery为您提供了一个工具包,用于选择DOM中的任意位并对它们进行即席更改。您可以逐个部分地做任何您喜欢的事情。
相比之下,AngularJS提供给您一个编译器。
这意味着AngularJS从顶部到底部阅读整个DOM,并将其视为代码,字面上作为编译器的指令。在遍历DOM时,它寻找特定的指令(编译器指令),告诉AngularJS编译器如何行事以及要做什么。指令是装满JavaScript代码的小对象,可以与属性、标签、类甚至注释进行匹配。
当Angular编译器确定DOM的一部分与特定指令匹配时,它会调用指令函数,并将DOM元素、任何属性、当前$scope(即本地变量存储)以及其他有用的位传递给它。这些属性可能包含Directive可以解释的表达式,告诉它如何呈现以及何时应该重新绘制自己。您的模板驱动您的应用程序。它被视为DSL。您编写AngularJS组件,AngularJS会负责根据模板结构在正确的时间将它们引入并使它们可用。这与标准的MVC模式非常不同,其中模板仅用于输出。
这更类似于XSLT而不是Ruby on Rails,这是一种彻底的控制反转,需要一些时间来适应。
停止试图从JavaScript驱动您的应用程序。让模板驱动应用程序,让AngularJS负责将组件连接起来。这也是Angular的方式。目前您可能对SEO和可访问性有各种问题,这是正常的。这里存在一些未解决的问题。大多数屏幕阅读器现在可以解析JavaScript。搜索引擎也可以索引AJAXed内容。尽管如此,您仍需要确保使用pushstate URL并拥有一个良好的网站地图。请参见这里以了解该问题的讨论:https://dev59.com/xWYr5IYBdhLWcg3wqr3o#23245379
关注点分离(SOC)是一个模式,经过多年的Web开发出现,原因包括SEO、可访问性和浏览器不兼容性。它看起来像这样:
它看起来像这样:
MVC和SOC不是同一尺度上的相反极端,它们处于完全不同的轴线上。在AngularJS环境中,SOC没有任何意义。您必须忘记它并继续前进。
如果像我一样经历了浏览器战争,您可能会觉得这个想法相当冒犯。克服它,它会值得的,我保证。
插件扩展jQuery。AngularJS指令扩展浏览器的功能。
在jQuery中,我们通过将函数添加到jQuery.prototype来定义插件。然后通过选择元素并在结果上调用插件来将它们钩入DOM中。其思想是扩展jQuery的功能。
例如,如果您想在页面上使用旋转木马,您可以定义一个无序的图像列表,可能包装在nav元素中。然后,您可以编写一些jQuery代码来选择页面上的列表,并将其重新设计为具有超时执行滑动动画的画廊。
在AngularJS中,我们定义指令。指令是返回JSON对象的函数。该对象告诉AngularJS要查找哪些DOM元素以及对它们进行哪些更改。指令使用属性或元素将其挂接到模板中,这些属性或元素是您自己发明的。其思想是使用新的属性和元素扩展HTML的功能。
AngularJS的方法是扩展本地外观的HTML的功能。您应该编写看起来像HTML的HTML,其中包含自定义属性和元素。
如果您想使用旋转木马,请只需使用<carousel />
元素,然后定义一个指令来引入模板,并使其正常工作。
在jQuery中的倾向是编写非常大的插件,例如light box,然后通过传递众多值和选项来进行配置。
在AngularJS中这是一个错误。
以下拉菜单为例。当编写下拉菜单插件时,您可能会试图编写点击处理程序,或者添加一个朝上或朝下的尖头函数,也许更改未展开元素的类,显示/隐藏菜单等等有用的东西。
但是当您想要进行一些小更改时就会遇到问题。
比如说,您希望在悬停时展开菜单。现在我们有一个问题了。我们的插件已经为我们连接了点击处理程序,我们需要添加一个配置选项来使它在特定情况下表现不同。
在AngularJS中,我们编写较小的指令。我们的下拉指令将非常简短。它可能会保持折叠状态,并提供fold()、unfold()或toggle()等方法。这些方法将简单地更新$scope.menu.visible,该布尔值保持状态。
现在,在我们的模板中,我们可以将其连接起来:
<a ng-click="toggle()">Menu</a>
<ul ng-show="menu.visible">
...
</ul>
需要在鼠标悬停时更新吗?
<a ng-mouseenter="unfold()" ng-mouseleave="fold()">Menu</a>
<ul ng-show="menu.visible">
...
</ul>
因为数据绑定是从模板中完成的,可以使用属性或大括号语法,所以非常容易实现。它几乎没有认知负荷,所以你会发现自己一直在使用它。
<input ng-model="user.name" />
$scope.user.name
。更新输入将更新当前作用域中的值,反之亦然。<p>
{{user.name}}
</p>
将会在段落中输出用户名。这是一个实时绑定,所以如果$scope.user.name
的值被更新,模板也会被更新。
在jQuery中,发起Ajax调用非常简单,但你可能会考虑两次。需要考虑额外的复杂性和大量需要维护的脚本。
在AngularJS中,Ajax是默认解决方案,几乎无需注意就能一直使用它。可以使用ng-include包含模板。可以使用最简单的自定义指令应用模板。可以在服务中包装一个Ajax调用并创建GitHub服务或Flickr服务,然后轻松访问。
在jQuery中,如果我们想完成一个小的非DOM相关任务(例如从API中获取信息),我们可能会在闭包中编写一个小函数来完成。那是一个有效的解决方案,但如果我们经常需要访问该信息怎么办?如果我们想在另一个应用程序中重用该代码怎么办?
AngularJS提供了服务对象。
服务是包含函数和数据的简单对象。它们始终是单例的,这意味着永远不会有多个。假设我们想要访问Stack Overflow API,我们可以编写一个StackOverflowService
,其中定义了执行此操作的方法。
假设我们有一个购物车。我们可以定义一个ShoppingCartService来维护我们的购物车,并包含添加和删除项目的方法。由于服务是单例的,并且由所有其他组件共享,因此需要使用购物车并从中提取数据的任何对象都可以将其写入购物车。它始终是同一个购物车。
服务对象是自包含的AngularJS组件,我们可以根据需要使用和重用它们。它们是包含函数和数据的简单JSON对象。它们始终是单例的,因此如果您在一个地方存储服务上的数据,则可以通过请求相同的服务在其他地方获取该数据。
AngularJS为您管理依赖项。如果您想要一个对象,只需引用它,AngularJS将为您获取它。
在你开始使用它之前,很难解释这是多么巨大的时间优势。jQuery内没有类似AngularJS DI的东西。
DI意味着,你不需要编写和连接应用程序,而是定义一个组件库,每个组件由一个字符串标识。
比如说我有一个名为“FlickrService”的组件,它定义了从Flickr获取JSON feed的方法。现在,如果我想编写一个可以访问Flickr的控制器,我只需要在声明控制器时按名称引用“FlickrService”。AngularJS会负责实例化组件并使其可供我的控制器使用。
例如,在这里我定义了一个服务:
myApp.service('FlickrService', function() {
return {
getFeed: function() { // do something here }
}
});
myApp.controller('myController', ['FlickrService', function(FlickrService) {
FlickrService.getFeed()
}]);
你能描述一下必要的范式转变吗?
命令式 vs 声明式
使用 jQuery,您逐步告诉DOM需要发生什么。而使用AngularJS,您描述了想要的结果,但不描述如何实现。更多信息请参见这里。另外,请查看Mark Rajcok的答案。
我应该如何在架构和设计客户端Web应用程序方面有所不同?
AngularJS是一个完整的客户端框架,使用MVC模式(请查看其图形表示)。它非常注重关注点分离。
最大的区别是什么?我应该停止使用什么,使用什么代替?
jQuery是一个库。
AngularJS是一个美丽的客户端框架,高度可测试,结合了许多很酷的东西,如MVC、依赖注入、数据绑定等等。
它专注于关注点分离和测试(单元测试和端到端测试),这有助于测试驱动开发。
最好的开始方式是通过他们令人惊叹的教程。你可以在几个小时内完成步骤;然而,如果你想掌握背后的概念,他们包括大量参考资料供进一步阅读。
你可以在已经使用纯jQuery的现有应用程序中使用它。但是,如果您想充分利用AngularJS的功能,则可以考虑使用RESTful方法编写服务器端代码。是否有任何服务器端的考虑/限制?
谈到“范式转变”,我认为简短的回答足以。
在jQuery中,通常使用选择器来查找元素,然后对其进行绑定:
$('#id .class').click(doStuff);
在AngularJS中,您使用指令直接标记元素并将其绑定:
<a ng-click="doStuff()">
AngularJS不需要(也不希望)您使用选择器查找元素 - AngularJS的jqLite
与全面的jQuery
之间的主要区别在于,jqLite不支持选择器。
因此,当人们说“根本不要包含jQuery”时,主要是因为他们不希望您使用选择器;他们希望您学会使用指令。 直接而非选择!
jQuery可以让像getElementByHerpDerp
这样的漫长JavaScript命令更简洁,并且跨浏览器。
AngularJS允许您创建自己的HTML标签/属性,以便与动态Web应用程序良好配合使用(因为HTML是针对静态页面设计的)。
说“我有jQuery背景,如何思考AngularJS?”就像说“我有HTML背景,如何思考JavaScript?”你提出这个问题实际上表明你很可能不理解这两种资源的根本目的。这就是为什么我选择通过简单指出基本区别来回答问题,而不是逐项列出说“AngularJS利用指令,而jQuery使用CSS选择器来创建一个jQuery对象,然后做这个那个等等...”。这个问题不需要冗长的答案。
jQuery是一种使在浏览器中编写JavaScript更容易的方式。它可以缩短命令长度,实现跨浏览器等功能。
AngularJS扩展了HTML,因此您不必在所有地方都放置<div>
来制作应用程序。它使HTML实际上可以为应用程序工作,而不是其最初的设计目的:用于静态教育性网页。它通过JavaScript以巧妙的方式实现这一点,但从根本上说,它是HTML的扩展,而不是JavaScript。
jQuery:你会考虑在“查询DOM”中查找DOM元素并执行操作。
AngularJS:模型是真相,你总是从这个角度思考。
例如,当你从服务器获取数据并打算以某种格式在DOM中显示时,在jQuery中,你需要“1.查找”你想要放置此数据的DOM位置,“2.更新/追加”它,创建一个新节点或仅设置其innerHTML。然后,当你想要更新此视图时,你需要“3.查找”位置并“4.更新”。在AngularJS中,这个在同一上下文中完成查找和更新的循环已经消失了,因为你可以直接操作模型。
在AngularJS中,你有你的模型(JavaScript对象),模型的值告诉你关于模型(显然)和视图的信息,对模型的操作自动传播到视图,所以你不必考虑它。你会发现在AngularJS中,你不再需要在DOM中查找元素。
换句话说,在jQuery中,您需要考虑CSS选择器,即具有类或属性的这些答案都很好,但是有点冗长。
总结我的经验:
jQuery是一个DOM操作库。
AngularJS是一个MV*框架。
事实上,AngularJS是为数不多的几个JavaScript MV*框架之一(许多JavaScript MVC工具仍属于库类别)。
作为一个框架,它托管您的代码并负责决定何时以及如何进行调用!
AngularJS本身包含了jQuery-lite版本。因此,对于一些基本的DOM选择/操作,您真的不必包括jQuery库(这可以节省网络运行的字节数)。
AngularJS有“指令”的概念,用于DOM操作和设计可重用的UI组件,因此在需要执行DOM操作相关任务时,应该使用它(在使用AngularJS时,指令是唯一编写jQuery代码的地方)。
AngularJS涉及一些学习曲线(比jQuery更多 :-)。
-> 对于任何来自jQuery背景的开发人员,我的第一个建议是“在跳入像AngularJS这样的丰富框架之前,先学习JavaScript作为一种一流语言!”我通过艰苦的方式学到了以上事实。
祝好运。
它们就像苹果和橙子一样。你不应该比较它们。它们是两个不同的东西。AngularJs已经内置了jQuery lite,允许您执行基本的DOM操作,甚至不需要包含完整的jQuery版本。
jQuery专注于DOM操作。它解决了跨浏览器问题,否则你将不得不处理,但它不是一个像AngularJS这样允许你将应用程序划分为组件的框架。
AngularJs的一个好处是它允许您在指令中分离/隔离DOM操作。有内置指令可供您使用,例如ng-click。您可以创建自己的自定义指令,其中将包含所有视图逻辑或DOM操作,以便您不会混合控制器或服务中应该处理业务逻辑的DOM操作代码。
Angular将您的应用程序分解为 - 控制器 - 服务 - 视图 - 等等
还有一件事,那就是指令。它是一个属性,您可以将其附加到任何DOM元素上,并且可以在其中使用jQuery而不必担心您的jQuery会与AngularJs组件发生冲突或破坏其架构。
我从一个我参加的聚会上听说,Angular的创始人之一说他们非常努力地分离DOM操作,因此不要尝试将它们包含在内。