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

你可能从没注意过我,但你一定听过我的兄弟 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 它不香吗?)。

分类:
前端
标签: