作者:[美]Chris Strom
出版社:人民邮电出版社
格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT
Dart语言程序设计试读:
前言
为什么使用Dart
当我问这个问题时,我并不想知道Google为什么正在致力于Dart,我也不是想问这门语言的设计者希望实现什么。当然,在这本书中我们将谈及这些问题和更多的内容。
我也常常问自己:“为什么使用Dart?”到底是什么让我认为这是一门值得学习的好语言,甚至愿意单独为这门语言写一整本书?特①别是当前才发布了0.08版 。【注】① 翻译此书时为0.10版。—译者注
这个问题的答案与我的个人及职业发展轨迹有关——理解如何使互联网更快。回到那段日子,我还是一名简简单单的Perl程序员。我很喜欢Perl这门语言,并用它做些我想做的东西。但是,当接触了Ruby和Ruby on Rails之后,我震惊了。简单、清晰的代码和强约定的组合赢得了我的青睐,而且持续了相当长的一段时间。②
之后,我尝试了小型框架,如Sinatra ,它保留了Ruby语言的美,但是却带来更小、更快的代码。这两种框架都满足了Web开发的连续性,我当时也很满意。【注】② http://sinatrarb.com,一种Ruby语言实现的用于快速创建Web应用的DSL。—译者注
但是还能有更多的选择吗?这最终把我带到了 Node.js 以及在其上构建的各种JavaScript框架,而且看起来很难再对服务器端进行改进。③
然后,我发现了SPDY 协议,它令我着迷,以至于写了The ④SPDY Book 这本书。这不仅是改进现有的东西,而且是尝试重新定义游戏规则。【注】③ http://www.chromium.org/spdy。【注】④ http://spdybook.com/。
在我接触SPDY的时候,我注意到了一件事,无论我利用多少这种协议所提供的优势,Web应用的最终速度还是受限于处理网页和客户端脚本、CSS等的速度。
JavaScript已经有 17年的历史了。在它首次被引入时,还没有Web 2.0、Ajax、CSS,而且根本没有多少客户端交互。当JavaScript首次出现时,主要的使用场景就是验证表单并用警告框提示!
在接下来的17年里,JavaScript语言已经从网景公司拥有并缓慢开发的一种专有语言,演化为一个定期添加新特性的Web标准。但是与委员会添加新特性到标准中相比,Web演化的速度明显更快。
然后Dart语言到来了。Dart问:考虑到我们现今所知的Web,我们如何从头开始构建 JavaScript?怎样才能尽可能快地加载和运行?如何编写才能使我们很容易地定义和加载外部库?
我们怎样才能使开发者轻松地写出漂亮的代码?
如果Dart语言是这许多问题的答案(并且我将实际尝试它们),那么Dart很可能是很长一段时间内最令人兴奋的技术。
这就是对“为什么使用Dart?”的回答。
谁应该阅读本书(除了技术达人)
这本书主要是为那些想让自己的 JavaScript 技能保持最新的开发者们写的。提高JavaScript技能的最好途径就是亲身实践和阅读别人的代码。但是,有时看看竞争者正在忙什么也会有根本上的帮助。既然这样,随着我们探索Dart语言带来了什么,我们能够更好地理解这种优秀语言的与众不同之处。
我也希望这本书会证明这种新的转变是有用的。对于当今的浏览器,Dart语言已经是一个可用于构建快速Web应用的有价值的平台。我希望你在读完这本书后,能够很好地武装起来去开发下一代Web应用。
这本书的目标读者应该是喜欢学习各种编程语言的开发者。我关注了Dart语言的很多方面,特别是那些令我兴奋和高兴的地方。
当然,技术达人也应该读这本书。Dart语言相当与众不同,并且足够强大,这使它正吸引着经典的语言技术达人,值得那些想改变世界的人们花时间阅读。
本书的组织
我想做出一些不同的尝试。我没有每章介绍语言的一部分。每个部分都是从一个实际的Dart项目开始的,包括一些特意选择的说明。在这些部分中,我的目标是通过给出这种语言的真实感觉,展现出Dart语言所声称的重要改进。因为这些都是真实的项目,所以很适合指出Dart语言的强项,当然也包括一些弱项。
每一个这样的项目章的后面都会跟着几个更深入语言某个方面的特定主题的章。我使用这些章来涵盖项目章需要更详细解释的地方,以及不能在当前Dart语言参考资料中找到的东西。
所以,如果你需要快速的语言介绍,你完全可以单独地从项目章节开始。如果你想要更传统方式的书,那么跳过项目章而只阅读主题章,或者全部阅读—我保证它值得你花时间!
第一个项目在第1章。对于这个项目的补充在第2章、第3章、第4章和第5章。
下一个项目在第9章,这个项目源于第1章的这个简单Ajax应用程序,并把它提炼为一个羽翼丰满的 MVC 库。如果你想要领略一门语言,编写一个程序库,特别是MVC库,是最好的方式。与MVC库相关的还有第7章、第10章和第8章。
紧接着,我们将在第 11 章看一下 Dart 语言中的依赖注入。与 JavaScript 不同, Dart语言并不是主要作为一种动态语言,尽管如在第11章所看到的那样,它仍然可能实现一些传统动态语言的技巧。在这个项目之后是关于Dart语言测试的介绍,这是一个重要的主题,尽管不是完全源自Dart。
最后一个项目在第13章,在这里我们探索作为传统传递回调函数的高层次的替代—Dart“Future”。这引起了第14章对代码隔离和消息传递的讨论。
最后,我们以各种HTML5技术的简短探索来结束本书。
本书不包含的内容
本书不涵盖Dart Editor的内容。在某种程度上看,算是一点儿缺失。Dart这样的强类型语言具有代码自动完成的能力,Dart Editor便可提供这种功能。不过,本书的重点是语言本身,而不是其相关的工具。此外,一些人(包括我自己)会坚持用他们自己选择的代码编辑器。
这本书的目的不是作为语言参考手册。为这种发展中的语言做参考手册还太早了。不过,希望本书能成为(对开发者不够友好的)语①②言规格说明书 和(有些地方尚不完整的)API文档 的一个有力补充。【注】① Dart语言规范在http://www.dartlang.org/docs/spec/。它主要是给语言实现者看的,但必要时也对应用开发者有用。【注】② http://api.dartlang.org。
关于未来
因为Dart语言还会继续发展,根据Dart语言的变化速度,本书的内容可能一年有一次或两次的评审,然后做相应的修订、删除或补充。如果您发现任何错误或需要改进的地方,请提交到公共问题跟踪上:https://github.com/dart4hipsters/dart4hipsters.github. com/issues。关于新主题方面的建议也非常欢迎。
本书的约定
类名按骆驼式命名(camel-cased),例如HipsterModel。类对应的文件名和类名一样,例如HipsterModel.dart。变量名按蛇式命名③(snake-cased ),例如background_color,而函数和方法名都是小写字母开头的骆驼式命名,例如routeToRegExp()。【注】③ 一种以下划线连结单词的命名方式。—译者注
让我们开始吧
正如前文所说的,让我们开始编写没有历史包袱的Web代码吧。让我们编写Dart代码!第一部分 入门
Dart 有很多特点,但也许最令人印象深刻的是,它让熟悉 JavaScript 的程序员感到非常熟悉。在前面的几章中,我们从头开始,编写一个Dart应用程序。第1章 项目:第一个Dart应用程序
大部分编程书籍都以一个“Hello World!”示例开始。我要说,换种方式——我们这里都是编程达人。让我们开始写代码吧!
首先因为Dart语言写起来感觉比较熟悉,所以我们在深入之前不应该走得太远。让我们直接跳到更有趣的事情上:一个带Ajax的网站。任何一个真正的达人都会收集大量的漫画书(我说的对吗?不是就我一个人这样吧,哈),那么让我们来考虑一个简单的Dart应用程序,通过REST风格的接口来操作一组漫画书。
在某种程度上,这可能有点儿太激进了。别担心,我们会在后续章节中深入细节。1.1 后端部分
本章的示例代码可以在https://github.com/eee-c/dart-comics的“your_first_dart_app”分支上找到。作为技术爱好者,我们已经在用Node.js了,所以后端需要Node.js和npm。说明包含在项目的README中。
作为REST风格,这个应用程序应该支持下面这些操作:
• GET /comics(返回漫画书的列表);
• GET /comics/42(返回一本漫画书);
• PUT /comics/42(更新一本漫画书);
• POST /comics(在集合中新建一本漫画书);
• DELETE /comics/42(删除一本漫画书)。
我们不用太关心除此之外的后端部分的细节。1.2 Dart的HTML部分
我们的整个应用程序将遵循最近的客户端 MVC 框架的惯例。因此,我们只需一个Web页面。
your_first_dart_app/index.html
navigator.webkitStartDart();
Dart Comics
Welcome to Dart Comics
Add a sweet comic to the collection.
对于大家而言,这个Web页面的大部分应该很熟悉。它包含了简单的HTML、CSS的链接和脚本。
1.HTML头部
唯一要注意的有点儿奇怪的地方是第一个
要点:在写这本书时,在Dartium上必须用navigator.webkitStartDart()来启用Dart VM,Dartium是包含Dart语言支①持的Chrome版本。这个要求可能会在不久的将来去除。【注】① http://www.dartlang.org/dartium/。
接下来,我们加载了实际代码的内容。这里唯一的变化是
第10章还有更多关于Dart加载库和包含代码的内容。现在只要知道它会按照我们期望的方式加载Dart即可。
2.HTML主体
作为HTML的主体部分,没什么新东西,不过我们应该注意一下将附加上行为的两个元素的ID。
your_first_dart_app/_index_body.html
Dart Comics
Welcome to Dart Comics
Add a sweet comic to the collection.
对#comics-list UL 元素,我们将附加上后端数据存储中的漫画书列表。#add-comic段落标签上将附加上一个表单处理程序。那么,我们就开始吧。1.3 Dart的Ajax部分
在scripts/comics.dart中,我们的Dart应用程序以两个Dart库的加载和一个main()函数开始。
your_first_dart_app/comics_initial_main.dart
import 'dart:html';
import 'dart:json';
main() {
load_comics();
}
load_comics() {
// 在这里具体实现
}
正如我们将在第10章看到的,import语句有很多功能。现在我们只要简单地认为它导入了Dart核心行为之外的其他功能即可。
所有的Dart应用程序都是以main()作为执行的入口点。在JavaScript中,我们只要直接写代码然后就可以运行,但在Dart中这样不行。起初这可能看起来类似C系列的语言,但这确实是有意义的,难道要让遍布于各处的 JavaScript 源文件和 HTML文件中的所有代码①都立刻执行?在 Dart 语言中,main()入口点不只是约定,它是由这一语言强制的最佳实践。【注】① 在一个页面中,出现的所有独立js文件或嵌入在HTML文件中的JavaScript都会立即被解析执行。但我们往往需要一个开始执行的入口点,一般是在文档准备好时执行一个函数,如jQuery的$(document).ready(handler)。—译者注
至于load_comics()函数,我们一步步来。首先我们需要标识出列表需要附加的DOM元素(#comics-list),接着我们需要一个Ajax调用来填充这个DOM元素。为了实现这两件事,我们最初的Dart代码看上去大概像下面这样:
your_first_dart_app/_load_comics.dart
load_comics() {
var list_el = document.query('#comics-list');
ajax_populate_list(list_el);
}
除了明显地忽略了function关键字之外,这个例子就像是JavaScript代码!在第3章,我们将介绍更多差异。Dart语言仍然用我们熟悉和喜欢的分号和大括号——语言设计者有意让人对这门语言感到很熟悉,至少在表面上看起来很熟悉。
注意:和JavaScript不同,在Dart中分号不是可选的。
除了熟悉,这段代码一看就很容易阅读和理解。没有怪异的、遗留的DOM方法。我们用document.query()而不是用document.findByElementId()来查找元素,并且用#comics-list这种CSS选择器的方式,正如我们在jQuery中已经习惯的那样。
现在已经找到了需要填充的UL元素,让我们来看看如何产生一个Ajax请求。就像在JavaScript中一样,我们要创建一个新的XHR对①象,并添加了一个请求加载完成的处理程序,然后打开并发送这个请求。【注】① 为了减少名称中的冗余信息,在新版的Dart中,过去的XMLHttpRequest已改为HttpRequest,相关的名称也是如此,如XMLHttpRequestException已改为HttpRequestException。—译者注
your_first_dart_app/_ajax_populate_list.dart
ajax_populate_list(container) {
var req = new HttpRequest();
req.on.load.add((event) {
var list = JSON.parse(req.responseText);
container.innerHTML = graphicNovelsTemplate(list);
});
// 动作, 资源, 异步模式
req.open('get', '/comics', true);
req.send();
}
对于过去做过Ajax编程的人,对大部分代码应该立刻就能看懂。我们打开创建的XHR对象,指定要获取的资源,然后实际发送请求。
当添加事件处理程序时,我们看到与JavaScript的使用方式有所不同。XHR对象有一个 on 属性,它列出了所有支持的事件处理程序。我们访问其中的一个 load 处理程序类型,这样我们就能用add()方法添加一个处理程序。在这种情况下,我们解析获得的JSON数据为一个散列值的列表。它大概就像这样:
your_first_dart_app/comics.json
[
{"title":"Watchmen",
"author":"Alan Moore",
"id":1},
{"title":"V for Vendetta",
"author":"Alan Moore",
"id":2},
{"title":"Sandman",
"author":"Neil Gaiman",
"id":3}
]
然后,我们要实现这个简单的Dart应用程序的最后一部分——一个填充漫画书列表的模板。
your_first_dart_app/_graphic_novels_template.dart
graphic_novels_template(list) {
var html = new StringBuffer();
list.forEach((graphic_novel) {
html.add(_graphic_novel_template(graphic_novel));
});
return html.toString();
}
_graphic_novel_template(graphic_novel) {
return """
${graphic_novel['title']}
by
${graphic_novel['author']}
""";
}
第一个函数简单迭代这个漫画书列表(在心里,我把它们当做漫①画小说),构建为一个HMTL字符串。【注】① Dart语言现在已不支持字符串拼接(+)操作,所以需要改用 StringBuffer。简单(非循环)的字符串拼接,如str1+str2,可以用字符串内的变量插值实现,如“$a $b”。或者使用相邻字符串的方式,在Dart语言中相邻的字符串会自动拼接,包括多行情况。—译者注
第二个函数展示了两个Dart语言特性:多行字符串和字符串变量插值。多行字符串由3个引号表示(单引号或双引号)。在字符串中,我们可以用美元符号插入值(甚至一个简单表达式)。对于简单的变量插入,大括号是可以省略的:$name和${name}是一样的。而对更复杂的插入,如散列查找,大括号就是必需的。
就是这样!我们准备好了一个功能完备的、带Ajax的Web应用程序。汇总的代码如下:
your_first_dart_app/comics.dart
import 'dart:html';
import 'dart:json';
main() {
load_comics();
}
load_comics() {
var list_el = document.query('#comics-list');
ajax_populate_list(list_el);
}
ajax_populate_list(container) {
var req = new HttpRequest();
req.on.load.add((event) {
var list = JSON.parse(req.responseText);
container.innerHTML = graphicNovelsTemplate(list);
});
// 动作, 资源, 异步模式
req.open('get', '/comics', true);
req.send();
}
graphic_novels_template(list) {
var html = new StringBuffer();
list.forEach((graphic_novel) {
html.add(_graphic_novel_template(graphic_novel));
});
return html.toString();
}
_graphic_novel_template(graphic_novel) {
return """
${graphic_novel['title']}
by
${graphic_novel['author']}
""";
}
页面加载后看上去像这样:
这便是我们探索Dart语言的良好开端。的确,我们这里掩饰了许多成就Dart语言的地方。但是这样做,让我们有了一个使用Ajax的Web应用程序的良好开始。最棒的是,我们写的代码看起来似乎与 JavaScript 没有什么不同。一些语法比我们使用JavaScript 更整洁一点(没人会抱怨整洁的代码),并且这些字符串特性也很不错。但是总的来说,可以肯定的是,使用相对更短的Dart语言是有生产力的。1.4 这个应用程序还无法运行
在写作本书时,这个应用还不能在(几乎)任何地方实际运行。
任何浏览器(甚至Chrome)都还不支持Dart语言。为了原生地运行这个Web应用程序,我们需要安装Dartium——一个嵌入Dart VM①的Chrome分支版本。Dartium可以从Dart语言网站获得 。【注】① http://www.dartlang.org/dartium/。
即使在Chrome支持Dart语言之后,我们仍然面临着只能被市场上个别浏览器支持的情况。这有点儿糟糕。
好在Dart能够编译为JavaScript,这意味着你可以在利用Dart能力的同时对所有平台可用。为了更容易实现这一点,我们添加一个小的 JavaScript 库,用来探测浏览器是否支持Dart语言,如果不支持就加载编译成JavaScript的等价物。
your_first_dart_app/_index_src_js_fallback.html
第5章中将讨论这个帮助文件的细节。目前,只要知道我们的Dart代码不是锁定在一个浏览器厂商那里就足够了。我们肯定不会回到VBScript那样。1.5 下一步做什么
不可否认,这是对Dart语言的快速入门。能够如此快速地搭建和运行真是太棒了。仿佛我们此刻就获得了生产力。
尽管如此,我们才刚刚开始学习Dart语言,别搞错了,我们的Dart代码还可以改进。因此,让我们用下面的几章来熟悉Dart语言中的一些重要概念。之后,在第6章中,我们将准备好把这个Dart应用转变成MVC的方式。第2章 基本类型
本书中一个反复出现的主题是,Dart语言就是要让人觉得很熟悉。如果做到了这一点,那么对这一语言的基本组成部分的论述应该是相对简短的,并且确实是这样。尽管如此,对一些核心类型的介绍还是有帮助的。自然,其中也会有几处“陷阱”。2.1 数字类型①
整数和小数都是数字类型,这意味着它们支持很多相同的方法和运算符。Dart语言中的数字类型与许多其他语言中的数字类型非常像。【注】① 在Dart中,int和double都继承自num。—译者注
2 + 2; // 4
2.2 + 2; // 4.2
2 + 2.2; // 4.2
2.2 + 2.2; // 4.4
可以看出,在二者混合运算时Dart语言的数字类型做了“正确的事”。2.2 字符串类型
字符串是不可变的,换种直观的说法就是,字符串的操作会产生新的字符串而不是修改现有的字符串。字符串(像数字一样)是可散①列的,这意味着唯一的对象有唯一的一个散列值来区分彼此。如果我们将一个字符串变量赋值给另一个变量,它们将有一样的散列值,因为它们是同一个对象。【注】① 严格地说,散列值并不要求一定唯一,但应该很好地分布。如果两个对象相等(equals),那么二者的散列值也应该相等,但反之不一定成立。也就是说,不同内容的字符串的散列值也有可能相同,只是概率很小。—译者注
var str1 = "foo",
str2 = str1;
str1.hashCode; // 425588957
str2.hashCode; // 425588957
但是,如果我们修改了第一个字符串变量,那么它将获得一个全新的对象,而另一个字符串对象还是指向原来的字符串。
str1 = "bar";
str1.hashCode; // 617287945
str2.hashCode; // 425588957
Dart语言使字符串的处理更容易。例如,可以用三重引号括起来的方式新建多行字符串。
"""Line #1
Line #2
Line #3""";
Dart语言也支持相邻字符串拼接。
'foo' ' ' 'bar'; // 'foo bar'
这种相邻字符串的便利用法甚至扩展到多行字符串上。
'foo'
' '
'bar'; // 'foo bar'
最后一个Dart字符串的便利用法是字符串内的变量插值。Dart语言使用$表示要插值的变量。
var name = "Bob";
"Howdy, $name"; // "Howdy, Bob"
如果在变量表达式结束和字符串开始的地方存在混淆,可以将大括号和$一起使用。
var comic_book = new ComicBook("Sandman");
"The very excellent ${comic_book.title}!";
// "The very excellent Sandman"2.3 布尔类型
在Dart语言中,布尔类型(bool)的值只允许是true和false。在Dart语言中“真值”和它自身的概念一样简单:如果不是true,那就是false。考虑下面的代码:
var name, greeting;
greeting = name ? "Howdy $name" : "Howdy";
// "Howdy"
/*** Name仍然不是true ***/
name = "Bob";
greeting = name ? "Howdy $name" : "Howdy";
// "Howdy"
greeting = (name != null) ? "Howdy $name" : "Howdy";
// "Howdy Bob"
如果你用过许多其他语言,那么不会奇怪在布尔上下文中 null、""和 0 的值为false。你可能也习惯于"Bob"和42的值为false。“真值”的语义运行在“类型检查”模式下会稍有不同(见 2.7 节),但是最好不要依靠这种差异。如果我们总是用最后那种写法,那么我们就肯定不会犯错了。
在第 7 章中,我们将探讨操作符定义,这允许我们自定义特定类的 equals/==方法的含义。这给Dart语言关于布尔类型带来了一定的灵活性。2.4 HashMap(也称为Hash或关联数组)
在Dart语言中,键值对的数据结构是由HashMap对象实现的。下面的代码定义了一个叫options的HashMap变量:
var options = {
'color': 'red',
'number': 2
};
如你所料,用方括号从HashMap中获取值:
var options = {
'color': 'red',
'number': 2
};
options['number']; // 2①
HashMap实现了Map接口,它的大多数API都定义在这里。这包括关于获取键(keys)、获取值(values)以及用forEach()迭代整个对象的信息。【注】① http://api.dartlang.org/dart_core/Map.html。
var options = {
'color': 'red',
'number': 2
};
options.forEach((k, v) {
print("$k: $v");
});
// number: 2
// color: red
注意:键值对的顺序没有保证。
HashMap的一个非常有用的特性是putIfAbsent()方法。下面两段代码是等价的:
// 普通的实现
if (!options.containsKey('age')) {
var dob = new Date.fromString('2000-01-01'),
now = new Date.now();
options['age'] = now.year - dob.year;
}
// 更好的实现
options.putIfAbsent('age', findAge);
findAge() {
var dob = new Date.fromString('2000-01-01'),
now = new Date.now();
return now.year - dob.year;
}
在第一个例子中,条件语句和代码块都与 options 对象的实现细节相关。但在第二个例子中,findAge()函数只与计算当前年龄相关,而options对象则只关心添加值。
要点:应该总是考虑使用putIfAbsent()方法,这样Dart程序会更清晰。
第一个例子也可以改成下面这样:
if (!options.containsKey('age')) {
options['age'] = findAge();
}
但是,不用putIfAbsent()方法的话,似乎只能把findAge的实现放到条件语句中。无论怎样,条件语句的格式永远不会像等价的 putIfAbsent()方法这样清晰。
options.putIfAbsent('age', findAge);
putIfAbsent()——学习它,喜欢它。它会节省你的生命(当然,可能不会,但至少会让生活更轻松)。2.5 列表(也称为数组)
任何语言都需要表示一个事物的列表。为使开发者容易掌握,Dart语言的列表基本符合你所期望的那样。
var muppets = ['Count', 'Bert', 'Snuffleupagus'];
var primes = [1, 2, 3, 5, 7, 11];
// 索引从0开始
muppets[0]; // 'Count'
primes.length; // 6
Dart语言确实提供了一些不错的并且一致的方法来操作列表。
var muppets = ['Count', 'Bert', 'Ernie', 'Snuffleupagus'];
muppets.setRange(1, 2, ['Kermit', 'Oscar']);
// muppets => ['Count', 'Kermit', 'Oscar', 'Snuffleupagus']
muppets.removeRange(1, 2);
// muppets => ['Count', 'Snuffleupagus'];
muppets.addAll(['Elmo', 'Cookie Monster']);
// muppets => ['Count', 'Snuffleupagus', 'Elmo', 'Cookie Monster']
同时也有一些内建的迭代方法。
var muppets = ['Count', 'Bert', 'Ernie', 'Snuffleupagus'];
muppets.forEach((muppet) {
print("$muppet is a muppet.");
});
// =>
// Count is a muppet.
// Bert is a muppet.
// Ernie is a muppet.
// Snuffleupagus is a muppet.
muppets.some((muppet) => muppet.startsWith('C'));// true
muppets.every((muppet) => muppet.startsWith('C'));// false
muppets.filter((muppet) => muppet.startsWith('C'));// ['Count']
要点:写作此书时,Dart语言当前缺少reduce()和fold()这样的能①够执行高阶操作的列表方法。【注】① 当前List已支持map和reduce方法。——译者注
太好了,对于Dart语言的列表和数组没有太多需要介绍的了。它是Dart语言中众多“刚好够用”的事物之一。
集合(Collection)
上一节中使用的迭代方法其实不是定义在List类上的。而是来自List的超类:Collection。Collection家族的另两个成员是Set和Queue。
Set是内部元素唯一的集合,并且拥有一些数学集合的操作方法。
var sesame = new Set.from(['Kermit', 'Bert', 'Ernie']);
var muppets = new Set.from(['Piggy', 'Kermit']);
// 因为Ernie已经在集合中了,所以不会插入
sesame.add('Ernie'); // => ['Kermit', 'Bert', 'Ernie']
sesame.intersection(muppets); // => ['Kermit']
sesame.isSubsetOf(muppets); // => false
Queue是一种可以在首尾两端操作的集合。
var muppets = new Queue.from(['Piggy', 'Rolf']);
muppets.addFirst('Kermit');
// muppets => ['Kermit', 'Piggy', 'Rolf']
muppets.removeFirst();
muppets.removeLast();
// muppets => ['Piggy']
根据现在的Queue推论,常规的列表不能操作开头的元素。也就①是说,List没有shift或unshift的方法。【注】① 除了支持的方法不同外,当前文档中没有清楚地说明 List 和 Queue 的区别。但是,目前可以认为 Queue默认是由双向链接的列表实现的,所以首尾操作的效率较高;而 List 默认是动态增长的数组,因此二者对于不同类型的操作性能表现是不同的。—译者注2.6 日期类型
在浏览器上Dart语言对日期和时间带来了一些非常期盼的改进。关于日期方面有一个令许多JavaScript开发者感到痛苦的问题,而在②Dart语言中第一个月是从1开始的。让我们开始庆祝吧。【注】② 在JavaScript和Java等语言中,第一个月是从0开始的,12个月分别是0~11。这种设定不直观,也容易犯错。—译者注
Dart语言中日期的改进还不止这些。例如,有多种创建日期对象的方法。③
var mar = new Date.fromString('2012-03-01 14:31:12');【注】③目前Dart API文档没有准确说明支持哪些字符串格式用于新建Date。—译者注
// 2012-03-01 14:31:12.000
var now = new Date.now();
// 2012-03-10 01:02:24.149
var apr = new Date(2012, 4, 1, 0, 0, 0, 0);
// 2012-04-01 00:00:00.000
更好的是,不仅可以操作日期,而且相当好用。
var mar = new Date(2012, 3, 1, 0, 0, 0, 0);
var apr = new Date(2012, 4, 1, 0, 0, 0, 0);
var diff = apr.difference(mar);
diff.inDays; // => 31①
apr.add(new Duration(days: 15)); // => 2012-04-16【注】① Date的add()方法返回一个新的Date对象,当前对象并不改变。—译者注
Date 中的 difference()方法返回一个封装了一段时间的 Duration 对象。Duration对象支持从“天”到“毫秒”的各种时间单位的查询。正如在add()方法示例中所见,Duration对象也很适合从一个特定的日期开始增加或减少时间。
在Dart语言中使用日期并不是一件可怕的事。正如我们所见,这很轻松。2.7 类型②
要点:强烈建议在类型检查模式下完成 Dart 代码的编写。默③认情况下,Dart 运行在“生产”模式下,在生产模式下当出现表面上的类型定义冲突时程序不会崩溃。这样就只能依靠开发者自己在开④发过程中捕获尽快多的此类问题。要启用类型检查模式,在命令行中用DART_FLAGS='--enable_type_checks --enable_asserts'/path/to/dartium参数启动Dartium。【注】② Dart语言有两种运行模式,即检查模式(checked mode)和生产模式(production mode),默认运行在生产模式下。本书原文采用的是“type-checked mode”表示检查模式,这是Dart语言早期的说法,现在一般采用“checked mode”的说法,这里还按照原文的“类型检查模式”翻译。—译者注【注】③ Dart VM和Dartium本身默认都是在生产模式下运行的,而在Dart Editor里,所有(命令行和Web)启动类型默认都是在检查模式下运行的,可以在启动选项中设置。—译者注【注】④ 官方建议在开发环境中使用检查模式运行,在生产环境下使用生产模式运行。检查模式可以帮你尽早发现问题,但会影响程序性能,所以正式的环境应该用生产模式运行。—译者注
在转移到其他主题之前,让我们快速了解一下Dart语言中的变量声明。到目前为止,我们都是按照JavaScript中的惯例用var关键字来声明变量的。在Dart语言中,var表示可变类型。换句话说,我们不仅没有指定类型,而且还告诉解释器这个类型可能会改变。
var muppet = 'Piggy';
// Dart类似JavaScript,允许这样做,但是更进一步!
muppet = 1;①
Dart语言一定程度上是强类型语言,这意味着它更喜欢我们用实际的类型声明而不是var。【注】① Dart是动态类型语言,也是强类型语言,类似于Ruby。—译者注
String muppet = 'Piggy';
// 在类型检查模式下会失败
// 其他地方会抱怨(在Dart Editor中会给出警告)
muppet = 1;
Dart语言也允许我们做一些傻事,就像前面那段代码中那样,但是它会给出警告。在命令行中可以启用类型检查模式,在类型检查模式下,前面那段代码会抛出异常。
尽管var关键字是可接受的,但是声明类型通常被认为是个好习惯。
int i = 0;
bool is_done = false;
String muppet = 'Piggy';
Date now = new Date.now();
对于包含其他类型的类型,也可以声明对象要持有的类型。
HashMap
'Scooter': false,
'Bert': true,
'Ernie': false
};
List
声明类型使代码的意图更明确也更容易阅读,这对可维护性是很重要的。在编译代码时,类型也将有助于解释器,允许它运行得更快。2.8 下一步做什么
本章中包含了很多方面的内容。此刻,Dart语言中的很多东西应该仍是感到熟悉的,也有一些关键但(我希望是)受欢迎的差异。第3章 Dart中的函数式编程
JavaScript 的一个特点是它支持函数式编程。因为 Dart 的目标是让人感觉熟悉,现在让我们看看在Dart语言中函数式编程是什么样的。
我们先从一个传统的例子开始,计算斐波纳契数列。在 JavaScript 中,大概像下面这样写:
function fib(i) {
if (i < 2) return i;
return fib(i-2) + fib(i-1);
}
探索一个语言的函数式编程特性,斐波纳契数列是个很好的例子。不仅因为它是一个函数,也因为它的递归性质可以展示函数的调用。①
我不想纠缠于描述递归或者这个函数的细节。相反,让我们关注如何在JavaScript中使用这个函数。【注】①斐波纳契数列在其他地方有很好的描述,如果你需要重温一下,请参考 htp:/en.wikipedia.org/wiki/Fibonacci_number。
fib(1) // => 1
fib(3) // => 2
fib(10) // => 55
看得出JavaScript函数足够简单。首先是function关键字,然后是函数名,跟着是圆括号中的参数列表,最后是描述函数体的代码块。
那么,等价的Dart语言版本是什么样呢?
// Dart
fib(i) {
if (i < 2) return i;
return fib(i-2) + fib(i-1);
}
等一下,这和JavaScript的版本有什么不同吗?
function fib(i) {
if (i < 2) return i;
return fib(i-2) + fib(i-1);
}
细心的读者会注意到,Dart版本缺少function关键字。除了这一点,两个函数是完全相同的,调用方式也一样。
fib(1); // => 1
fib(5); // => 5
fib(10); // => 55
如果没有其他区别,可以看出Dart语言的设计者确实让这门语言让人感觉很熟悉。3.1 匿名函数
有经验的JavaScript程序员非常精通于使用匿名函数。因为在JavaScript中函数是顶级概念,函数可以在 JavaScript 中任意传递。甚至某些框架成了回调函数的地域,但是撇开审美不说,没有人会否认匿名函数是 JavaScript 中的一个重要部分。那么,在Dart中也一定是这样的,对吗?
在JavaScript中,匿名函数省略了函数名,仅使用function关键字。
function(i) {
if (i < 2) return i;
return fib(i-2) + fib(i-1);
}
我们已经看到JavaScript和Dart的函数仅有的差异是后者没有function关键字。事实证明,这也是二者在匿名函数上仅有的差异。
(i) {
if (i < 2) return i;
return fib(i-2) + fib(i-1);
}
乍一看,这看起来很奇怪,感觉光秃秃的。但是,这仅仅是从 JavaScript 的角度来看。Ruby中有比较类似的lambda和Proc。
{ |i| $stderr.puts i }
认真地考虑一下,在JavaScript中function关键字真正起了什么作用?下意识的反应是,它有助于标识出匿名函数,但在实践中,这仅仅是一个干扰。
考虑下面这个显示斐波纳契数值的代码:
var list = [1, 5, 8, 10];
list.forEach(function(i) {fib_printer(i)});
function fib_printer(i) {
console.log("Fib(" + i + "): " + fib(i));
}
function fib(i) {
试读结束[说明:试读内容隐藏了图片]