angular2 es6 有必要用es6开发吗

Angular 2.0 的设计方法和原则 - 文章 - 伯乐在线
& Angular 2.0 的设计方法和原则
在开始实现Angular 2.0版本之际,我们认为应该着手写一些东西,告诉大家我们在设计上的考虑,以及为什么做这样的改变。现在把这些和大家一起分享,从而有助我们做出正确选择。
Angular 2 是一个针对移动应用的框架。它同时也支持桌面环境,但是移动端是难点,我们把它放在第一位。你了解并且喜爱的Angular还在那里,数据绑定,可扩展的HTML,以及专注可测试开发。
Angular 2 所有的设计文档都公开在上。而这篇文档概括了我们的方法和设计原则,文中的一些链接指向特定的设计文档。
警告:我们仍然在Angular 2 的设计和原型开发阶段。所以这篇文中的有些东西可能会跟我们的最终产品不一样,甚至完全不存在。
为未来而设计
我们基于未来人们的使用方式而设计Angular 2。尤其,这意味着我们的针对是现代浏览器以及使用ECMAScript 6.
现代浏览器
现代浏览器是指那些“永远绿色”或者总是能自动更新到最新版本的浏览器。针对这些浏览器的开发让我们扔掉了很多深入篡改和变通方案,而这些正是让Angular的使用和开发更加困难的罪魁祸首。
目前这些浏览器包括Chrome、FireFox、Opera、Safari和IE 11。在移动端,我们的支持列表包括Android、IOS6+、Windows Phone 8+上的Chrome,FireFox移动版等。我们在研究对Android老版本的支持,但是还没有决定下来。
是的,现在仍然有很多老的浏览器在被使用,但是当Angular 2准备好的时候,我们现在针对的这些浏览器将会是主流,而且如你一样优秀的开发人员肯定已经在上面开发过一段时间的应用了。
ECMAScript 6+()
所有的Angular 2的代码都已经是基于ES6写的。由于现在ES6还没有在浏览器上运行,我们使用 来生成能够在任何地方良好运行的ES5代码。我们正在和 Traceur 团队一起工作,实现对一些扩展的支持,比如标注(annotation)和。
不用担心,尽管Angular会基于ES6,但是如果你不想移植的话,你仍然可以使用ES5来写。编译器会生成可读性很强的JS代码,针对扩展也有可以理解的模拟实现。阅读来查看更多信息。
更快的更新监测()
Angular应用是在DOM和JS对象的数据绑定的基础上构建的。Angular应用的速度很大程度上取决于我们底层使用的更新监测机制。当在Chrome M35上能用的时候,我们已经反复地说过我们多么希望能够使用它来加速我们的更新监测机制。当然我们会这么做!
然而,经过了非常细致的分析(细节见文档),我们还发现即使在浏览器还未支持原生更新监测的情况下,我们已经能够很大程度上提高Angular速度和内存使用效率。所以,极度平滑顺畅的应用指日可待。
仪表化()
性能杠杆的另一半就是你写的代码。为了这,我们会提供高精细度的计时细节来还显示你的应用中的时间花销。你可以把这个看成更新监测设计的一个目标,但是我们会通过一个名叫diary.js的新的Angular日志服务的来为计时提供直接的支持。
当我们发布 Angular 1.0 的时候,所有的功能是在一个“要么接受要么放弃的”单独包里。不管你用不用它,你都得承担每个部分的下载开销。尽管整个Angular对于桌面应用来说是非常小的,但是在移动设备上却是完全不同的情景。
我们注意到一件很有意思的事情是,当我们吧$resource分离成一个单独的库的时候,冒出了好几个非常有创意的替代品。
为了性能和提倡创新,我们的目标是将Angular几乎所有的部分都做成可选的,可替代的,甚至是可以在其他的非Angular框架中使用。你可以挑选和使用你喜欢的部分,另外的部分你可以自己实现或者使用其他你更喜欢的方案。
其他性能话题
尽管目前还没有设计,我们还会涉及很多Angular其他方面的性能优化。从使用预编译的模板来改进第一次加载时间到保证60帧每秒的顺滑动画,我们会在用户体验上做非常深度的调研。请帮助我们,告知我们应该关注的点,并在解决方案中给予我们技术上的支持。
依赖注入()
依赖注入依然是 Angular 区别于客户端框架的关键所在,它帮你消除了很多应用中的连接性代码,并且使默认的可测试性变成了现实。尽管在我们开发应用的时候已经很享受依赖注入带来的好处,但我们对目前的实现仍然不满意。我们可以让他变的更简单且功能更强大。
我们会看到一个更加简化的依赖注入,移除了配置阶段,使用声明式的ES6+标注取代命令式的代码来简化语法。通过依赖注入和ES6模块化的模块加载的集成而获得更加强大的功能。我们还会看到使用子注入器(child injector)的方式来实现模块延时加载的特性。
上面文档链接中是我们的初步设想,但是它是Angular 2中你现在就可以尝试的部分。你可以在这个中看到目前实现的细节。
模板和指令()
能够直接使用HTML来定义模板,以及扩展HTML的语法,这些都是Angular赖以生存的东西。我们对Angular 2 的模板编译器新增了很多高级的改进:
简化指令的API
支持与其他标准Web框架的集成
允许IDE等工具对模板进行分析和验证
对上面的这些内容,我们非常激动,以至于迫不及待地炫耀它们。有太多优秀的东西而不能都在这篇概要中摘录,所以请直接跳到来查看更多内容。
更强大的功能
触摸动画()
用户们已经习惯于一些特定的触摸行为模式。比如,使用手指来滚动一个列表,循环查看轮播中的照片,通过滑动来删除一个列表项。然而:
目前的对于轮播、无限滚动等的实现,都没有共享通用的核心代码,因此有一堆各种各样的冗余和差异。
目前的大多数的实现对原生的滚动事件都不提供可选的支持,因为老的浏览器甚至一些现代的浏览器对它们支持不好。然而,这些实现,也就不能在新设备上达到本来可以有的最佳性能。
我们想给这些场景以最一流的支持,来让你的应用尽可能达到最佳的用户体验。
初始的Angular路由只为一些简单的用例而设计的。随着Angular的成长,我们已经渐渐的加入了一些新的功能。然而,底层的设计始终不适合做更多深层次的扩展。
我们非常关注一些已知的用例以及其他很多应用框架的路由的实现,这样我们才能交付一个简单而又可扩展的路由,能够广泛地适用于各种应用。
我们特别热衷于支持的一些用例包括:
基于状态(State-based)的路由
集成授权和认证
选择性地保留一些视图的状态,移动端尤其需要。
持久化()
在Angular简陋的 $http 之上,很多开发者渴望一个更高层次的抽象来处理来自服务器端的数据以及浏览器的本地持久化存储。
移动应用需要在一个“一直离线”的模式下工作,通过同步与服务器端保持一致。RESTful服务需要的远比我们$resource提供的更多。有些数据需要能够批量更新,而有些需要持续的流连接。
在这个新的持久化层,我们会为这些情景提供干净的架构,如果需要的话会从当前的样板文件中剔除更多。
什么时候能做完?
如果你和我们一起经历了 1.2 版本的发布,你应该知道我们也不知道答案。:)尽管我们现在才发表设计文档,但我们已经为很多模块做过了原型。依赖注入和Zone.js甚至已经可以使用了。所有的工作都会在GitHub上完成,我们也会继续发布,你可以一直关注。
Angular 1.x 到 Angular 2 的移植过程将是怎样的?
Angular 2 目前仍然在开发中,老实说我们也不知道。在我们的想象中,移植将会非常直接和简单,但是也不是不劳而获的。如何使用ES6的优势将是最大的工作重心。模板的更新几乎就是机械的查找和替换的练习。如果你的控制器中包含的是你的业务逻辑,而没有使用太多现有的Angular API的话,升级将会非常简单。最需要考虑的部分会是你对模块和指令的使用。
Angualr 2 会是 PhoneGap 或 Ionic框架等移动技术的替代品吗?
不是,Angular依然只是核心模块。你仍然需要使用其他的库,比如 Ionic框架来提供移动优化的 CSS/JS组件,PhoneGap之类的工具来打包和访问原生API。
Angular 2 和 AngularDart的关系是怎样的?
在将AngularJS向Dart语言移植的时候,我们运用所有我们学到的东西创建了一个新的Angular版本。这篇文档中讨论的很多改进已经在那里了,比如改良的指令的概念和语法,以及类和基于标注的依赖注入。
尽管这不是我们在 2 中要实现的目标,但它是对未来的一个很好的预览。
我们在打造AngularJS 2的同时,我们也会不断升级 AngularDart,这样喜欢Dart语言的人可以和喜欢JS的人享用到相同的好处。我们的目标是根据你选择的语言都会有一个单一的框架。
其他有趣的文档
关于作者:
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2016 伯乐在线React 和 Angular 相比,哪一个的学习曲线更陡峭?
按投票排序
看人了。有些答案里所谓的『后端经验』无非就是 java 经验啦。Angular 的初始曲线对于搞 java 的人来说确实不高,因为那一套概念都是 java 里面有的,而且搞 java 的都已经习惯繁琐了。但是对于不搞 java 的人来说,就很烦了,更关键的是在我看来前端根本就不应该学 java 那一套。Angular 的后续曲线也不低,因为它的可优化性比较糟糕,以至于你不理解它的内部运作原理就很难避免各种性能坑。React 上手的曲线也看人。对于习惯了服务端模板的人来说,要接受 JSX 是一个挑战;但是对于搞函数式的人来说,简直就是太自然了,因为 JSX 能让你把模板看做一个纯函数。过了这个坎后上手写两个例子,React 还是相对简单的。但是这也是个假象。接下来要搞『正经』的 React 应用了,突然出现了 Node,CommonJS, Webpack 一堆东西。写过 Node 的人会觉得哦哦,太自然了。没接触过 Node 的人就像没接触过 java 的人初学 Angular 一样:这些都是什么鬼。再往下,终于把构建环境搭好了,这时候你会发现 React 文档里的东西你已经也基本看完了,你以为你这就叫『学会 React』了?其实这时候你还是不知道怎么用 React 写一个应用,因为 React 本身不管这些事情,所以你要开始跳入 React 社区的大坑,开始学习 react-router, react-hot-reload, Flux 及其 N 种实现 () , CSS in JS 及其 N 种实现 (),Immutable-js, GraphQL, Relay... 还有 context 这种大家都在用但是没有官方文档的功能...(现在终于有文档了)用 Redux 又开始一堆概念了,actions, action creators, reducers, containers, higher order components... 然后现在又开始配着 generator 搞 redux-saga,搞着搞着你可能还会被 Clojure/Elm 吸引过去,跳入函数式的大坑... 这个曲线比起 Angular,只能说有过之而无不及... 当然,因为学习过程中多了很多可以用来装逼的资本,还是有一定的激励效果的,不像 Angular 学了半天也没什么能吹的。
Backbone:一开始就几个很简单的类和概念,看上去很完美,万事具备;都学会了,做个应用吧,操,完全做不出来。Backbone:一开始就几个很简单的类和概念,看上去很完美,万事具备;都学会了,做个应用吧,操,完全做不出来。Angular:一开始都没啥概念,随便绑定下就好了;然后开始跳坑,解决特殊需求的时候,就开始坑;然后就开始学习 Angular 里的各种概念了;可以做出一个应用,也可以很复杂;但是接下来就感觉无法前进了。React:开始不是那么简单,但作为前端来说沿途的风景是优美的;比起 Angular 没有那么复杂的概念,行为都是可以预知的;接下来就是不停地点技能树。
简单总结一下,angular适合做表单,而且所有的mvvm都适合做表单。然后大型的mvvm比如angular会提供一系列和表单相关的支持react适合做交互app,比如facebook的聊天页面啊,微博啊,或者在线编辑器啊,这种状态复杂的东西,典型的比如印象笔记。jquery更适合做没有数据的活动页,宣传页,交互很多,dom为中心。所以实际上这东西和曲线没啥关系,两者根本不能在典型场景呼唤,学会在合适的时候用合适的东西才是最重要的。
拿React和Angular比较本身就没多大意义。如果非要比较的话,我认为React比Angular 1.x的Directive要简单。从做一个复杂完整的SPA的角度来说的话,就目前而言,我认为基于React会更难一些(主要以react的角度来谈一下)1. React 本身只是一个View层的Library,通过state来控制具体的Virtual DOM render,而复杂SPA所需要的额外所有功能都需要自己去寻找解决方案(backbone笑而不语…)。比如如果要增加前端路由则需要额外进行技术选型(react-router、react-routing等等等),更不要说涉及到异步请求的fetch,涉及到状态管理的Flux,flummox,redux等等…2. 需要深入理解React Component的life circle,以及基于state的开发思想,否则很容易做得不伦不类,说的好像自己项目用了react一样,实际上可能只不过是jquery的心套了层react的皮。更不要说flux系列具有一定的理解难度和学习成本。(另外,设计app的时候会更多的从顶层思考app的状态及状态之间的转化,但并非任何业务场景都适合,很有可能状态中间穿插着form表单所需要的双向绑定,或者不直接通过render而进行的dom操作)3. react和webpack联系还是比较紧密的,开发react app大多会接触到webpack,接触到webpack自然就会接触到webpack庞大的生态圈,深入下去又是一个具有不太平坦学习曲线的东东。4. 调试过程中不出意外还会用到react-dev-server,但是如果使用redux的话,还有redux-dev以及对应的各种插件…5. React文档和资源比angular要少很多,react还没有发布1.0,react本身也在迅速迭代和变化,这意味着它可以更好的进化,也意味着进化过程中跟随者要付出更大的成本。比如某些同学刚用熟flux,就出了reflux,Alt,flummox…用惯了flummox结果人家作者不维护了,说redux用着更爽,作者也跟着用一个口音很呆萌的德国小帅哥写的很呆萌的redux去了…6. react面相未来,我们可以使用很多ES6和ES7的语法来使得定义一个Component变的更简单,比如我们可以使用class static properties来初始化propType、defaultProps或者state,constructor初始化来代替getInitialState方法等,但react在es6/7中反而不支持使用mixin,需要额外引入第三方库来解决对应的问题。(另外,使用es6/7还需要额外了解如何配置babel,eslint,babel-eslint,eslint-react plugin等等,当然这和react无关,但在开发的时候很可能是需要去学习的。)7. 基于react的各种插件相比ng来说还是太少,并且大多不成熟,所以真是要用到第三方组件的时候可能需要自己wrap一个component出来。--------------我是分割线--------------我想对看到这里还想学React的同学说:“React虽然不是银弹,但真的很棒,咱们一起加油,学习React是一件很cool的事情!”其实,很多时候我们说的所谓的“坑”,实际上是更创新的思想,更适合某些业务场景的框架,更好的调试工具,更cool的语法,更有意思的编程环境。面对坑,有的人欣喜若狂,有的人踌躇不定,有的人未足不前,不过是对于突破舒适区不同的态度罢了。搞技术纯粹些比较好,因为不会不了解,所以想去学习和体验,仅此而已。手机党码字不易… 如果错误和纰漏,麻烦告知,不胜感激。
其实题主到底是想问AngularJS和React到底哪个学习曲线陡峭呢?还是哪个入门简单呢?首先声明我使用的是ReactJS,就是那个纯洁无瑕的官方文档介绍的不带其他bling bling功能特性的React,所以如果没有阐述到其他没接触过的,比如React Native,请各位理解。鉴于我只是入门,而且私觉得题主在问React入门的问题,我就说说自己入门的经历好了------这段时间刚好用React搭建了一个简单的网站,此前基本没多少实际的前端开发经验,因为我都是写后端服务的,然后之前也接触过AngularJS,但是也没实际应用于应用开发中,这次头脑发热想用React作为前端MVC的V,其实大体上也就用了几天时间吧,就【入门】了,至少题主说的JSX什么的,我觉得并不难,一开始只是比较恶心,觉得大量JS和大量标签混在一起写不是很好的风格,所以至今还无法全然接收React的风格,但是也没觉得有多难,懂一点HTML,参照着文档就可以搞出来。至于我是怎么学的呢?其实就是看官方文档,学Anugular的时候我还专门跑到国内某比较有名的IT学习网站去看了几位名人的讲解,现在想想都没有带着实际的项目开发需求去学习来得靠谱。首先按照自己的思维写了个大概,页面中几个重要模块我都进行了独立拆分,各自逻辑开发完后我突然发现,模块之间怎么通信呢?好的,React提供了Flux,我又花了大半天把Flux理解了一遍,发现自己来写似乎比较烦,于是还自学了一些NodeJS(此前有了解过Node),用EventEmitter写了一个简单的全局事件机制,终于可以愉快地完成我那几个独立模块的通信和同步了,OK,网站逻辑完成。但是我深深地觉得这样的网站始终有问题,于是稍稍深入看了一下React文档(其实也就那么一些罢了),突然我发现,我模块化网站内容的方式不对,本可以被组织到一个高层组件的模块全都被我独立拆分了,导致到处都是事件订阅和通知,于是我重新整理了一下,用一个高层组件处理数据模型更新,再通过props传递自动完成其他组件的自动更新,于是乎我发现,整个JS的逻辑变得很清晰简单。而对于确实是独立的组件间通信,我们有一些别人实现好的Flux框架可以用。总体来说,我个人觉得我应该了解到的React基本齐全了,因为按照官方文档的内容,我觉得已经掌握地差不多了,这个时候我自称我自己的React水平是:【入门】(难道不是吗,你打我呀)期间还要写写后台逻辑,还因为不喜欢手动转换JSX而找了一堆前端模块化和构建工具,首先是简单地用seajs,发现不好,又考察了browserify,和webpack,了解了webpack配置文件的编写。总之,我就这么入门了(各位前端大侠轻喷,我只是临时过来写前端的)。那么,React入门难吗,对于我这种没有太多前端工程经验,顶多帮别人用jquery写写ajax的程序员来说,都觉得还好。而AngularJS呢?我觉得就像题主说的,入门也不难,但是必然也会在实践中踩一些坑,然后把写好的代码重新构造一遍,这个时候往往是刷新自己对框架认知的时候,这一步是很重要的,一定要硬着头皮上。当然如果这一步有高人在一开始给你点明,估计入哪个们都不难。最后个人觉得,React全量的高效刷新比起AngularJS绑定更新来说还是方便一些的,整套逻辑的数据流动很简单清晰,从上到下,子组件都是弱状态或者无状态,一致性比较好保证。有机会还是想实际用用AngularJS,说不定又是一次奇妙的开发经历。说得不对的,请轻拍指正~
作为一个新手我得到过长者的教诲,他告诉我我年纪轻轻不要碰的10件东西,其中就有Angular
以前做Web前端,或者传统客户端WinForm和Swing的,或者安卓、iOS的学React快以前做后端的学Angular快以前做Flex和WPF的学两个都快
我就是个java程序员,偶尔写个前端,用过angular,感觉angular毕竟是个前端框架控制整个页面的逻辑和数据还是不错的,在服务端被视作一个mvc框架;react感觉毕竟是个view,在服务端可能被视为一个模板引擎;服务端程序员的比喻,不恰当的话,希望勿喷。
Angular是长期坑。很多人说React不好学是因为他们大部分都偏后端,对npm生态不熟悉。Angular2是巨坑……但是总得来说还是很优秀的,上手难度更高,实用性跟React平手。
哈哈,亲身体验告诉你:Angular要比React陡峭的多。
已有帐号?
无法登录?
社交帐号登录Angular 1.x和ES6的结合 - WEB前端 - 伯乐在线
& Angular 1.x和ES6的结合
在Web前端技术飞速发展的今天,Angular 1.x可以说是一个比较旧的东西,而ES6是新生事物。我们想要把这两个东西结合起来,感觉就好像“十八新娘八十郎,苍苍白发对红妆。”但这件事的难度也并不大,因为我们最终是要把ES6构建成ES5代码,而ES5代码是可以很容易和Angular 1.x协作的。
不过,为什么我们要干这件事呢?
在中,我提到过:
尽管在整个前端开发圈中,大家并不是很欢迎Angular,而且很多人认为它的1.x版本已经衰落,但我跟 @小猪有个观点是一致的,那就是:“在企业开发领域,ng1的应用才方兴未艾”,也就是说,它在这个领域其实还是上升阶段。
所以,在不少场合下,它还是要承载一些开发工作,部分老系统的逐步平滑迁移也是比较重要的。
做这件事的另外一个意图是:虽然未来的框架选型会有不少争议,但有一点毋庸置疑,那就是业务JS代码的全面ES6或者TS化,这一点我们现在就可以着手去做,并且可以尽量把数据和业务逻辑层实现成框架无关的形式。
在里大致讲了点对这方面的考虑。
Angular 1.x的module机制是比较别扭的,也是一种框架私有的模块机制,所以,我们需要淡化这层东西,具体的措施是:
把各功能模块的具体实现代码独立出来
module机制作为一个壳子,对功能模块进行包装
每个功能分组,使用一个总的壳子来包装,减少上级模块的引用成本
每个壳子文件把module的name属性export出去
举例来说,我们有一个moduleA,里面有serviceA,serviceB,那么,就有这样一些文件:
serviceA的实现,service/a.js
JavaScript
export default class ServiceA {}
export default class ServiceA {}
serviceB的实现,service/b.js
JavaScript
export default class ServiceB {}
export default class ServiceB {}
moduleA的壳子定义,moduleA.js
JavaScript
import ServiceA from "./services/a";
import ServiceB from "./services/b";
export default angular.module("moduleA", [])
.service("ServiceA", ServiceA)
.service("ServiceB", ServiceB)
import ServiceA from "./services/a";import ServiceB from "./services/b";&export default angular.module("moduleA", [])&&&&.service("ServiceA", ServiceA)&&&&.service("ServiceB", ServiceB)&&&&.name;
存在一个moduleB要使用moduleA:
JavaScript
import moduleA from "./moduleA";
export default angular.module("moduleB", [moduleA]).
import moduleA from "./moduleA";&export default angular.module("moduleB", [moduleA]).name;
注意,这里为什么我们要export module的name呢?这是为了这个module的引用者方便,如果某个module改名了,所有依赖它的module可以不修改代码。
在这里我们可以看到,a.js,b.js,moduleA.js这三个文件,只有moduleA是作为一次性的配置项,而a和b可以尽量实现成框架无关的代码,这样将来的迁移代价会比较小。
service,factory,controller,filter
在Angular 1.x里面,有factory和service两个概念,其实这两者可以替换,service传入的是构造函数,通过new创建出实例,而factory传入的是工厂函数,通过对这个工厂函数的调用而创建实例。
所以,如果要使用ES6代码来编写这个部分,也就很自然了:
serviceA的实现,service/a.js
JavaScript
export default class ServiceA {}
export default class ServiceA {}
serviceA的模块包装器moduleA的实现
JavaScript
import ServiceA from "./service/a";
export angular.module("moduleA", [])
.service("ServiceA", ServiceA)
import ServiceA from "./service/a";&export angular.module("moduleA", [])&&&&.service("ServiceA", ServiceA)&&&&.name;
factoryA的实现,factory/a.js
JavaScript
import EntityA from "./model/a";
export default function FactoryA {
return new EntityA();
import EntityA from "./model/a";&export default function FactoryA {&&&&return new EntityA();}
factoryA的模块包装器moduleA的实现
JavaScript
import FactoryA from "./factory/a";
export angular.module("moduleA", [])
.factory("FactoryA", FactoryA)
import FactoryA from "./factory/a";&export angular.module("moduleA", [])&&&&.factory("FactoryA", FactoryA)&&&&.name;
注意看这个例子中,FactoryA函数的返回结果是new EntityA,在实际项目中,这里不一定是通过某个实体类创建的,也可能是直接一个对象字面量:
JavaScript
export default function FactoryA {
export default function FactoryA {&&&&return {&&&&&&&&a: 1&&&&};}
在ES6下,factory的定义其实可以有一些优化,比如说,我们可以不需要factory/a.js这个文件,也不需要这层factory封装,而是在module定义的地方,这样写:
JavaScript
import EntityA from "./model/a";
export angular.module("moduleA", [])
.factory("FactoryA", () =& new EntityA())
import EntityA from "./model/a";&export angular.module("moduleA", [])&&&&.factory("FactoryA", () =& new EntityA())&&&&.name;
使用ES6定义controller的方式大致与service相同,
如何处理依赖注入
有一点值得注意,刚才我们提到的模块定义方式里,并没有考虑依赖注入,但实际业务中一般都要注入点东西,那怎么办呢?
有两种办法:
controllers/a.js
JavaScript
export default class ControllerA {
constructor(ServiceA) {
this.serviceA = ServiceA;
ControllerA.$inject = ["ServiceA"];
export default class ControllerA {&&&&constructor(ServiceA) {&&&&&&&&this.serviceA = ServiceA;&&&&}}&ControllerA.$inject = ["ServiceA"];
JavaScript
import ControllerA from "./controllers/a";
export angular.module("moduleA", [])
.controller("ControllerA", ControllerA);
import ControllerA from "./controllers/a";&export angular.module("moduleA", [])&&&&.controller("ControllerA", ControllerA);
controllers/a.js
JavaScript
export default class ControllerA {
constructor(ServiceA) {
this.serviceA = ServiceA;
export default class ControllerA {&&&&constructor(ServiceA) {&&&&&&&&this.serviceA = ServiceA;&&&&}}
JavaScript
import ControllerA from "./controllers/a";
export angular.module("moduleA", [])
.controller("ControllerA", ["ServiceA", ControllerA]);
import ControllerA from "./controllers/a";&export angular.module("moduleA", [])&&&&.controller("ControllerA", ["ServiceA", ControllerA]);
个人推荐前一种,理由是,一个模块的依赖项声明,最好跟其实现放在一起,这样对可维护性更有利。
在考虑依赖注入的时候,还存在另外一个问题,我们现在这样做,实质上已经弱化了Angular自身的DI,但这时候,为什么我们还需要DI?如果我们在一个Controller里面依赖某个Service,大可以直接import它啊,为什么还非要去从DI走一圈?
这里面有个麻烦,如果你所依赖的东西没有对Angular DI依赖,那还好,不然的话,没法实例化,比如说:
JavaScript
export default class ServiceA {
constructor($http) {}
ServiceA.$inject = ["$http"];
export default class ServiceA {&&&&constructor($http) {}}&ServiceA.$inject = ["$http"];
如果我要在一个别的东西里实例化这个ServiceA,就没法给它传入$http,这些东西要从ng里获取,考虑是不是搞个专门的实例化函数,类似provider,专门去做这个实例化,这样可以消除DI,直接import。
这个是终极纠结点了,因为一个directive,可能包含有compile,link等多个成员函数,各种配置项,一个可选controller之类,这里面我们要考虑这么一些东西:
directive自身怎么定义为ES6代码
里面的各项成员如何处理
controller如何定义
我们看一下directive主要包含些什么东西,它其实是一个ddo(Directive Definition Object),所以本质上这是一个对象,我们可以给它构建一个类。
JavaScript
export default class DirectiveA {
export default class DirectiveA {}
DDO上面的东西大致可以分两类,属性和方法,所以就在构造函数里这样定义:
JavaScript
constructor () {
this.template =
this.restrict = "E";
constructor () {&&&&this.template = template;&&&&this.restrict = "E";}
像这些都是基础的配置字符串,没什么特别的。剩下的就是controller和link,compile等函数了,这些东西其实也简单,比如controller,可以先实现一个普通controller类,然后赋值到controller属性上来:
JavaScript
this.controller = ControllerA;
this.controller = ControllerA;
注意现在写directive,尽量使用controllerAs这样的语法,这样controller可以清晰些,不必注入$scope,而且还可以使用bindToController属性,把在attr上定义的属性或者方法直接传递到controller实例上来。
比如我们要做一个日期控件,最后合起来就是这样:
JavaScript
import template from "../templates/calendar.html";
import CalendarCtrl from "../controllers/calendar";
import "../css/calendar.css";
export default class CalendarDirective {
constructor() {
this.template =
this.restrict = "E";
this.controller = CalendarC
this.controllerAs = "calendarCtrl";
this.bindToController =
this.scope = {
minDate: "=",
maxDate: "=",
selectedDate: "=",
dateClick: "&"
link (scope) {
// 这段代码太别扭了,但问题是如果搬到controller里面去写成setter,会在constructor之前执行,真头疼,先这样吧
scope.$watch("calendarCtrl.selectedDate", newDate =& {
if (newDate) {
scope.calendarCtrl.calendar.year = newDate.getFullYear();
scope.calendarCtrl.calendar.month = newDate.getMonth();
scope.calendarCtrl.calendar.date = newDate.getDate();
123456789101112131415161718192021222324252627282930313233
import template from "../templates/calendar.html";import CalendarCtrl from "../controllers/calendar";&import "../css/calendar.css";&export default class CalendarDirective {&&&&constructor() {&&&&&&&&this.template = template;&&&&&&&&this.restrict = "E";&&&&&&&&&this.controller = CalendarCtrl;&&&&&&&&this.controllerAs = "calendarCtrl";&&&&&&&&this.bindToController = true;&&&&&&&&&this.scope = {&&&&&&&&&&&&minDate: "=",&&&&&&&&&&&&maxDate: "=",&&&&&&&&&&&&selectedDate: "=",&&&&&&&&&&&&dateClick: "&"&&&&&&&&};&&&&}&&&&&link (scope) {&&&&&&&&// 这段代码太别扭了,但问题是如果搬到controller里面去写成setter,会在constructor之前执行,真头疼,先这样吧&&&&&&&&scope.$watch("calendarCtrl.selectedDate", newDate =& {&&&&&&&&&&&&if (newDate) {&&&&&&&&&&&&&&&&scope.calendarCtrl.calendar.year = newDate.getFullYear();&&&&&&&&&&&&&&&&scope.calendarCtrl.calendar.month = newDate.getMonth();&&&&&&&&&&&&&&&&scope.calendarCtrl.calendar.date = newDate.getDate();&&&&&&&&&&&&}&&&&&&&&});&&&&}}
然后,在module定义的地方:
JavaScript
import CalendarDirective from "./directives/calendar";
export default angular.module("components.form.calendar", [])
.directive("snCalendar", () =& new CalendarDirective())
import CalendarDirective from "./directives/calendar";&export default angular.module("components.form.calendar", [])&&&&.directive("snCalendar", () =& new CalendarDirective())&&&&.name;
上面这个例子里,还有些比较头疼的地方。本来我们剥离了清晰的controller,就是为了里面不要有$scope这些奇奇怪怪的东西,但我们需要$watch这个selectedDate的赋值,就折腾了,$watch是定义在$scope上面的,而如果在controller上给selectedDate定义一个setter,可能由于babel跟angular共同的作用,时序有点问题……后面再想办法优化吧。
一个directive除了有这些,还可以有template的定义,所以在这个例子里我们也是用import把一个html加进来了,Webpack的html loader会自动把它变成一个字符串。
还有,组件化的思想指导下,单个组件也应当管理自己的样式,所以我们在这里也import了一个css,这个后面会被Webpack的css loader处理。
消除显式的$scope
我们前面提到,做这套方案有一个很重要的意图,那就是在数据和业务逻辑层尽量清除Angular的影子,使得除了最上层的部分,其他都可以被其他框架方案使用,比如React和Vue,这里面有一些关键。
在Angular 1.x中,一个核心的东西是$scope,它是一切东西运行的基石,然而,把这些东西暴露给一线开发者,其实并不优雅,所以,Angular 1.2之后,逐步提供了一些选项,用于减少开发过程中对$scope的显式依赖。
那么,我们可能会在什么场景下用到$scope,主要用到它的什么能力呢?
controller中注入,给界面模板中的绑定变量或者方法用
依赖属性的计算,比如说我们可以手动$watch一个变量、对象、数组,然后在变更回调中更改另外的东西
事件的冒泡和广播,根作用域
directive中的controller,link等函数使用
我们一个一个来看,这些东西怎么消除。
controller注入
以前我们一般要在controller中注入$scope,但是从1.2版本之后,有了controllerAs语法,所以这个就不再必要了,之前是这样:
JavaScript
&div ng-controller="TestCtrl"&
&input ng-model="aaa"&
&div ng-controller="TestCtrl"&&&&&&input ng-model="aaa"&&/div&
JavaScript
xxx.controller("TestCtrl", ["$scope", function($scope) {
$scope.aaa = 1;
xxx.controller("TestCtrl", ["$scope", function($scope) {&&&&$scope.aaa = 1;}]);
现在成了:
JavaScript
&div ng-controller="TestCtrl as testCtrl"&
&input ng-model="testCtrl.aaa"&
&div ng-controller="TestCtrl as testCtrl"&&&&&&input ng-model="testCtrl.aaa"&&/div&
JavaScript
xxx.controller("TestCtrl", [function() {
this.aaa = 1;
xxx.controller("TestCtrl", [function() {&&&&this.aaa = 1;}]);
这里的关键点就在于,controller变成了一个纯净的视图模型,实际上框架会做一件事:
JavaScript
$scope.testCtrl = new TestCtrl();
$scope.testCtrl = new TestCtrl();
所以,对于这一块,其实我们是不必担忧的,把那个function换成一个普通的ES6 Class就好了。
依赖属性的计算
我们知道,在$scope上,除了有$watch,$watchGroup,$watchCollection,还有$eval(作用域上的表达式求值)这类东西,我们必须想到对它们的替代办法。
先来看看$watch,一个典型的例子是:
JavaScript
$scope.$watch("a", function(val) {
$scope.b = val + 1;
$scope.$watch("a", function(val) {&&&&$scope.b = val + 1;});
这个我们的办法很简单,在ES5+,对象上是有setter和getter的,那我们只要在ES6代码里这么定义就行了:
JavaScript
set a(val) {
this.b = val + 1;
class A {&&&&set a(val) {&&&&&&&&this.b = val + 1;&&&&}}
如果有多个变量的观测,比如:
JavaScript
$scope.$watchGroup(["firstName", "lastName"], function(val) {
$scope.fullName = val.join(",");
$scope.$watchGroup(["firstName", "lastName"], function(val) {&&&&$scope.fullName = val.join(",");});
我们可以写多个setter来做,也可以写一个getter:
JavaScript
get fullName() {
return this.firstName + "," + this.lastN
class A {&&&&get fullName() {&&&&&&&&return this.firstName + "," + this.lastName;&&&&}}
下一个,$watchCollection,这个有些复杂,因为它可以观测数组内部元素的变化,但其实JavaScript语法层面是缺少一些东西的,对比其他语言,早在十多年前,C# 1.0中就支持了indexer,也就是可以自定义下标操作。
不过这个也难不倒我们,在Adobe Flex里面,有一个ArrayCollection,实际上是封装了对于数组的操作,所以,我们需要的只是把数组的变更操作封装起来,不直接在原始数组上进行操作就好了。
所以我们的结构就类似如下:
JavaScript
constructor() {
this.arr = [];
add(item) {
this.arr.push[item];
//这里干点别的
1234567891011
class A {&&&&constructor() {&&&&&&&&this.arr = [];&&&&}&&&&&add(item) {&&&&&&&&this.arr.push[item];&&&&&&&&&//这里干点别的&&&&}}
对于这个封装好的东西,我们的原则是:读取操作可以直接取引用,但是写入操作必须通过封装的这些方法去调用。
这里还有技巧,我们其实是可以把这类数组操作全部封装,也搞成类似ArrayCollection那样,但很多时候,ArrayCollection太通用了,我们其实要的是强化的领域模型,而不是通用模型。所以,针对每个业务模型单独封装,有其自身的优势。
注意,我们这里仅仅是封装了数组元素的操作,并未对元素自身属性的变更,或者高维数组,这些需要多层封装。
事件的冒泡和广播
在$scope上,另外一套常用的东西是$emit,$broadcast,$on,这些API其实是有争议的,因为如果说做组件的事件传递,应当以组件为单位进行通信,而不是在另外一套体系中。所以我们也可以不用它,比较直接的东西通过directive的attr来传递,更普遍的东西用全局的类似Flux的派发机制去通信。
根作用域的问题也是一样,尽量不要去使用它,对于一个应用中全局存在的东西,我们有各种策略去处理,不必纠结于$rootScope。
directive等地方中的$scope
哎,其实理论上是可以把业务代码中每个地方都搞得完全没有$scope的,而且也能比较优雅通用,但是。。。总有一些例外。
先看看正常的吧。
我们知道,在定义directive的时候,ddo中有个属性是scope,这个里面定义了要在directive内外进行传递的属性或者方法,并且有不同的传递类型。我们又知道,directive有个controllerAs选项,可以类似前面提到的,controller中不注入$scope:
JavaScript
class TestCtrl {
constructor() {
this.a = 1;
export default class CalendarDirective {
constructor() {
this.controller = TestC
this.controllerAs = "testCtrl";
this.scope = {
1234567891011121314151617
class TestCtrl {&&&&constructor() {&&&&&&&&this.a = 1;&&&&}}&export default class CalendarDirective {&&&&constructor() {&&&&&&&&//...&&&&&&&&this.controller = TestCtrl;&&&&&&&&this.controllerAs = "testCtrl";&&&&&&&&&this.scope = {&&&&&&&&&&&&a: "="&&&&&&&&};&&&&}}
这时候就有个问题了,我们知道,最终结构会变成:
JavaScript
$scope.testCtrl.a == 1;
$scope.testCtrl.a == 1;
JavaScript
this.scope = {
this.scope = {&&&&a: "="};
又会导致$scope.a == 1,而且,在testCtrl这个实例中,如果你不显式传入$scope,还访问不到外面那个a,这跟我们的预期是不相符的。所以,这时候我们要配合用bindToController,可以写个属性true,也可以把scope对象搬上去(1.4以上版本支持)。
所以代码就成了这样:
JavaScript
class TestCtrl {
constructor() {
this.a = 1;
export default class CalendarDirective {
constructor() {
this.controller = TestC
this.controllerAs = "testCtrl";
this.bindToController =
this.scope = {
123456789101112131415161718
class TestCtrl {&&&&constructor() {&&&&&&&&this.a = 1;&&&&}}&export default class CalendarDirective {&&&&constructor() {&&&&&&&&//...&&&&&&&&this.controller = TestCtrl;&&&&&&&&this.controllerAs = "testCtrl";&&&&&&&&this.bindToController = true;&&&&&&&&&this.scope = {&&&&&&&&&&&&a: "="&&&&&&&&};&&&&}}
这样都对了吗,并不会……
我们再综合一下:
JavaScript
class TestCtrl {
constructor() {
this.a = 1;
set a(val) {
this.b = val + 1;
export default class CalendarDirective {
constructor() {
this.controller = TestC
this.controllerAs = "testCtrl";
this.bindToController =
this.scope = {
12345678910111213141516171819202122
class TestCtrl {&&&&constructor() {&&&&&&&&this.a = 1;&&&&}&&&&&set a(val) {&&&&&&&&this.b = val + 1;&&&&}}&export default class CalendarDirective {&&&&constructor() {&&&&&&&&//...&&&&&&&&this.controller = TestCtrl;&&&&&&&&this.controllerAs = "testCtrl";&&&&&&&&this.bindToController = true;&&&&&&&&&this.scope = {&&&&&&&&&&&&a: "="&&&&&&&&};&&&&}}
这里,只是在TestCtrl中给a加了一个setter,然而这个代码是不运行的,貌似绑定过程有问题,所以我才会在上面那个地方加了个很别扭的$watch,也就是:
JavaScript
class TestCtrl {
constructor() {
this.a = 1;
export default class CalendarDirective {
constructor() {
this.controller = TestC
this.controllerAs = "testCtrl";
this.bindToController =
this.scope = {
link(scope) {
scope.$watch("testCtrl.a", val =& scope.testCtrl.b = val + 1);
12345678910111213141516171819202122
class TestCtrl {&&&&constructor() {&&&&&&&&this.a = 1;&&&&}}&export default class CalendarDirective {&&&&constructor() {&&&&&&&&//...&&&&&&&&this.controller = TestCtrl;&&&&&&&&this.controllerAs = "testCtrl";&&&&&&&&this.bindToController = true;&&&&&&&&&this.scope = {&&&&&&&&&&&&a: "="&&&&&&&&};&&&&}&&&&&link(scope) {&&&&&&&&scope.$watch("testCtrl.a", val =& scope.testCtrl.b = val + 1);&&&&}}
而且,这里再$watch的话,需要把controller实例的别名也作为路径放进去,testCtrl.a,而不是a。总之还是有些别扭,但我觉得这里应该还有办法解决。
// 上面这段等我有空详细再想想
有的时候,直接把setter或者getter绑定到界面,会不太适合,虽然Angular的ng-model中支持getterSetter这种辅助,但毕竟还有所不同,所以很多时候我们很多时候可能需要把带getter和setter的业务对象下沉一级,外面再包装一层给angular绑定用。
在任何一个严谨的项目中,应当有比较确定的业务模型,即使脱离界面本身,这些模型也应当是可以运作的,而ES6之类语法的便利性,使得我们可以更好地组织下层业务代码。即使目的不是为了使用Angular 1.x,这一层的精心构造也是有价值的。当做完这层之后,上层迁移到各种框架都基本只剩体力活了。
可能感兴趣的话题
关于伯乐前端
伯乐前端分享Web前端开发,包括JavaScript,CSS和HTML5开发技术,前端相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2016 伯乐在线

我要回帖

更多关于 es6 模块化开发 的文章

 

随机推荐