- Published on
在有jQuery背景下如何理解AngularJS?
- Authors
- Name
AngularJS与jQuery的观念差异
AngularJS 和 jQuery 采用了非常不同的理念。如果你来自 jQuery 的背景,可能会对某些差异感到惊讶。Angular 可能会让你有些恼火。
这是正常的,你应该坚持下去。相信我,Angular是值得的。
关键差异(TLDR)
jQuery 为你提供了一套工具包,允许你选择DOM的任意部分并对其进行临时性的更改。你可以逐块地做任何你想做的事情。
而 AngularJS 则为你提供了一个编译器。
AngularJS的工作原理
AngularJS 从上到下读取整个DOM,并将其视为代码,字面上当做是对编译器的指令。当遍历DOM时,它会寻找特定的指令(编译指令),这些指令告诉AngularJS编译器如何行为以及该做什么。指令是充满JavaScript的小对象,可以匹配属性、标签、类甚至注释。
当Angular编译器确定DOM的一部分与某个特定指令相匹配时,它会调用指令函数,传递给它DOM元素、任何属性、当前的$scope(这是一个局部变量存储)以及一些其他有用的东西。这些属性可能包含由指令解释的表达式,这些表达式告诉指令如何渲染以及何时应该重新绘制。
指令还可以进一步引入额外的Angular组件,如控制器、服务等。编译器的输出是一个完整的Web应用程序,该应用程序已经有了所有的连线和准备工作。
这意味着Angular是模板驱动的。你的模板驱动JavaScript,而不是反过来。这是一种角色的彻底转变,完全不同于我们过去十年编写的非侵入式JavaScript。这可能需要一些时间来适应。
如果这听起来可能是过度规范和限制,那就大错特错了。因为AngularJS将你的HTML视为代码,你在Web应用程序中获得了HTML级别的细粒度控制。一切皆有可能,而且大多数事情一旦你跨过了一些概念上的障碍,就会变得非常容易。
AngularJS并不替代jQuery
Angular 和 jQuery 做的是不同的事情。AngularJS 为你提供了一套工具来生成Web应用程序。 jQuery 主要为你提供了修改DOM的工具。如果页面上存在 jQuery, AngularJS 会自动使用它。如果不存在,AngularJS 会自带 jQuery Lite,这是一个精简但仍然非常可用的 jQuery 版本。
如果你确实使用 jQuery,不应到处撒它。正确的位置是 AngularJS 中的指令。稍后会详细讲解这些。
非侵入式JavaScript与声明式模板
jQuery通常是非侵入式应用的。你的JavaScript代码链接在头部(或尾部),这是唯一提到它的地方。我们使用选择器来挑选页面的部分,并编写插件来修改这些部分。
JavaScript是掌控者。HTML是独立存在的。即使没有JavaScript,你的HTML仍然具有语义。点击事件是非常不好的实践。
AngularJS的一个显著特点是自定义属性无处不在。你的HTML将布满ng属性,这些属性本质上是升级版的点击事件。这些是指令(编译指令),也是模板与模型连接的主要方式之一。
当你第一次看到这些时可能会认为AngularJS是老派的侵入式JavaScript(我起初也是这么认为的)。实际上,AngularJS并不按照这些规则行事。
模板驱动
使用AngularJS时,你会发现模板驱动应用程序的现象非常明显。甚至在没有编写任何JavaScript代码的情况下,你的应用程序也能表现出预期的行为。这是因为AngularJS将DOM视为代码,通过模板将模型与视图连接起来。
停止试图通过JavaScript驱动应用程序。让模板驱动应用程Angular.js.
语义HTML与语义模型
在使用jQuery时,你的HTML页面应包含语义丰富的内容。即使JavaScript被关闭,你的内容仍然可访问。
在AngularJS中,因为HTML被视为模板,语义信息被包含在模型中,最终来自你的API。你的HTML源代码不再具有语义,取而代之的是你的API和编译的DOM具有语义。
分离关注点(SOC)与MVC
分离关注点(SOC)是一种经过多年Web开发形成的模式,原因包括SEO、可访问性和浏览器不兼容问题。它的表现形式如下:
- HTML - 语义意义,HTML应该独立存在。
- CSS - 样式,没有CSS页面仍可阅读。
- JavaScript - 行为,没有脚本内容仍然存在。
AngularJS 并不遵循这些规则。相反,AngularJS 实现了一种MVC模式,其中模板不再具有任何语义性。
它的表现形式如下:
- Model - 模型包含你的语义数据,模型通常是JSON对象。
- View - 视图用HTML写成,通常不具有语义,因为数据在模型中。
- Controller - 控制器是一个JavaScript函数,负责将视图与模型连接起来,初始化$scope。
MVC和SOC并不在同一轴线上,它们是完全不同的概念。SOC在AngularJS上下文中没有意义。你需要忘记它们并向前看。
插件与指令
插件扩展了jQuery。AngularJS指令扩展了浏览器的功能。
在jQuery中,我们通过将函数添加到jQuery.prototype来定义插件,然后通过选择元素并对结果调用插件来将插件钩入DOM。目的是扩展jQuery的能力。
在AngularJS中,我们定义指令。指令是一个返回JSON对象的函数。这个对象告诉AngularJS应该查找哪些DOM元素,以及对它们进行哪些更改。指令通过自定义属性或元素来钩入模板。目的是通过新的属性和元素扩展HTML的功能。
AngularJS的方式是扩展原生的HTML。应使用看起来像HTML的自定义元素和属性。
多个小指令与大量配置开关的大插件
在jQuery中,我们倾向于编写大插件,如lightbox,并通过传递多个值和选项进行配置。
在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>
模板驱动应用程序,所以我们在HTML层面拥有了细粒度的控制。如果我们想做具体的例外处理,模板很容易实现。
闭包与$scope
jQuery插件是在闭包中创建的。闭包中的隐私得到了维护,你需要在闭包中维护你的作用域链。你只能访问传入插件的DOM节点集合、本地变量以及任何全局变量。这意味着插件是自包含的,但在创建整个应用程序时可能限制性的。
AngularJS有scope,默认情况下继承其包裹的scope对象在控制器和视图中都可以访问。
这是巧妙的部分。因为scope。
这使得数据传递变得更容易,并可以在适当级别存储数据。如果下拉菜单展开,只有下拉菜单的scope,所有监听用户偏好的嵌套作用域会自动收到通知。
手动DOM更改与数据绑定
在jQuery中,你需要手工进行所有的DOM更改。你需要编写函数来生成HTML并插入。
在AngularJS中,你也可以这么做,但AngularJS更鼓励使用数据绑定。更改你的模型,因为DOM通过模板与其绑定,DOM会自动更新,无需干预。
因为数据绑定是通过模板完成的,使用属性或花括号语法,很容易做到。它的认知负担很小,因此你会发现自己经常使用它。
<input ng-model="user.name" />
绑定输入元素到$scope.user.name
。更新输入会更新当前作用域中的值,反之亦然。
同样地:
<p>{{user.name}}</p>
将用户名称输出到段落中。这是一个实时绑定,因此如果$scope.user.name
值更新,模板也会更新。
全时Ajax
在jQuery中发起一个Ajax调用相对简单,但仍然需要考虑到附加的复杂性和维护大量脚本。
在AngularJS中,Ajax是默认的解决方案,几乎不需要意识到。你可以使用ng-include包含模板。你可以使用简单的自定义指令应用模板。你可以将Ajax调用包裹在服务中,创建一个GitHub服务或Flickr服务,非常容易访问。
服务对象与辅助函数
在jQuery中,如果我们需要完成一个小的非DOM相关任务,如从API提取数据源,我们可能会在闭包中编写一个小函数来完成。这是一种有效的解决方案,但如果我们需要经常访问数据源呢?如果我们想在另一个应用程序中重用这段代码呢?
AngularJS给我们提供了服务对象。
服务是包含函数和数据的简单对象。它们总是单例的,意味着只能有一个。比如我们想访问Stack Overflow API,我们可以编写一个StackOverflowService定义相应的方法。
假设我们有一个购物车。我们可以定义一个ShoppingCartService来维护我们的购物车,并包含添加和删除项目的方法。因为服务是单例的,并且由所有其他组件共享,任何对象都可以写入购物车并从中提取数据。
服务对象是自包含的AngularJS组件,可以随意使用和重用。它们是包含函数和数据的简单JSON对象,总是单例的。
依赖注入(DI)与实例化——告别“意大利面条式”代码
AngularJS为你管理依赖。如果你需要一个对象,简单地引用它,AngularJS会为你获取。
这一点在你开始使用之前可能难以理解其多大的时间节省。jQuery中没有类似的东西。
DI意味着你不再需要连接应用程序,而是定义一个组件库,每个组件用字符串标识。
假设我有一个叫做'FlickrService'的组件,定义了从Flickr提取JSON数据的方法。现在,如果我想编写一个控制器来访问Flickr,只需要在声明控制器时引用'FlickrService'。AngularJS会负责实例化组件并将其提供给控制器。
例如,这里我定义了一个服务:
myApp.service('FlickrService', function() {
return {
getFeed: function() { // 执行某些操作 }
}
});
现在,当我想要使用这个服务时,只需按名称引用它:
myApp.controller('myController', [
'FlickrService',
function (FlickrService) {
FlickrService.getFeed()
},
])
AngularJS会识别到需要一个FlickrService对象来实例化控制器,并为我们提供一个。
这使得连接组件变得非常容易,并且几乎消除了“意大利面条式”代码的倾向。我们有一个平坦的组件列表,AngularJS会根据需要一个一个地将它们递给我们。
模块化服务架构
jQuery对代码组织几乎没有要求。AngularJS则有所见解。
AngularJS给你模块,将你的代码放入其中。如果你正在编写一个与Flickr通信的脚本,你可能希望创建一个Flickr模块来封装所有与Flickr相关的功能。模块可以包含其他模块(通过DI)。你的主应用程序通常是一个模块,包含你的应用程序依赖的所有其他模块。
你可以简单地复用代码。如果你想编写另一个基于Flickr的应用,可以直接包含Flickr模块,然后你就可以访问所有与Flickr相关的功能。
模块包含了AngularJS组件。当我们包含一个模块时,该模块中的所有组件都会作为一个简单的列表向我们提供。我们可以使用AngularJS的依赖注入机制将这些组件注入到彼此中。
总结
AngularJS与jQuery并不对立。可以在AngularJS中很好地使用jQuery。使用AngularJS(模板、数据绑定、$scope、指令等)时,你会发现需要的jQuery代码会大大减少。
关键是要意识到,模板驱动应用程序。停止编写大插件,取而代之编写小指令,然后编写简单的模板来连接它们。
少考虑非侵入式JavaScript,而要更多地考虑HTML扩展。