你们中许多人都知道,ECMAScript 6现在处于草稿状态,预计将在今年的某个时候完成。 但是它已经引起了社区的广泛关注,并且浏览器已经开始实施它。 我们也有许多编译器,例如Traceur,6to5,以及许多其他将ES6代码转换为ES5兼容代码的编译器。 社区成员已经开始使用ES6,其中许多人正在博客中介绍他们所学到的知识。 SitePoint的JavaScript频道中也有很多文章描述了ES6的不同功能。
可以使用ES6编写任何日常JavaScript。 为此,我们需要了解ES6的关键功能,并知道哪一部分适合于何处。 在本文中,我们将看到如何使用ES6的功能来构建AngularJS应用程序的不同部分,并使用ES6模块加载它们。 我们将通过构建一个简单的在线书架应用程序来完成此任务,然后我们将了解其结构和编写方式。
与以往一样,可以在我们的GitHub存储库中找到此应用程序的代码。
书架应用注意事项
示例书架应用程序包含以下视图:
- 主页:显示活动书籍列表。 可以将书籍标记为已读并从此页面移至档案
- 添加书页:通过接受书名和作者姓名将新书添加到书架中。 不允许重复标题
- 存档页面:列出所有存档的书籍
设置ES6的应用程序
由于我们将使用ES6编写应用程序的前端部分,因此我们需要一个编译器,以使所有浏览器都可以理解ES6功能。 我们将使用Traceur客户端库即时编译我们的ES6脚本并在浏览器中运行它。 该库在凉亭上可用。 示例代码在bower.json
有此库的bower.json
。
在应用程序的主页上,我们需要添加对此库的引用和以下脚本:
traceur.options.experimental = true;
new traceur.WebPageTranscoder(document.location.href).run();
该应用程序的JavaScript代码分为多个文件。 使用ES6模块加载器将这些文件加载到主文件中。 由于当今的浏览器无法理解ES6模块,因此Traceur为我们填充了此功能。
在示例代码中, bootstrap.js
文件负责加载主要的AngularJS模块并手动引导Angular应用程序。 由于模块是异步加载的,因此我们无法使用ng-app
引导应用程序。 这是该文件中包含的代码:
import { default as bookShelfModule} from './ES6/bookShelf.main';
angular.bootstrap(document, [bookShelfModule]);
在这里, bookShelfModule
是包含所有部分的AngularJS模块的名称。 稍后我们将看到bookShelf.main.js
文件的内容。 bootstrap.js
文件使用以下脚本标记加载到index.html
文件中:
<script type="module" src="ES6/bootstrap.js"></script>
定义控制器
AngularJS控制器可以通过两种方式定义:
- 使用
$scope
控制器 - 使用控制器作为语法
第二种方法更适合ES6,因为我们可以定义一个类并将其注册为控制器。 通过控制器的别名可以看到与该类的实例相关联的属性。 另外, 作为语法的控制器与$scope
耦合相对较少。 如果您不知道, $scope
将从Angular 2的框架中删除,所以我们现在可以通过使用控制器作为语法来训练我们的大脑以减少对$scope
依赖。
尽管ES6中的类使我们摆脱了处理原型的困难,但它们不支持创建私有字段的直接方法。 有一些间接的方法可以在ES6中创建私有字段。 其中之一是使用变量在模块级别存储值,而不将其包括在导出对象中。
我们将使用WeakMap来存储私有字段。 选择WeakMap的原因是,一旦对象被垃圾回收,那些以对象为键的条目就会被删除。
如上所述,应用程序的主页将加载并显示活动书籍列表。 它依赖于一项服务来获取数据并将一本书标记为已读,或将其移至存档。 我们将在下一部分中创建此服务。 为了使注入控制器构造函数的依赖项在实例方法中可用,我们需要将其存储在WeakMaps中。 主页的控制器具有两个依赖项:执行Ajax操作的服务和$timeout
(用于显示成功消息并在特定时间后隐藏它们)。 我们还需要一个私有的init
方法,以便在控制器加载后立即获取所有活动的书。 因此,我们需要三个WeakMap。 让我们将WeakMaps声明为常量,以防止意外重新分配。
以下代码段创建了这些WeakMaps和类HomeController
:
const INIT = new WeakMap();
const SERVICE = new WeakMap();
const TIMEOUT = new WeakMap();
class HomeController{
constructor($timeout, bookShelfSvc){
SERVICE.set(this, bookShelfSvc);
TIMEOUT.set(this, $timeout);
INIT.set(this, () => {
SERVICE.get(this).getActiveBooks().then(books => {
this.books = books;
});
});
INIT.get(this)();
}
markBookAsRead(bookId, isBookRead){
return SERVICE.get(this).markBookRead(bookId, isBookRead)
.then(() => {
INIT.get(this)();
this.readSuccess = true;
this.readSuccessMessage = isBookRead ? "Book marked as read." : "Book marked as unread.";
TIMEOUT.get(this)(() => {
this.readSuccess = false;
}, 2500);
});
}
addToArchive(bookId){
return SERVICE.get(this).addToArchive(bookId)
.then(() => {
INIT.get(this)();
this.archiveSuccess = true;
TIMEOUT.get(this)(() => {
this.archiveSuccess = false;
}, 2500);
});
}
}
上面的代码片段使用了以下ES6功能:
- 如前所述,类和WeakMaps
- 箭头函数语法,用于注册回调。 在
this
箭头功能内部参考是相同this
参考外部,这是类的当前实例 - 用于创建方法并将其附加到对象而不使用
function
关键字的新语法
让我们应用依赖注入并将此类注册为控制器:
HomeController.$inject = ['$timeout', 'bookShelfSvc'];
export default HomeController;
如您所见,应用依赖项注入的方式没有什么不同–与我们在ES5中所做的方式相同。 我们正在从该模块中导出HomeController
类。
检查AddBookController
和ArchiveController
的代码。 它们遵循类似的结构。 文件bookShelf.controllers.js
导入这些控制器并将其注册到模块。 这是此文件中的代码:
import HomeController from './HomeController';
import AddBookController from './AddBookController';
import ArchiveController from './ArchiveController';
var moduleName='bookShelf.controllers';
angular.module(moduleName, [])
.controller('bookShelf.homeController', HomeController)
.controller('bookShelf.addBookController', AddBookController)
.controller('bookShelf.archiveController', ArchiveController);
export default moduleName;
bookShelf.controllers
模块导出其创建的AngularJS模块的名称,以便可以将其导入另一个模块以创建以创建主模块。
定义服务
“服务”在Angular和Angular中都是重载的术语! 所使用的三种服务类型是: 提供者 , 服务和工厂 。 其中,提供程序和服务被创建为类型的实例,因此我们可以为其创建类。 工厂是返回对象的函数。 我可以想到两种创建工厂的方法:
- 与ES5中一样,创建一个返回对象的函数
- 具有静态方法的类,该方法返回相同类的实例。 此类将包含必须从工厂对象公开的字段
让我们使用第二种方法来定义工厂。 该工厂负责与Express API进行交互并将数据提供给控制器。 工厂依靠Angular的$http
服务来执行Ajax操作。 由于它必须是类中的私有字段,因此我们将为其定义WeakMap。
以下代码段创建了工厂类,并将静态方法注册为工厂:
var moduleName='bookShelf.services';
const HTTP = new WeakMap();
class BookShelfService
{
constructor($http)
{
HTTP.set(this, $http);
}
getActiveBooks(){
return HTTP.get(this).get('/api/activeBooks').then(result => result.data );
}
getArchivedBooks(){
return HTTP.get(this).get('/api/archivedBooks').then(result => result.data );
}
markBookRead(bookId, isBookRead){
return HTTP.get(this).put(`/api/markRead/${bookId}`, {bookId: bookId, read: isBookRead});
}
addToArchive(bookId){
return HTTP.get(this).put(`/api/addToArchive/${bookId}`,{});
}
checkIfBookExists(title){
return HTTP.get(this).get(`/api/bookExists/${title}`).then(result => result.data );
}
addBook(book){
return HTTP.get(this).post('/api/books', book);
}
static bookShelfFactory($http){
return new BookShelfService($http);
}
}
BookShelfService.bookShelfFactory.$inject = ['$http'];
angular.module(moduleName, [])
.factory('bookShelfSvc', BookShelfService.bookShelfFactory);
export default moduleName;
该代码段使用ES6的以下附加功能(除了类和箭头功能之外):
- 类中的静态成员
- 字符串模板,将变量的值连接成字符串
定义指令
定义指令类似于定义工厂,但有一个例外-我们必须使该指令的实例可供link
函数内部使用,因为在指令对象的上下文中不会调用link
函数。 这意味着link
函数内部的this
引用与指令对象不同。 我们可以通过静态字段使对象可用。
我们将创建一个属性指令,以验证在文本框中输入的书名。 它必须调用API来检查标题是否已经存在,并且如果找到标题则使该字段无效。 对于此任务,它需要我们在上一节中创建的服务以及$q
的promise。
以下代码段创建了一个指令,并在模块中注册了该指令。
var moduleName='bookShelf.directives';
const Q = new WeakMap();
const SERVICE = new WeakMap();
class UniqueBookTitle
{
constructor($q, bookShelfSvc){
this.require='ngModel'; //Properties of DDO have to be attached to the instance through this reference
this.restrict='A';
Q.set(this, $q);
SERVICE.set(this, bookShelfSvc);
}
link(scope, elem, attrs, ngModelController){
ngModelController.$asyncValidators.uniqueBookTitle = function(value){
return Q.get(UniqueBookTitle.instance)((resolve, reject) => {
SERVICE.get(UniqueBookTitle.instance).checkIfBookExists(value).then( result => {
if(result){
reject();
}
else{
resolve();
}
});
});
};
}
static directiveFactory($q, bookShelfSvc){
UniqueBookTitle.instance =new UniqueBookTitle($q, bookShelfSvc);
return UniqueBookTitle.instance;
}
}
UniqueBookTitle.directiveFactory.$inject = ['$q', 'bookShelfSvc'];
angular.module(moduleName, [])
.directive('uniqueBookTitle', UniqueBookTitle.directiveFactory);
export default moduleName;
在这里,我们可以使用ES6的promise API,但这将涉及在promise产生结果之后调用$rootScope.$apply
。 好消息是AngularJS 1.3中的promise API支持类似于ES6 promise的语法 。
定义主模块和配置块
现在我们有了包含指令,控制器和服务的模块,让我们将它们加载到一个文件中并创建应用程序的主模块。 让我们从导入模块开始。
import { default as controllersModuleName } from './bookShelf.controllers';
import { default as servicesModuleName } from './bookShelf.services';
import { default as directivesModuleName } from './bookShelf.directives';
config块定义了应用程序的路由。 这可以是一个简单的函数,因为它不必返回任何值。
function config($routeProvider){
$routeProvider
.when('/',{
templateUrl:'templates/home.html',
controller:'bookShelf.homeController',
controllerAs:'vm'
})
.when('/addBook',{
templateUrl:'templates/addBook.html',
controller:'bookShelf.addBookController',
controllerAs:'vm'
})
.when('/archive', {
templateUrl:'templates/archive.html',
controller:'bookShelf.archiveController',
controllerAs:'vm'
})
.otherwise({redirectTo:'/'});
}
config.$inject = ['$routeProvider'];
最后,让我们定义主模块并导出其名称。 如果您还记得的话,此名称在bootstrap.js
文件中用于手动引导。
var moduleName = 'bookShelf';
var app = angular.module(moduleName, ['ngRoute','ngMessages', servicesModuleName, controllersModuleName, directivesModuleName])
.config(config);
export default moduleName;
结论
希望这可以使您深入了解如何使用ES6编写AngularJS应用。 AngularJS 2.0完全使用ES6编写,作为Web开发人员,我们需要意识到在不久的将来必须编写代码的方式。 ES6解决了许多困扰JavaScript程序员多年的问题,将其与AngularJS一起使用非常有趣!
并且请记住,可以在我们的GitHub存储库中找到此应用程序的示例代码。
From: https://www.sitepoint.com/writing-angularjs-apps-using-es6/