Dart,献给 JavaScript 开发者的礼物🎁(一、常用语法)

949 阅读5分钟

你可能从没注意过我,但你一定听过我的兄弟 Flutter 和我们的父亲 Google。

起源

Dart 诞生于 2011 年,但是在 2017 年之前一直不温不火。最初 Dart 是在谷歌内部用于构建网络和移动应用程序。Dart 依附于 Flutter 的火爆而被大家所关注。

常用语法

Dart 对于前端同学来说上手还是很简单的,如果熟悉 TypeScript 的话,那转 Dart 简直不要太熟悉了,下面归纳整理了一些日常开发 Flutter 中常用的 Dart 语法。

var number = 1; /// dart 会自行推断 number 为 int 型,此时再给 number 赋值其他类型,例如字符串,则会报错 A value of type 'String' can't be assigned to a variable of type 'int'.
int number = 1;

var number = 1.0; /// dart 会自行推断 number 为 double 型;
double number = 1.0;

dynamic variable = 1; /// 可以使用 dynamic 来指定动态类型,但强烈建议不要这么写代码,你会失去你的朋友
variable = '2';

String string = 'string'; /// 声明字符串
String string = "string"; /// 也可以用双引号

var empty; /// dart 没有 undefined,只有 null,表示为空,即这里 empty == null 为 true
var empty = null; /// 和上面等价,可以但没必要这么写

null == null; /// dart 的 == 即等价于 JavaScript 的 ===,dart 没有 JavaScript 中的非严格比较(==)

double pi = 3.14;
String combineStr = '${pi} 是圆周率'; /// 字符串模板
String combineStr = '$pi 是圆周率'; /// 甚至可以省略 {},注:'$pixxx 是圆周率',这里就不能省略,因为 dart 会以为变量名是 pixxx

f() {} /// 声明一个函数
void f() {} /// 建议声明一个函数时同时提供返回类型,另函数没有 return 语句时默认该函数返回 null;
int f() => 1; /// 和 int f() { return 1; } 等价

void f(int a, b, c) {} /// 必填位置参数,参数前加类型用以表示参数类型(建议)
void f(a, b, [c]) {} /// c 为可选参数,可选参数必须在必填参数后面
void f(a, b, [c = 1]) {} /// c 如果不传,即为 1
void f(a, { b, c = 1 }) {} /// 被 {} 包裹的为命名参数,命名参数都是可选参数,使用时为 f(1, b: 2, c: 3),命名参数有点 JavaScript 里面拿 JSON 对象做参数的感觉,但是更简洁优雅

List<int> arr = [];
arr.add(1); /// 新增一项
arr.removeLast(); /// 删除最后一项
arr.where((e) => e > 0).toList(); /// where 等筛选的方法返回的是 Interable 对象,需用用 toList 方法转回 List
arr.map((e) => e * 2).toList(); /// 使用 map 转化数组
arr.firstWhere((e) => e > 0, orElse: () => null); /// 这里和 JavaScript 有点区别,dart 找不到需要的项时,默认是抛出 StateError,除非你提供了 orElse 方法,比如上面如果没有找到,则返回 null

/// dart 遍历数组时获取下标相对于 JavaScript 来说麻烦了一些
List<String> arr = ['apple', 'orange', 'peach'];
arr.asMap().entries.forEach((e) {
  /// 0 apple
  /// 1 orange
  /// 2 peach
  print('${e.key} ${e.value}');
});

/// Map 类型类似于 JavaScript 中常用的对象
Map map = { 'apple': '苹果', 'orange': '橘子' }
/// 获取某个 key 的值,注意,不能使用 map.apple 形式
map['apple']

/// 布尔值,值得注意的是 dart 中 if 等条件判断中判断条件必须是布尔值,不会像 JavaScript 一样进行隐式转换
true
false

上面介绍了日常开发中会经常用到的语法知识,下面再介绍一些 Dart 中特有的知识点,日常开发中也会用到。

final 和 const

在 JavaScript 中,声明常量我们会用到 const 语法,例如声明一个数组 const arr = [1, 2, 3],但是这样真的就能确保这个数组不可变了吗?众所周知,并不能。毕竟 JavaScript 中的常量只是表示这个变量不能被重新赋值,但是对于上面的代码,我们仍然可以执行 arr.push(4) 等改变这个数组的内容。

在 Dart 中不会有这个问题,尝试对 const 声明的变量进行修改时,会抛出错误(对于 final 声明的常量就可以和 JavaScript 一样正常操作):

Unsupported operation: Cannot modify an unmodifiable list

另外,在 Dart 中除了 const,还有一个关键词 final 也可以用来声明常量,二者的区别就是 const 是编译时常量,其值在编译时就能确定,而 final 是运行时常量,其值在运行时才能确定。换个说法就是,const 修饰的常量必须在声明时初始化,并且是可以确定的值,不是需要经过计算的值,而 final 修饰的常量必须在声明进初始化或者在构造函数中初始化,它的值可以动态计算。举个例子:

const c1 = "hello"; /// 正确,编译期常量
final f1 = "hello"; /// 正确,运行时常量

const c2 = DateTime.now(); /// 编译报错,值不是编译期常量
final f2 = DateTime.now();/// 正确,运行时常量

有趣的 .. 符

Dart 中有一个级联操作符 .. 很有意思,在日常开发中会使用到。假设我们要获取一个 html 元素,然后设置一些属性,再添加一个方法监听,一般情况下,我们可能需要这样来实例化 Person 并调用 say 方法。

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

在 Dart 中,我们可以用 .. 操作符这样来写:

querySelector('#confirm') /// 获得对象
  ..text = 'Confirm' /// 设置属性
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一个方法调用 querySelector() 返回选择器对象。级联符号后面的代码在此选择器对象上运行,而忽略了可能返回的任何后续值。

特立独行的闭包

List<Function> funs = List(100);
for (var index = 0; index < 100; index++) {
  funs[index] = () => print(index);
}
funs.forEach((f) { f(); });

在 JavaScript 中,上述代码的效果是打印 100 次 99,每个闭包捕获到的都是同一变量 index 在最后一次迭代中被设置的值,即 99。这是符合逻辑的,但不符合直觉。在 Dart 中,每次迭代都会分配一个新的变量,所以上述代码打印的是 0, 1, ..., 99。

好用的 assert

日常开发中,我们会写很多方法,有一些参数我们可能会希望加以限制,例如只能传大于零的整数等等。在 Dart 中我们可以通过适当添加断言来显示声明我们的预期。例如我们有一个求解阶乘的方法:

factorial(int x) {
  return x == 0 ? 1 : x * factorial(x - 1);
}

上面的代码如果对 factorial(-1) 求值,则函数会无限递归直到堆栈空间被占满,在 Dart 中,我们可以这样判断:

factorial(int x) {
  assert(x >= 0);
  return x == 0 ? 1 : x * factorial(x - 1);
}

上面的代码非常类似于:

factorial(int x) {
  if (x >= 0) return x == 0 ? 1 : x * factorial(x - 1);
  else throw new AssertionError();
}

如果条件不成立,则一个 AssertionError 被抛出。使用 assert 的好处是在生产模式下它是被关闭的,在空间和时间上都不会带来开销。 当然,上面只是举个例子说明 assert,实际情况下,还是需要对必须要处理的情况使用 if 判断等做进一步的校验。

结束语

第一篇文章,介绍了日常开发中使用 Dart 的常见场景。笔者准备做一个系列,后续会结合着 Flutter 来讲(毕竟有谁会用 Dart 开发 Web 呢,JavaScript 它不香吗?)。