Flutter入门——Dart语法

Posted by lingjye on July 12, 2019

Flutter 开发语言是Dart,点击可查看官方文档

注释

单行

1
// 单行注释

多行

1
2
3
/*
多行注释
*/ 

文档注释

1
/// 文档注释

文档注释是多行或单行的,开头使用///或/**。使用///连续的行,与多行文档注释有同样的效果。

1
2
3
4
5
6
/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
}

语言特性

  • Dart所有内容都是一个对象,每个对象都是一个类的实例。数字(numbers),函数(funtions)和null都是对象。所有对象都继承自Object类。
  • Dart是强类型的语言,变量定义时Dart可以推断类型。如果要明确说明不需要任何类型,请使用特殊类型dynamic。尽量给变量定义时设定类型,会更加安全。
  • Dart支持泛型类型,如List(整数列表)或List(任何类型的对象列表)。
  • Dart支持顶级函数(例如main()),以及绑定到类或对象的函数(分别是静态和实例方法)。还可以在函数内创建函数(嵌套函数)。
  • 类似地,Dart支持顶级变量,以及绑定到类或对象的变量(静态和实例变量)。实例变量有时称为字段或属性。
  • Dart不具备public,protected和private等关键字。如果标识符以下划线(_)开头,则表示其是私有的。
  • 标识符以小写字母或下划线(_)开头,后跟字符和数字的任意组合。
  • Dart有表达式(具有运行时值)和 语句(不具有)。语句通常包含一个或多个表达式,但表达式不能直接包含语句。
  • Dart可以报告两种问题:警告和错误。警告只是表明您的代码可能无法正常工作,但它们不会阻止您的程序执行。错误可以是编译时或运行时,编译时错误会阻止代码执行; 运行时错误导致 代码执行时引发异常。

关键字 (60个)

上下文关键字(5个),仅在特定位置具有含义,它们不在是有效的标识符

| show | async | sync | on | hide | | :—: | :—: | :—: | :—: | :—: |

内置标识符(20个),为了简化将JavaScript移植到Dart代码,这些关键字在大多数地方都是有效的标识符,但它们不能用作类或类型名称,也不能用作前缀。

abstract dynamic implements as import
static export interface external library
factory mixin operator typedef covariant
Function part get deferred set

异步支持标记(2个),Dart 1.0发布后添加的异步支持相关的更新,有限的保留字。不能使用await或yield作为任何函数体中的标识符标记async,async*或sync*。

  • await
  • yield

保留字(33个)

else assert enum in
super extends is break
this case throw catch
false new true class
final null try const
finally continue for var
void default rethrow while
return with do if
set      

变量

定义一个字符串变量:

1
var name = 'Dart';

也可以使用:

1
dynamic name = 'Dart';

或者

1
2
// 指明String类型
String name = 'Dart';

如果对象不限于单一类型,可指定Object或dynamic类型 。

var, Object, dynamic 不同:

var:其实它仅仅只是一个语法. var本身并不是一种类型,而object和dynamic是类型。var声明的变量在赋值的那一刻,就已经决定了它是什么类型。

object:所有的类型都派生自Object,所以这也是它能够被赋值为任意类型的原因。

dynamic:在编译时候不确定实际类型,而在运行时确定其实际类型

默认值:

未初始化的变量的初始值为:null。

即使是具有数字类型的变量最初也是null,因为数字也是对象。

1
int lineCount ; assert (lineCount == null );
  • assert在开发期间如果condition为false ,则抛出异常,在Release模式则会被忽略调用。

final和const

final定义的变量的值只能设置一次; const变量是编译时常量。实例变量可以是 final,但不能是 const。final实例变量必须在构造函数启动之前初始化。

例如:

1
2
3
final name = 'Dart' ; //没有类型注释final String nickname = 'Bobby' ; 
// 无法修改name的值
name = 'Flutter'; // 错误

内置类型

  • numbers
  • strings
  • booleans
  • lists (also known as arrays)
  • sets
  • maps
  • runes (for expressing Unicode characters in a string)
  • symbols

Numbers

包含两种子类:int和double。

int

整数值不大于64位,具体取决于平台。在Dart VM上,值可以是-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253-1。

double

4位(双精度)浮点数,由IEEE 754标准规定。

在Dart 2.1之前,在双上下文中使用整数文字是错误的。从Dart 2.1开始,必要时整数文字会自动转换为双精度数:

1
double z = 1; // 相当于double z = 1.0

Strings

Dart字符串UTF-16编码的一个队列。可以使用单引号或双引号来创建字符串,也可以使用表达式拼接字符串,如果表达式是标识符,则跳过’{}‘。要获取与对象相对应的字符串,Dart会调用该对象的toString()方法。可以使用 ’==‘ 判断两个字符串是否相等。也可以使用带有单引号或双引号的三引号('''""")创建多行字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var str1 = 'abc';
var str2 = "abc";
var str3 = str1 + str2;
var b = str1 == str2; // 结果true
// 多行字符串
var str4 = ’‘’
abc
def
‘’‘;

var str4 = """
abc def
""";

可以添加r前缀,代表’raw‘字符串:

1
var str = r'abc';

Booleans

  • Dart 是强 bool 类型检查,只有两个对象具有bool类型:true和false,它们都是编译时常量。
  • Dart的类型安全意味着我们不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代码, 相反Dart使用的是显式的检查值,这一点需要注意。

List

Dart中,数组是 List 对象。默认下标从0开始。

1
2
// Dart推断出list类型List<int>。如果尝试将非整数对象添加到此列表,则会引发错误
var l1 = [1, 2, 3];

Dart 2.3引入了spread operator(…)和 null-aware spread operator(…?),它提供了一个将多个元素插入集合的简洁方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// var l1 = List();
var l1 = [1, 2, 3];
var l1 = [1, 2, 3, 4];
l1.add(5);
l1.clear();
l1.addAll([10, 11, 12, 13, 14]);
l1.remove(1);
l1.removeAt(0);
l1.insert(0, 6);
l1.replaceRange(0, 1, [8, 9]);
l1.removeLast();
print(l1);
print(l1.first);
print(l1.last);
print(l1[1]);
l1.removeRange(0, 1);
// 删除值等于 8 的元素
l1.removeWhere((item)=>item == 8);
print(l1);
// 添加另一个数组的所有元素
var l2 = [0, ... l1]; 
print(l2);

Sets

Dart中的Set是无序且不重复的集合,不能通过索引去获取值,也没有固定元素的定义。从Dart 2.2开始引入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// set new是可选的
var set1 = new Set();
set1.add(1);
// 添加数组
set1.addAll(['a', 'b', 1]);
print(set1);
var set2 = new Set.of([1, 2]);
print(set2);
print(set1.contains(1));
set1.remove(1);
set1.clear();

// 指定类型
var set3 = <String>{ 'a', 'b', 'c' };
print(set3);

// Set常量
final constantSet = const {'a', 'b', 'c'};
// constantSet.add('d'); 报错,不能修改
print(constantSet);

支持(… 和 …?),参考list。

Maps

映射是无序的键值对,键和值可以是任何类型的对象。key不能相同,但是value可以一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Map
// 推断类型 Map<String, String>
var map1 = { 'key1' : 'value1', 'key2' : 'value2' };
print(map1);
// Map<int, String>
var map2 = { 2 : 'value1', 3 : 'value2' };
print(map2);
// 使用构造函数创建, Map可变的
var map3 = Map();
map3['key1'] = 'value1';
// 指定类型
var map4 = Map<int, String>();
map3[2] = 'value2';
print(map3);
// 断言, 如果在Map中找不到对应的键,则返回null
assert(map3['key2'] == null); 
// 长度
print(map3.length);
// 常量
final constantMap = const { 'key1' : 'value1'};

支持(… 和 …?),参考list。

List、Set和Map有一些通用的方法。其中的一些通用方法都是来自于类Iterable。List和Set是iterable类的实现,Map没有实现Iterable, 但是Map的属性keys和values都是Iterable对象

Runes

在Dart中,runes用于在字符串中表示Unicode字符。Dart字符串是UTF-16代码单元的序列,所以在字符串中表示32位Unicode值需要特殊的语法。

表达Unicode代码点的常用方法是‘\uXXXX’,‘XXXX’是4位十六进制值。例如,心形表情符号(♥)是\u2665。要指定多于或少于4个十六进制数字,请将值放在大括号中。例如,笑的表情符号(😆)是\u{1f600}。

该字符串类有几个属性可以用来提取符文信息。codeUnitAt和codeUnit属性返回16位编码单元。使用runes属性获取字符串的符文。

以下示例说明了符文,16位代码单元和32位代码点之间的关系

1
2
3
4
5
6
7
8
9
10
// Runes 
var r1 = '\u{1f44f}';
print(r1);
print(r1.codeUnits);
print(r1.runes.toList());

Runes input = Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}'
);
print(String.fromCharCodes(input));

使用列表操作操作符文时要小心。这种方法很容易分解,具体取决于特定的语言,字符集和操作。

Symbols

符号对象表示程序声明的操作者或标识符。它们对于按名称引用标识符的API非常有用。

要获取标识符的符号,使用符号文字‘#’号后面跟着标识符:

  • #radix
  • #bar

符号文字是编译时常量。

Functions

方Dart是一种真正的面向对象语言,因此即使是函数也是对象并且具有类型。这意味着函数可以作为变量或作为参数传递给其他函数。也可以调用Dart类的实例,就好像它是一个函数一样。

1
2
3
4
5
6
// Functions 
// 类型bool可以省略
bool compareNumber(int a, int b) {
	return a > b;
}
print(compareNumber(1, 2));

对于只包函一个表达式的函数,可以简写为:

1
2
3
// 是否是偶数
bool isEvenNumber(int a) => a % 2 == 0;
print(isEvenNumber(2));
  • 可选参数

使用这种形式paramName : value指定命名参数。

1
2
3
4
5
// {param1, param2, ...}
// 设置[bold]和[hidden]标志...
void enableFlags({ bool bold, bool hidden }){
// ...
};

在Flutter中通常还会使用@required来注释命名参数,以指定必须传的参数:

1
const Scrollbar({Key key, @required Widget child})
  • 可选位置参数

将参数放到‘[]’中,就是可选位置参数:

1
2
3
4
String say(String from, String msg, [ String device ]) {
	var result = from + msg + device;
	return result;
};  
  • 默认参数值

默认值必须是编译时常量,如果未提供则默认为null

1
2
3
4
// 默认参数值
enableFlags1({bool bold = true}) {
	// ...
};

旧代码可能使用冒号’:’而不是’=’设置命名参数的默认值。原因是最初只支持’:’命名参数。该支持可能已被弃用,因此建议使用’=’指定默认值。

  • main()函数

main()函数是应用程序的入口点,每个应用程序都必须有顶级的main()函数,该main()函数返回void并具有List参数的可选参数。

  • 作为第一类对象的功能

将函数作为参数传递给另一个函数:

1
2
3
4
5
6
void printElement(int element) {
	print(element);
}
var l = [1, 2, 3];
// 参数为函数
l.forEach(printElement); 
  • 匿名函数

匿名函数看起来类似于命名函数-零个或多个参数,在逗号和括号之间用逗号和可选类型注释分隔。通常用lambda和闭包创建。

1
2
3
4
5
6
7
8
// 匿名函数, 打印元素
var list = ['a', 'b', 'c'];
list.forEach( 
	// 参数f是一个函数,这里使用不指定名称的匿名函数
	(item) {
	  print(item);
	}
);
  • 静态作用域

Dart是静态作用域语言,变量的作用域在写代码的时候就确定过了。基本上大括号里面定义的变量就 只能在大括号里面访问。

  • 词法闭包

一个闭包是一个方法对象,不管该对象在何处被调用,该对象都可以访问其作用域内的变量。

函数可以封闭定义到其作用域内的变量。下面的示例中,makeAdder()捕获到了变量addBy。不管在哪调用makeAdder()所返回的函数,都可以使用addBy参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 返回一个相加的函数
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // add2, 和add4是闭包,Closure: (num) => num
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);
	
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}
  • 测试函数是否相等

由于Dart中的Function也是对象,所以也是可以判断相等的。

1
2
3
4
5
6
7
8
void foo() {}
void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);
}
  • 返回值

所有函数都有返回值。如果未指定返回值,则将语句return null;隐式附加到函数体。

1
2
foo() {}
assert(foo() == null);

运算符

描述 操作符
一元后置操作符 expr++ expr– () [] . ?.
一元前置操作符 expr !expr ~expr ++expr –expr
乘除 * / % ~/
加减 + -
位移 «  »
按位与 &
按位或  
按位异或 ^
逻辑与 &&
逻辑或  
关系和类型判断 >= > <= < as is is!
相等 == !=
是否为空 ??
条件表达式 expr1 ? expr2 : expr3
赋值 = *= /= ~/= %= += -= «= »= &= ^= = ??=
级联 ..

流程控制语句

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

switch语句跟Objective-C不同的是可以将String类型用来当做case。这一点跟Swift一样。

异常

  • throw 抛出异常
  • rethrow 重新抛出
  • try-catch 捕获异常
  • Finally
1
2
3
4
5
6
7
8
9
// 捕获异常
try {
	// 抛出异常
throw FormatException('预计至少有1个部分'); 
} catch(e){
	print(e);
	// 重新抛出
	rethrow;
};

可以指定多个catch()子句,使用on时需要指定异常类型,可以指定一个或两个参数catch()。第一个是抛出的异常,第二个是堆栈跟踪(StackTrace对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 捕获异常
try {
	// 抛出异常
	// throw FormatException('预计至少有1个部分'); 
	throw Exception('预计至少有1个部分'); 
} on FormatException catch(e) {
	print(e);
	// 重新抛出
	// rethrow;
} catch(e){
	print('抛出的异常:$e');
} finally {
    print('最后执行的');
};

类型定义

在Dart中,函数是对象,就像字符串和数字是对象一样。一个类型定义,或功能型的别名,使我们能够声明一些字段和返回值。当函数类型分配给变量时,typedef会保留类型信息。

以下代码不使用typedef,:

1
2
3
4
5
6
7
8
9
10
11
12
13
class SortedCollection {
  // 声明compare函数,并未指明类型
  Function compare;
  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

void main() {
  int sort(Object a, Object b) => 0;
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
}

上边分配的类型信息在f到compare的过程中丢失了,函数f的类型是(Object, Object → int ( → 代表返回),而无法知道compare的函数类型。

使用typedef来显式的定义名称和返回类型。

1
2
3
4
5
6
typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;
  SortedCollection(this.compare);
}
  • 目前,typedef仅限于函数类型,以后可能会支持更多。

  • typedef只是别名,它们提供了一种检查任何函数类型的方法。

1
2
3
4
5
6
typedef CheckCompare<T> = int Function(T a, T b);

void main() {
	int checkSort(int a, int b) => a - b;
	print(checkSort is CheckCompare<int>);
}

元数据

可以在代码中使用元数据提供其他信息。元数据注解以字符开头@,后跟对编译时常量的引用(如deprecated)或对常量构造函数的调用。

所有Dart代码都有两个注解:@deprecated和 @override(表示重写父类方法)。使用@deprecated示例:

1
2
3
4
5
6
7
8
9
10
11
class MTelevision {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  void turnOn() {
    print('turn On');
  }
}

可以定义自己的元数据注解。这是一个定义带有两个参数的@todo注解的示例:

1
2
3
4
5
6
7
8
9
10
// 必须在所有define和import的上方,一般放在文件开头
library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

使用:

1
2
3
4
5
6
7
import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

元数据可以出现在库,类,typedef,类型参数,构造函数,工厂,函数,字段,参数或变量声明之前以及导入或导出指令之前。可以使用反射在运行时检索元数据。

本文Demo