之前在学习flutter,本以为自己可以轻松上手掌握dart,结果发现经常有不懂的语法。所以决定踏踏实实的学习一遍dart。网上有很多相关学习资料.我主要从官网来学习,然后又找了一个视频来补充学习。
文章中涉及的代码,可以从我的Github上找到。
- 第一遍先看中文文档。毕竟母语是汉语,有利于快速了解。大概掌握自己哪里是之前就会的知识,哪里是新知识。这一遍只看,不进行代码编写。
- 制作思维导图。在看第一遍的时候,可以用思维导图制作一个清晰的脉络图。也不需要太复杂,只需要将每个大标题、小标题添加上就行。等接下来再去补充。
- 第二遍就看英文文档。因为第一遍的时候,对文档已经都有印象了,再看英文文档就会比较容易。主要是为了加强自己的英文阅读的能力,这样看得多了,慢慢也就记得多了。
- 第二遍的时候,遇到不会的生单词,或者高频的单词,记下来,扩大自己的词汇量。就算现在记不住,也先混个脸熟。
- 第二遍还要进行代码的演练。光看文档的话,我可没有那么强的天赋,能全都理解,毕竟文档只是给出了部分代码示例,或者说是伪代码。所以这一遍将文档中所涉及的代码全部进行一遍演练。
- 第二遍的时候,还要顺手做两件事情。其中一件就是补充之前做的思维导图,将其完整化。另一件就是,整理一下学习笔记,也就是现在写的这篇笔记。笔记的内容主要记录自己的学习内容即可。
- 第三遍,观看视频。这最后一遍,就是查漏补缺,有时候文档里面没有的,在视频中还能涉及不少,及时补充。另外视频最重要的一点,就是有些地方看文档、写代码并没有明白,视频中刚好涉及了,稍微一听,也就明白了。当然了,视频比较长,只要是之前学过了,就可以跳步看。
其实这样学下来的话,是比较耗时间的,但是我想还是扎实一下基础吧,磨刀不误砍柴工。往往有时候做项目的话,遇到一些细节,就会模棱两可,含糊不清。甚至有时候看到一块代码,并没有接触过,然后去百度、查文档,虽然当时有印象了,但知识并不系统。
环境搭建
1. 安装Dart SDK
我的电脑是Mac系统,所以需要安装Homebrew。这里我遇到的问题是始终下载失败。最后是通过科学上网以后才下载成功。
接着按照官网给出的示例,安装dart。
最后在终端中输入dart --version
显示dart版本号就说明安装成功了。
2. 配置编辑器的Dart插件
Dart环境我使用的是VS Code,非常简单,只需要安装Dart的插件即可。
- Code Runner: 是在VS Code中运行Dart插件
- Dart:是核心插件
编写一个测试文件:test.dart
void main(){
// dart类似java,需要一个入口main函数
print('123');
}
运行结果如上,说明Dart环境已经配置好了。
注意:
- dart中的文件命名规范是使用下划线分隔符,例如
test_hello
,而不要使用驼峰命名了。可以去查看官方规范手册。
重要概念
- 所有变量引用的都是 对象,每个对象都是一个 类 的实例。数字、函数以及 null 都是对象。所有的类都继承于 Object 类。
- 尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。在上述代码中,变量 number 的类型被推断为 int 类型。如果想显式地声明一个不确定的类型,可以使用特殊类型 dynamic。
- Dart 支持泛型,比如 List<int>(表示一组由 int 对象组成的列表)或 List<dynamic>(表示一组由任何类型对象组成的列表)。
- Dart 支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态 和 实例方法)。你还可以在函数中定义函数(嵌套 或 局部函数)。
- Dart 支持顶级 变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。
- Dart 没有类似于 Java 那样的 public、protected 和 private 成员访问限定符。如果一个标识符以下划线 (_) 开头则表示该标识符在库内是私有的。可以查阅 库和可见性 获取更多相关信息。
- 标识符 可以以字母或者下划线 (_) 开头,其后可跟字符和数字的组合。
- Dart 中 表达式 和 语句 是有区别的,表达式有值而语句没有。比如条件表达式 expression condition ? expr1 : expr2 中含有值 expr1 或 expr2。与 if-else 分支语句相比,if-else 分支语句则没有值。一个语句通常包含一个或多个表达式,但是一个表达式不能只包含一个语句。
- Dart 工具可以显示 警告 和 错误 两种类型的问题。警告表明代码可能有问题但不会阻止其运行。错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致异常。
变量
变量定义
//变量仅存储对象的引用
var name = 'Bob';
// 未初始化的变量拥有一个默认的初始化值:null。即便数字也是如此,因为在 Dart 中一切皆为对象,数字也不例外。
int lineCount;
assert(lineCount == null);
final和const
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
// const 关键字不仅仅可以用来定义常量,还可以用来创建 常量值
var foo = const [];
final bar = const [];
const baz = []; // 相当于 `const []` (Equivalent to `const []`)
// 还可以在变量中使用类型检查
// Valid compile-time constants as of Dart 2.5.
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: "int"}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread
内置类型
Numbers
Dart 两种数据类型:int
和double
下面是字符串和数字之间转换的方式:
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String 并且保留了指定的小数
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
Strings
使用单引号或双引号定义
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";
字符串拼接可以使用+
或者直接挨在一起的方式
var s1 = 'String '
'concatenation'
" works even over line breaks.";
var s2 = 'The + operator ' + 'works, as well.';
可以使用三个单引号或者三个双引号创建多行字符串:
var s1 = '''
你可以像这样创建多行字符串。
''';
var s2 = """这也是一个多行字符串。""";
只要是编译时常量都可以作为字符串字面量的插值表达式
void main(List<String> args) {
// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];
const validConstString = '$aConstNum $aConstBool $aConstString';
print(validConstString);
// 非编译时变量,不可以赋值
// const invalidConstString = '$aNum $aBool $aString $aConstList';
var invalidConstString = '$aNum $aBool $aString $aConstList'; // 编译时变量,可以赋值
}
string常用属性:
- length
- isEmpty
- isNotEmpty
void main(List<String> args) {
String a = '123';
print(a.length); // 3
print(a.isEmpty); // false
print(a.isNotEmpty); // true
}
string常用属性
- contains
- subString
- startsWith
- endsWith
- indexOf
- lastIndexOf
- toLowerCase
- toUpperCase
- trim
- trimLeft
- trimRight
- split
- replaceXXX
Booleans
布尔类型只有true
和false
。
void main(List<String> args) {
var test;
// dart中一切皆是对象,所以要显示判断是否为null
if(test == null){
print('test is null');
}
}
List
Dart中数组用List
对象表示。
void main(List<String> args) {
// 1.可以混合的list
var list1 = [1, 2, 3, '4'];
print(list1);
// 2.只可以是指定类型的list
List<int> list2 = [1, 2, 3];
print(list2);
// 3.用const关键字定义一个编译时数组常量
List<int> list3 = const [1, 2, 3];
print(list3);
list3[1] = 4; // 编译时出错,不可以修改
// 4.通过构造方式创建数组
List fixedLengthList = new List(3);
print(fixedLengthList.length); // 3
}
Dart中数组长度类似JavaScript语法。
void main(List<String> args) {
var list = [1, 2, 3, 4];
print(list.length); // true
print(list[1] == 2); // true
list[2] = 4;
print(list); // [1, 2, 4, 4]
}
Dart中新增扩展操作符
void main(List<String> args) {
var list1 = [1, 2, 3, 4, 5];
var nullList;
// 使用 ... 扩展list插入到另一个list
var list2 = [0, ...list1];
print(list2); // [0, 1, 2, 3, 4, 5]
// 使用 ...? 如果nullList为空,则不插入
var list3 = [0, ...?nullList];
print(list3);
}
Dart 还可以使用Collection If
和Collection for
来根据条件创建数组。
void main(List<String> args) {
/// 可以根据test条件,动态创建数组
var test = true;
var list = [
1,
2,
3,
if(test) 4
];
print(list); // [1, 2, 3, 4]
/// 也可以用循环遍历另一个数组创建一个数组
var arrays = [1,2,3,4];
var location = [
'#0',
for(var i in arrays) '#$i'
];
print(location); // [#0, #1, #2, #3, #4]
}
List常用的操作
void main(List<String> args) {
var a = [1, 2, 3];
a.add(4);
print(a); // [1, 2, 3, 4]
a.insert(1, 100);
print(a); // [1, 100, 2, 3, 4]
a.remove(4);
print(a); // [1, 100, 2, 3]
// 打乱顺序
a.shuffle();
print(a); // [2, 3, 100, 1]
print(a.asMap()); // {0: 1, 1: 100, 2: 2, 3: 3}
// 排序
List<String> numbers = ['two', 'three', 'four'];
// Sort from shortest to longest.
numbers.sort((a, b) => a.length.compareTo(b.length));
print(numbers); // [two, four, three]
// 截取
a.sublist(1);
print(a);
// 可以调用print直接打印,或者自定义其他函数
numbers.forEach(print);
}
Sets
创建sets
void main(List<String> args) {
var names = <String>{}; // 类型+{}的形式创建Set。
Set<String> names2 = {}; // 声明类型变量的形式创建 Set (This works, too).
var names3 = {}; // 这样的形式将创建一个 Map 而不是 Set (Creates a map, not a
print(names.runtimeType); // _CompactLinkedHashSet<String>
print(names3.runtimeType); // _InternalLinkedHashMap<dynamic, dynamic>
}
注意:
如果忘记在 {}
上注释类型或赋值到一个未声明类型的变量上,那么 Dart 会创建一个类型为 Map<dynamic, dynamic>
的对象。
Maps
创建Map
void main(List<String> args) {
// 相当于 Map<String, String> test = {}
var test = {
'a': '1',
'b': '2',
'c': '3'
};
// 可以不使用关键字New实例化一个对象
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
}
操作Map
void main(List<String> args) {
var map = {'a': 1, 'b': 2, 'c': 3};
print(map.length); // 3
print(map.isNotEmpty); // true
print(map.isEmpty); // false
print(map.keys); // (a, b, c)
print(map.values); // (1, 2, 3)
print(map.containsKey('c')); // true
print(map.containsValue(4)); // false
// 移除
map.remove('a'); // {b: 2, c: 3}
print(map);
map.forEach((key, value) {
print('key = $key, value = $value');
// key = b, value = 2
// key = c, value = 3
});
}
运算符
算术运算符
以前我没用过取整运算符,这里记一下。
void main(List<String> args) {
print(2 + 3 == 5);
print(2 - 3 == -1);
print(2 * 3 == 6);
// 除
print(5 / 2 == 2.5); // 结果是一个浮点数
// 取整
print(5 ~/ 2 == 2); // 结果是一个整数
// 取余
print(5 % 2 == 1); // 取余
print('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
}
自增与自减
文档中的自增与自减解释很棒。
- ++var和--var,先对var变量进行计算,然后再赋值给另一个变量
- var++和var--,先将var变量赋值给另外一个变量,然后再对自身进行操作
void main(List<String> args) {
var a, b;
a = 0;
b = ++a; // 在 b 赋值前将 a 增加 1。
print(a == b); // 1 == 1
a = 0;
b = a++; // 在 b 赋值后将 a 增加 1。
print(a != b); // 1 != 0, a = 1
a = 0;
b = --a; // 在 b 赋值前将 a 减少 1。
print(a == b); // -1 == -1, a = -1
a = 0;
b = a--; // 在 b 赋值后将 a 减少 1。
print(a != b); // -1 != 0
}
关系运算符
void main(List<String> args) {
print(2 == 2);
print(2 != 3);
print(3 > 2);
print(2 < 3);
print(3 >= 3);
print(2 <= 3);
}
类型判断运算符
当且仅当 obj
实现了 T
的接口,obj is T
才是 true。
void main(List<String> args) {
Emp emp = Emp();
Person p = Person('张三');
print(emp is Person); // true
}
class Person{
final _name;
Person(this._name);
}
class Emp implements Person{
// 必须实现
get _name => '';
}
赋值运算符
- 使用
=
来赋值 -
??=
来为值为 null 的变量赋值
void main(List<String> args) {
var a;
var b;
a = 1;
// 当且仅当 b 为 null 时才赋值
b ??= 2;
print(a); // 1
print(b); // 2
var c = 9;
c ~/= 2;
print(c); // 4
}
逻辑运算符
使用逻辑运算符你可以反转或组合布尔表达式
void main(List<String> args) {
var flag = true;
const tab = 0;
if (flag && (tab == 3 || tab == 0)) {
print('hello'); // hello
}
}
条件表达式
- 如果赋值是根据布尔表达式则考虑使用
?:
- 如果赋值是根据判定是否为 null 则考虑使用
??
void main(List<String> args) {
// 三目运算符写法 good
String playName(String name) => name != null ? name : 'Tom';
// ??写法 best
String playName3(String name) => name ?? 'Tom';
// if - else 写法 bad
String playName2(String name) {
if (name != null) {
return name;
} else {
return 'Tom';
}
}
}
级联运算符
级联运算符(..)
可以让你在同一个对象上连续调用多个对象的变量或方法。
void main(List<String> args) {
// 级联运算符严格意义上说并不是一个操作符,而是dart的特殊语法
var p = Person()
..name = 'tom'
..age = 1
..say(); // name = tom, age = 1
// 最后直接调用了say方法
}
class Person{
String name;
int age;
void say(){
print('name = $name, age = $age');
}
}
流程控制语句
与 JavaScript 不同的是,Dart 的 if 语句中的条件必须是一个布尔值,不能是其它类型
if和else
void main(List<String> args) {
var bar = false;
if (bar ==false){
print('false');
}else if( bar == true){
print('true');
}else{
print('not true or false');
}
}
for 循环
Dart在循环中的闭包会自动捕获。下面的例子在JavaScript中就会输出两个2。
void main(List<String> args) {
// for 循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱
var list = [];
for(var i =0; i<2; i++){
list.add(()=>print(i));
}
list.forEach((v) => v());
}
不需要数组索引时,使用forEach即可
var prints = [1, 2,3];
prints.forEach((v)=>print(v));
List和Set支持for-in
// List和Set支持for-in
var collections = [1, 2, 3, 4];
for (var i in collections) {
print('i = $i');
print(i);
}
while和do-while
- while 循环会在执行循环体前先判断条件
- do-while 循环则会先执行一遍循环体 再 判断条件:
void main(List<String> args) {
var i = 0;
while (true) {
++i;
print(i); // 1 2 3
if (i == 3) break;
}
print('i = $i');
do {
i++;
print(i); // 4 5 6
if(i == 6)break;
} while (true);
}
break 和 continue
- break 跳出循环
- continue 继续循环
void main(List<String> args) {
for(var i = 0; i<3; i++){
print(i); // 输出0 1
if(i ==1 ){ // 跳出循环
break;
}
print('hi 我被执行了'); // 只输出一次
}
for(var i = 0; i<3; i++){
print(i);// 输出 0 1 2
if(i ==1 ){ // 继续循环
continue;
}
print('hi 我被执行了'); // 输出2次,第二次被跳过了,循环继续
}
}
switch 和 case
- Switch 语句在 Dart 中使用 == 来比较整数、字符串或编译时常量,比较的两个对象必须是同一个类型且不能是子类并且没有重写 == 操作符
- 每一个非空的 case 子句都必须有一个 break 语句
- 当没有 case 语句匹配时,可以使用 default 子句来匹配这种情况
-
case
如果为空,则采用fall-through
形式 -
case
如果为非空,则采用continue
和label标签 - case中的变量为局部变量
void main(List<String> args) {
var name = 'annie';
switch (name) {
case 'tim':
print('tim');
break;
case 'peter':
print('peter');
break;
case 'jack': // fall-through 形式
case 'tom':
print('jack and tom');
break;
case 'annie':
print('annie');
continue ruth; // 继续执行标签为ruth的语句
ruth:
case 'ruth':
print('ruth');
break;
case 'wilson':
var test = 'test'; // 局部变量
print(test);
break;
}
}
断言
- 在开发环境下,添加断言来打断代码的执行
-
assert
是否生效依赖开发工具和使用的框架,在命令行中可以执行dart
命令
void main(List<String> args) {
var num = 100;
// 命令行中执行 dart --enable-asserts 断言.dart
// 然后就会报错,后面的所有内容不再执行
assert(num < 10);
// 第二个参数可以指定异常错误信息
assert(num < 90,
'异常: ($num) 不小于90');
// 如果直接执行 dart 断言.dart 因为是在生产环境,所以不会出现错误
}
函数
函数定义
void main(List<String> args) {
// 函数最好定义返回值
bool isBool(bool flag){
var test = false;
return test;
}
// 不写返回值倒是也行
isBool2(bool flag){
var test = false;
return test;
}
// 使用箭头函数
isBool3(bool flag) => flag = false;
}
命名可选参数
虽然参数是可选,但是也可以指定某个参数为必传,使用@required
。
import 'package:meta/meta.dart';
void main(List<String> args) {
// 定义一个可选命名参数
void saySomething({String name, int age}){
print('name = $name, age = $age');
}
// 调用可选命名参数时,不需要写{}
saySomething(name: 'tom', age: 12); // name = tom, age = 12
saySomething(name: 'cook'); // name = cook, age = null
// time参数必须传递
// 使用@required 注解必须导入meta包
// 导入meta.dart包,则必须在pubspec.yaml 文件中进行声明
void playGame({String name,@required int time}){
print('name = $name, age = $time');
}
// 虽然使用了@required 注解,并不会对应用程序报错,而是发出警告
playGame(name: '和平精英');
}
位置可选参数
位置可选参数用 []
表示
void main(List<String> args) {
void say(String name, int age, [String address]){
if(address == null){
print('name = $name, age = $age');
}else{
print('name = $name, age = $age, address = $address');
}
}
say('tom', 123); // name = tom, age = 123
say('tim', 34, '北京'); // name = tim, age = 34, address = 北京
}
默认值
可以使用=
给可选参数设置默认值
void main(List<String> args) {
void startEng({bool oil = true, bool state = false}){
return print(oil && state);
}
startEng(); // 默认false
startEng(state:true); // true
startEng(oil:true,state:true); // true
// 如果name使用默认值,但是传递 age 呢?
String say(String start, String end, [String name = 'jack', int age]){
if(name != null){ // 永远不为null
print('start = $start, end = $end, name = $name');
}
if(age!=null){
print('start = $start, end = $end, name = $name, age = $age');
}
}
say('北京','上海', '张三');
say('河南','河北', 'jack', 22);
}
main函数
- 所有Dart程序都必须有一个入口
main
函数 - 可以在命令行中传递参数
void main(List<String> args) {
print(args);
// 命令行中没有传递参数时
if(args.length == 0)return;
// 命令行中执行 dart main函数.dart 1 test
if(int.parse(args[0]) == 1){
print('第一个参数为 ${args[0]}');
}
if(args.length == 2){
print('参数的个数是 ${args.length}');
}
}
函数作为一级对象
- 将函数作为参数传递给另一个函数
- 将函数作为一个变量
void main(List<String> args) {
void say(int num){
print('hello dart, and num is $num');
}
List<int> list = const [1,2,3,4];
// 将函数作为参数传递给另一个函数
list.forEach(say);
// 将函数作为一个变量
var printName = (v)=>print('you are print $v');
printName('jack'); // you are print jack
var printName2 = (v){ return print('another print name function $v');};
printName2('tom'); // another print name function tom
}
匿名函数
- 匿名函数就是没有函数名称的函数
- 函数体只有单行时,可以使用箭头函数
void main(List<String> args) {
var list = [1, 2, 3, 4];
// 匿名函数
list.forEach((v) {
print(v);
});
// 箭头函数
list.forEach((v) => print(v));
List<String> getTime(List list, String times(str)) {
List<String> tmp = [];
list.forEach((v) {
tmp.add(times(v));
});
return tmp;
}
String times(str) {
return str * 3;
}
var list2 = ['h', 'e', 'l', 'l', 'o'];
// 这里调用 times 时不需要写(),否则就变成了执行函数了
print(getTime(list2, times)); // [hhh, eee, lll, lll, ooo]
}
词法作用域
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
// 内部函数可以逐级向上访问外部函数变量
void nestedFunction() {
var insideNestedFunction = true;
print(topLevel);
print(insideMain);
print(insideFunction);
print(insideNestedFunction);
}
}
}
闭包
- 函数对象的调用在它原始作用域之外,能够访问在它词法作用域内的变量
- 函数可以封闭定义到它作用域内的变量
void main(List<String> args) {
// 闭包就是一个函数对象
// 函数可以封闭它作用域内的变量,即使是函数在外部调用
Function sum(int add){ // 注意返回值类型是函数
return (i) => add + i;
}
// 这个1就是add,然后被封闭了起来
var sumAll = sum(1);
print(sumAll(1)); // 2
// 闭包就是在一个函数中返回另一个函数
a(){
var count = 0;
void printCount(){
print(count ++);
}
return printCount;
}
var fun = a();
// 想访问方法中的局部变量时,就使用闭包
fun(); // 0
fun(); // 1
}
函数相等性测试
- 不同实例的函数之间不相等
- 静态方法、顶级函数,都相等
void foo() {} // 定义顶层函数 (A top-level function)
class A {
static void bar() {} // 定义静态方法
void baz() {} // 定义实例方法
}
void main() {
var x;
// 比较顶层函数是否相等。
x = foo;
assert(foo == x);
// 比较静态方法是否相等。
x = A.bar;
assert(A.bar == x);
// 比较实例方法是否相等。
var v = A(); // A 的实例 #1
var w = A(); // A 的实例 #2
var y = w;
x = w.baz;
// 这两个闭包引用了相同的实例对象,因此它们相等。
assert(y.baz == x);
// 这两个闭包引用了不同的实例对象,因此它们不相等。
assert(v.baz != w.baz);
}
返回值
- 所有函数都有返回值
- 如果没有指定就返回null
void main(List<String> args) {
// 这里没有指定函数返回值
// 实际上,如果指定了,编辑器就会报错
foo(){}
var test = foo();
print(test); // null
}
库和可见性
- 使用
import
关键字导入 - dart内置库,使用
dart:xxxx
- 其他库,
package:xxxx
- 以下划线(_)开头的成员仅在代码库中可见
- 每个 Dart 程序都是一个库,即便没有使用关键字 library 指定
库前缀
如果两个库代码有冲突,可以指定库前缀
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// 使用 lib1 的 Element 类。
Element element1 = Element();
// 使用 lib2 的 Element 类。
lib2.Element element2 = lib2.Element();
导入库的一部分
只想使用代码库中的一部分,你可以有选择地导入代码库
// 只导入 lib1 中的 foo。(Import only foo).
import 'package:lib1/lib1.dart' show foo;
// 导入 lib2 中除了 foo 外的所有。
import 'package:lib2/lib2.dart' hide foo;
注释
单行注释
单行注释以 //
开始。所有在 //
和该行结尾之间的内容被编译器忽略。
// 单行注释
多行注释
- 不会忽略文档注释
- 多行注释可以嵌套
- 多行注释以
/*
开始,以*/
结尾。所有在/*
和*/
之间的内容被编译器忽略
void main() {
/*
* This is a lot of work. Consider raising chickens.
Llama larry = Llama();
larry.feed();
larry.exercise();
larry.clean();
*/
}
文档注释
- 在文档注释中,除非用中括号括起来,否则 Dart 编译器会忽略所有文本。
/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
String name;
/// Feeds your llama [Food].
///
/// The typical llama eats one bale of hay per week.
void feed(Food food) {
// ...
}
/// Exercises your llama with an [activity] for
/// [timeLimit] minutes.
void exercise(Activity activity, int timeLimit) {
// ...
}
}
在生成的文档中,[Food] 会成为一个链接,指向 Food 类的 API 文档。
也就是说,在生成的文档中[Food]这个标识符就可以显示一个链接。
类型定义
- 使用typedef显示保留类型信息
- 目前类型定义只能在函数上
// 自定义一个类型
typedef Compare = int Function(Object a, Object b);
/// 使用类型定义的情况
class SortedCollection {
Compare compare; // 自定义类型
SortedCollection(this.compare);
}
// 简单的不完整实现。
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
print(coll.compare is Function); // true
print(coll.compare is Compare); // true
}
类
声明类
- 使用class声明
- 使用new创建一个对象,new可以省略
- 所有对象都是一个类的实例
- 所有的类都继承自 Object 类
使用类成员
- 类的成员包括函数和数据
- 使用(.)来访问变量和方法
- 使用
(?.)
避免表达式为null
void main(List<String> args) {
Person p = Person();
p.name = 'tom';
p.age = 12;
print(p.name); // tom
/// ?.
// 因为p2是null,所以无法设置并且打印
// 但是使用了?.以后就不会报错了。
Person p2;
p2?.name = 'jack';
p2?.age = 13;
print(p2?.name); // null
}
class Person{
String name;
int age;
}
使用构造函数
- 使用类名
- 使用类名.标识符
- 使用identical函数判断两个类的实例是否相等
void main(List<String> args) {
// 通过 类 创建实例
Person p = Person('tom', 12);
print(p.name); // tom
print(p.age); // 12
// 通过 类名.标识符 创建实例
Person p2 = Person.fromJson({'name': 'jack', 'age': 13}) ;
print(p2.name); // jack
print(p2.age); // 13
Animal a = const Animal('titi', 2);
Animal b = const Animal('titi', 2);
print(a.name);
print(a.age);
print(b.name);
// 两个实例相等
print(identical(a,b)); // true
}
class Person{
String name;
int age;
Person(this.name, this.age);
Person.fromJson(Map<String, dynamic> json){
name = json['name'];
age = json['age'];
}
}
// 常量构造函数
class Animal{
final String name;
final int age;
const Animal(this.name, this.age);
}
实例变量
- 所有未初始化的变量均会被设置为null
- 所有实例变量均会隐式地声明一个 Getter 方法
- 所有 非 final 变量均会隐式声明一个 Setter方法
void main(List<String> args) {
Point p = Point();
print(p.x); // 调用x的 Getter
p.y = 1; // 调用y的 Setter
print(p.y); // 调用y的 Getter
}
class Point{
int x,y;
}
命名式构造函数
void main(List<String> args) {
Point p = Point.origin();
print(p.x); // 0
print(p.y); // 1
}
class Point{
int x,y;
Point(this.x, this.y);
// 命名式构造函数
Point.origin(){
x = 0;
y = 1;
}
}
调用父类非默认构造函数
调用顺序
- 1.初始化列表
- 2.父类的无参数构造函数
- 3.当前类的构造函数
传递给父类构造函数的参数不能使用 this 关键字。
使用(:)为子类的构造函数指定一个父类的构造函数。
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {.
// Person没有默认构造函数
// 需要通过 super.fromJson 来显示调用
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// 打印:
// in Person 先执行父类的构造
// in Employee
if (emp is Person) { // emp类继承了Person
emp.firstName = 'Bob';
}
print(emp.firstName); // Bob
(emp as Person).firstName = 'Jack';
print(emp.firstName); // Jack
}
初始化列表
- 在构造函数体执行前初始化变量
- 初始化列表用来设置 final 字段是非常好用的
class Person {
String firstName;
// 初始化列表 会比 构造函数优先执行
Person.fromJson(Map data): firstName = data['firstName'] {
print(firstName);
}
}
main() {
Person p = Person.fromJson({ 'firstName': 'zhangsan'});
}
设置final 字段
import 'dart:math';
class Point {
final num x;
final num y;
final num distanceFromOrigin;
// 初始化列表设置final属性,非常好用
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin); // 3.605551275463989
}
重定向构造函数
- 调用自己类中其它的构造函数
- 没有函数体
void main(List<String> args) {}
class Point {
int x, y;
Point(this.x, this.y);
// 重定向构造函数
// 在函数中调用另一个构造函数的形式
Point.origin(int num) : this(num, 0);
}
常量构造函数
- 属性用final定义为常量属性
- 构造函数用const定义为常量构造函数
void main(List<String> args) {
Point p = const Point(0, 0);
Point p2 = const Point(0, 0);
Point p3 = Point(0, 0);
// 这两个实例对象是相同的
print(identical(p, p2)); // true
// 如果不使用const声明实例,则不会相等
print(identical(p, p3)); // false
}
class Point {
// 变量必须用final 定义
final num x, y;
const Point(this.x, this.y); // 构造函数也是常量
}
工厂构造函数
void main(List<String> args) {
Person p = Person('tom');
p.say(); // tom
}
class Person{
String name;
// 必须static 定义
static final Map<String, dynamic> _cach = Map<String, dynamic>();
factory Person(String name){
return _cach.putIfAbsent(name, () => Person._init(name));
}
Person._init(this.name);
void say(){
print(name);
}
}
方法
实例方法
对象的实例方法可以访问实例变量和this
。
void main(List<String> args) {
Person p = Person('tom', 'hello title');
p.say();
}
class Person{
String name;
String title;
Person(this.name, this.title);
void say(){
// 可以访问变量
print('name is $name');
// 也可以访问this
print(this.name);
}
}
Getter和Setter
你可以使用 get 和 set 关键字为额外的属性添加 Getter 和 Setter 方法
void main(List<String> args) {
Point p = Point(1, 2, 3);
print(p.point); // 6
p.point = 0;
print(p.point);
print(p.z);
}
class Point {
int x, y, z;
Point(this.x, this.y, this.z);
get point => x + y + z;
// TODO: 这里为啥设置point 却返回z的值?
set point(int num) => z = num + x;
}
抽象方法
void main(List<String> args) {
}
// 定义抽象类
abstract class Person{
// 定义抽象方法
void doSomething();
}
class Zhangsan extends Person{
// 实现具体的方法
void doSomething(){
}
}
抽象类
void main(List<String> args) {
var me = Me();
me.sayHello();
}
abstract class Person{
String name;
int age;
void sayHello();
}
class Me extends Person{
void sayHello(){
print('hello');
}
}
隐式接口
一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API。
void main(List<String> args) {
print(saySomething(Person('张三')));
print(saySomething(Man()));
}
String saySomething(Person person) => person.sayName('李四');
class Person {
String _name;
Person(this._name);
String sayName(String name) => '$_name,你好。我是$name';
}
class Man implements Person {
get _name => '谁也不是';
set _name(String name) => ''; // 因为存在隐式的setter,所以这个也要定义
String sayName(String name) => '$_name,你好。我是$name';
}
扩展一个类
- 使用extends来扩展一个类
- 使用super来引用一个父类
void main(List<String> args) {
Man man = Man();
man.sayName();
}
class Person{
void sayName() => print('hello person');
}
class Man extends Person{
void sayName() => super.sayName(); // 调用父类方法
}
重写类成员
void main(List<String> args) {
Man man = Man();
man.sayName();
}
class Person{
void sayName() => print('hello person');
}
class Man extends Person{
@override
void sayName() => print('hello man'); // 重写实例方法
}
重写运算符
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// 运算符 == 和 hashCode 的实现未在这里展示,详情请查看下方说明。
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
noSuchMethod
这个地方没有看明白
void main(List<String> args) {
Man man = Man();
// man.name;
// todo 怎么使用??
}
class Person {
void sayName() => print('hello person');
}
class Man extends Person {
void sayName() => super.sayName(); // 调用父类方法
@override
void noSuchMethod(Invocation invocation) {
print('你尝试使用一个不存在的成员:' + '${invocation.memberName}');
}
}
枚举
- 使用enmu定义
- 每个枚举值都有index
- 使用values获取所有枚举
- 枚举不能成为子类
- 枚举不可以mixin
- 不可以实现一个枚举
- 不可以显示实例化一个枚举
使用枚举
void main(List<String> args) {
print(Color.blue); // 获取枚举
print(Color.red.index); // 获取枚举下标
List<Color> colors = Color.values; // 获取全部枚举
print(colors[2]);
}
enum Color{ // 定义枚举
red, blue, green
}
switch枚举
void main(List<String> args) {
var aColor = Color.red;
// 如果使用switch 则枚举中的每一个成员都得用case判断
// 否则就会发出警告
switch (aColor) {
case Color.red:
print('红色');
break;
case Color.blue:
print('蓝色');
break;
case Color.green:
print('绿色');
break;
}
}
enum Color {
// 定义枚举
red,
blue,
green
}
使用mixin为类添加功能
- Mixin 是一种在多重继承中复用某个类中代码的方法模式
- 使用with关键字
- 使用mixin定义
- 使用on规定哪个类可以使用
覆写操作符基本格式:
返回类型 operator 操作符(参数1,参数2...){
实现体...
return 返回值
}
void main(List<String> args) {
Musical musical = Musical();
musical.doSomethin();
}
mixin Person {
bool canCook = true;
bool canSay = false;
// mixin 模式不可以定义构造函数
// Person();
void doSomethin() {
if (canCook == true) {
print('可以做饭');
} else if (canSay == true) {
print('可以说话');
}
}
}
class Musical with Person{
@override
void doSomethin() {
// TODO: implement doSomethin
super.doSomethin(); // 直接调用父类
print('我是子类哦');
}
}
类变量和方法
静态变量
- 静态变量在其首次被使用的时候才被初始化
void main(List<String> args) {
print(Person.name); // test static
}
class Person{
static final String name = 'test static';
}
静态方法
- 对于一些通用或常用的静态方法,应该将其定义为顶级函数而非静态方法
- 可以将静态方法作为编译时常量
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
// 对于一些通用或常用的静态方法,应该将其定义为顶级函数而非静态方法。
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
泛型
为什么使用泛型
- 通常使用一个字母来代表类型参数,比如E、T、S、K 和 V 等等
- 适当地指定泛型可以更好地帮助代码生成
- 使用泛型可以减少代码重复
代码错误提示
void main(List<String> args) {
var names = List<String>(); // 声明为字符串数组,一旦不是则报错
names.addAll(['Seth', 'Kathy', 'Lars']);
// 提示报错
// names.add(42); // Error
}
减少重复代码
使用泛型声明一个类,让不同类型的缓存实现该类做出不同的具体实现。
void main(List<String> args) {}
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
class Acache extends Cache<String> {
String getByKey(String key) {
// 具体实现时指定
return 'hello';
}
void setByKey(String key, String value) {
// 具体实现时指定
print(11);
}
}
使用集合字面量
void main(List<String> args) {
List list = <String>['1', '2', '3']; // 字符串集合
Set set = <String>{'1','2','3'}; // 字符串集合
Map map = <String, int>{'age': 1, 'size':12}; // Map
}
使用类型参数化的构造函数
void main(List<String> args) {
// 与字面量相对应,也可以通过构造函数的方式使用泛型
Map map = Map<String, int>();
}
泛型集合以及他们所包含的类型
void main(List<String> args) {
List list = List<String>();
// list.addAll(['1','2']);
// 如果此时使用addAll则会报错
list.add('1');
list.add('2');
print(list is List<String>); // true
var names = List<String>();
names.addAll(['小芸', '小芳', '小民']);
print(names is List<String>); // true
}
限制参数化类型
- 指定参数类型
- 不指定参数类型,使用默认类型
- 错误参数类型,编译报错
void main(List<String> args) {
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
print(someBaseClassFoo.toString()); // 'Foo<SomeBaseClass>' 的实例
print(extenderFoo.toString()); // 'Foo<Extender>' 的实例
// 如果不指定泛型,默认是SomeBaseClass
var foo = Foo();
print(foo);
// 将非 SomeBaseClass 的类型作为泛型参数则会导致编译错误
// var foo = Foo<Object>();
}
class SomeBaseClass {}
// 这里的T,其实可以随意指定。一般是T、E、S、K等
class Foo<T extends SomeBaseClass> {
// 具体实现……
String toString() => "'Foo<$T>' 的实例";
}
class Extender extends SomeBaseClass {}
使用泛型方法
- 函数的返回类型<T>
- 参数的类型List<T>
- 局部变量的类型<T>
void main(List<String> args) {
var list = List<String>();
list.addAll(['1','2']);
var firstValue = first(list);
print(firstValue); // 1
}
T first<T>(List<T> ts) {
// 处理一些初始化工作或错误检测……
T tmp = ts[0];
// 处理一些额外的检查……
return tmp;
}
异步支持
处理Future
- 使用 async 和 await 的代码是异步的,但是看起来有点像同步代码
- 必须在带有 async 关键字的 异步函数 中使用 await
- 使用 try、catch 以及 finally 来处理使用 await 导致的异常
- await 表达式的返回值是一个 Future 对象
- Future 对象代表一个“承诺”,await 表达式会阻塞直到需要的对象返回
void main(List<String> args) {}
// async 与 await同时使用
Future checkVersion() async {
// 通过 try-catch 捕获异常
try {
var version = await lookUpVersion();
} catch (e) {
// 无法找到版本时做出的反应
}
}
void lookUpVersion() {}
异步函数
void main(List<String> args) {
}
// 普通函数直接添加async关键字即可
Future<String> lookUpVersion() async => '1.0.0';
处理Stream
- 使用async和await for循环
- 使用Stream API
- 表达式 的类型必须是 Stream
- 使用 break 和 return 语句停止接收 Stream 数据,跳出循环
- 1.等待直到 Stream 返回一个数据
- 2.使用 1 中 Stream 返回的数据执行循环体
- 3.重复 1、2 过程直到 Stream 数据返回完毕
可调用类
通过实现类的 call() 方法,允许使用类似函数调用的方式来使用该类的实例。
// WannabeFunction 类定义了一个 call() 函数,函数接受三个字符串参数,函数体将三个字符串拼接,字符串间用空格分割,并在结尾附加了一个感叹号
class WannabeFunction {
String call(String a, String b, String c) => '$a $b $c!';
}
var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');
main() => print(out);
思维导图
制作的思维导图,加深学习印象。如有错误欢迎指正。
原始图片比较大,为了保证打开速度只上传了一张截图。如果需要高清图片可以在我的源码文件中找到。
参考资料:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。