最好的Dart语言学习教程是官网
英文原版官网
也可以看中文版的
以下知识点总结基于 Dart VM version: 2.5.0
-
1.Dart是一个函数式编程语言
在函数式编程中,你可以做到: 1.将函数当做参数进行传递 2.将函数直接赋值给变量 3,对函数进行解构,只传递给函数一部分参数来调用它, 让它返回一个函数去处理剩下的参数(也被称为柯里化) 4.创建一个可以被当作为常量的匿名函数 (也被称为 lambda 表达式,在 Java 的 JDK 8 release 中支持了 lambda 表达式)
重要概念(来自官网的翻译) 当你在学习 Dart 语言时, 应该牢记以下几点:
1.所有变量引用的都是 对象,每个对象都是一个 类 的实例。数字、函数以及 null 都是对象。所有的类都继承于 Object 类。
2.尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。
在上述代码中,变量 number 的类型被推断为 int 类型。
如果想显式地声明一个不确定的类型,可以使用特殊类型 dynamic。
3.Dart 支持泛型,比如 List(表示一组由 int 对象组成的列表)或 List(表示一组由任何类型对象组成的列表)。
4.Dart 支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态 和 实例方法)
。你还可以在函数中定义函数(嵌套 或 局部函数)。
5.Dart 支持顶级 变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。
6.Dart 没有类似于 Java 那样的 public、protected 和 private 成员访问限定符。
如果一个标识符以下划线 (_) 开头则表示该标识符在库内是私有的。可以查阅 库和可见性 获取更多相关信息。
7.标识符 可以以字母或者下划线 (_) 开头,其后可跟字符和数字的组合。
8.Dart 中 表达式 和 语句 是有区别的,表达式有值而语句没有。
比如条件表达式 expression condition ? expr1 : expr2 中含有值 expr1 或 expr2。
与 if-else 分支语句相比,if-else 分支语句则没有值。
一个语句通常包含一个或多个表达式,但是一个表达式不能只包含一个语句。
9.Dart 工具可以显示 警告 和 错误 两种类型的问题。警告表明代码可能有问题但不会阻止其运行。
错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致异常。
1.程序运行的入口函数 void main()
void main() {
print('Hello, World!');
}
2.关于变量定义和类型
dart的基本变量类型有
numbers
strings
booleans
lists (also known as arrays)
sets
maps
runes (for expressing Unicode characters in a string)
symbols
//dynamic 定义一个类型可变的变量
dynamic value = "";
//获取变量类型
print(value.runtimeType);//String
value = 1;
print(value.runtimeType);//int
value = [];
print(value.runtimeType);//List
value = [];
print(value.runtimeType);//List
//使用 var 定义变量
//bool变量
var isDebug = true;
//定义字符串变量
//双引号
var name = "hello world dart";
//单引号
var name2 = 'hello world dart';
//单双混用
var name3 = 'hello world "dart"';
var name4 = "hello world 'dart'";
//三个双引号(按书写格式输出)
var name5 = """
hello
world
dart
""";
//三个单引号(按书写格式输出)
var name6 = '''
hello
world
dart
''';
//创建一个原样的字符串,不会转义,回车换行也不会生效,原样输出
var s = r'In a raw string,not even \n gets special treatment';
var s1 = 'test test \ntest2';
print(s);//不会换行
print(s1);//会换行
print(r"^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+");//打印结果 ^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+
print("^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+");//打印结果 ^((https|http|ftp|rtsp|mms)?://)[^s]+
//dart里只有两个数值类型 int double 都是num的子类
//定义int变量
var year = 1977;
//定义double变量
var antennaDiameter = 3.7;
//定义List变量
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var emptyList = [];//List
//定义Set变量
var setObjects = {'Jupiter', 'Saturn', 'Uranus', 'Neptune'};
var emptySet = {};//Set
//定义map变量
var image = {
'tags': ['saturn'],
'url': '//path/to/saturn.jpg'
};
var emptyMap = {};//Map
//除了用var定义变量外
//可以直接声明类型
double z = 1;
3.流程控制语句
if (year >= 2001) {
print('21st century');
} else if (year >= 1901) {
print('20th century');
}
for (var object in flybyObjects) {
print(object);
}
for (int month = 1; month <= 12; month++) {
print(month);
}
while (year < 2016) {
year += 1;
}
dart支持switch,并且可以使用 continue和label
If you really want fall-through, you can use a continue statement and a label:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// 继续执行标签为 nowClosed 的 case 子句。
nowClosed:
case 'NOW_CLOSED':
// case 条件值为 CLOSED 和 NOW_CLOSED 时均会执行该语句。
executeNowClosed();
break;
}
4.定义方法
//最好都指定返回值
int fibonacci(int n) {
if (n == 0 || n == 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
var result = fibonacci(20);
//如果方法里只有一行,可以简写(可以省略{}和return)
int add(int a,int b) {
return a+b;
}
//可以简写成
int add(int a,int b) => a+b;
//定义一个无参匿名方法并立即运行
(){
print("hello world");
}();
//定义一个有参匿名方法并立即运行
(int i,int j){
print('i+j=${i+j}');
}(7,8);
//定义一个函数,赋值给一个变量
var fun = () {
print("hello world");
};
print("fun is function:${fun is Function}");//true
fun();
5.注释
//单行注释
var intValue = 1;
///多行注释,
///可用于dartdoc
var doubleValue = 1.0;
/*支持单行和多行注释*/
var comments = "这是注释";
6.导包
// 导入dart核心库
import 'dart:math';
// Importing libraries from external packages
import 'package:test/test.dart';
// Importing files
import 'path/to/my_other_file.dart';
//show hide
// 只导入 lib1 中的 foo。(Import only foo).
import 'package:lib1/lib1.dart' show foo;
// 导入 lib2 中除了 foo 外的所有。
import 'package:lib2/lib2.dart' hide foo;
//as
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// 使用 lib1 的 Element 类。
Element element1 = Element();
// 使用 lib2 的 Element 类。
lib2.Element element2 = lib2.Element();
//延迟加载库
import 'package:greetings/hello.dart' deferred as hello;
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
7.类 class
class Spacecraft {
// 私有变量,当前.dart文件内可以访问,其他dart文件类里面不能访问这一个变量
int _speed = 0;
String name;
DateTime launchData;
//带有语法糖的构造函数,使用创建对象同时并给成员变量赋值
Spacecraft(this.name,this.launchData) {
// 初始化代码
}
//命名构造函数
//重定向构造方法
//有时一个构造方法仅仅用来重定向到该类的另一个构造方法。
//重定向方法没有主体,它在冒号(:)之后调用另一个构造方法。
Spacecraft.unlaunched(String name) : this(name,null);
//构造函数成员变量初始化列表,成员变量初始化列表赋值比构造函数函数体还要早运行的
Spacecraft.fromJson(Map json)
: name = json["name"],
launchData = DateTime.fromMicrosecondsSinceEpoch(json["launchData"]) {
print("fromJson $launchData");
}
//构造函数上也可以加断言,调试模式下,如果断言条件不成立,会抛异常,及时发现错误
Spacecraft.fromJson2(Map json)
: assert(json.containsKey("name")),
assert(json.containsKey("launchData")) {
print("fromJson $launchData");
}
//Dart 支持工厂构造方法。它能够返回其子类甚至 null 对象。要创建一个工厂构造方法,请使用 factory 关键字。
//工厂构造方法不支持this.参数,不支持初始化列表,不支持断言
factory Spacecraft.fromJson3() {
return null;
}
// 当作常量使用的匿名函数
int get launchYear => launchData?.year;
//无参无返回值方法
void describe(){
print("Spacecraft:$name");
}
//私有的函数,以_开头的函数,只能在类内部使用,不能在外部使
void _privateFunction() {
}
}
//使用时
var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5));
voyager.describe();
var voyager3 = Spacecraft.unlaunched('Voyager III');
voyager3.describe();
//Const 构造方法
//如果你的类生成的对象永远都不会更改,则可以让这些对象成为编译时常量,会放到常量池
//为此,请定义 const 构造方法并确保所有实例变量都是 final 的。
class ImmutablePoint {
const ImmutablePoint(this.x, this.y);
final int x;
final int y;
static const ImmutablePoint origin = ImmutablePoint(0, 0);
}
var a = ImmutablePoint.origin;//常量
var a1 = const ImmutablePoint(0, 0);//常量
var a2 = ImmutablePoint(0, 0);
print(a1 == a);//true
print(a == a2);//false 一个是常量,一个不是常量
print(a1 == a2);//false 一个是常量,一个不是常量
8.继承
dart是单继承机制
//Spacecraft定义见 7.类 class
class Orbiter extends Spacecraft {
num altitude;
Orbiter(String name, DateTime launchDate, this.altitude)
: super(name, launchDate);
}
var orbiter = Orbiter("google",DateTime.now(),100);
print(orbiter.name);
print(orbiter.launchData);
print(orbiter.altitude);
orbiter.describe();
9. Mixins
Mixins 跨多个类层次结构重用代码
Mixins are a way of reusing code in multiple class hierarchies
class Piloted {
int astronauts = 1;
void describeCrew() {
print('Number of astronauts: $astronauts');
}
}
//Spacecraft定义见 7.类 class
class PilotedCraft extends Spacecraft with Piloted {
// ···
}
//PilotedCraft同时有 Spacecraft 和 Piloted的成员变量和方法
var p = PilotedCraft("test", DateTime.now());
print(p is Spacecraft);//true
print(p is Piloted);//true
//不使用 class 关键字,只使用 mixin定义,代表这一个类不能被实例化,没有构造函数,只能被with使用
mixin Musical {
bool canPlayPiano = false;
bool canCompose =false;
bool canConduct = false;
void entertainMe() {
if(canPlayPiano) {
print("Playing piano");
} else if(canConduct) {
print("Waviing hands");
}else {
print("Humming to self");
}
}
}
10.接口与抽象类
Dart 语言并没有提供 interface 关键字,但是每一个类都隐式地定义了一个接口。所有类都可以当成接口被实现
class MockSpaceship implements Spacecraft {
// ···
}
抽象类
abstract class Describable {
void describe();
void describeWithEmphasis() {
print('=========');
describe();
print('=========');
}
}
class MyDescribable extends Describable {
@override
void describe() {
// TODO: implement describe
}
}
11.async/await 避开回调地狱
网上另外一个人写的关于dart异步和消息机制的文章
【Dart学习】-- Dart之消息循环机制[翻译]
【dart学习】-- Dart之异步编程
使用async/await代替回调,可以不使用回调就可获取到异步操作后的结果,避免回调地狱,async/await可以理解成代替回调的语法糖
以创建文件,写文件,读取文件内容 为例
//使用回调方式
void main() async {
var writeToFile = (File file) {
file.writeAsStringSync("${DateTime.now()}\n", mode: FileMode.append);
file.readAsString().then((content) {
print("$content");
}, onError: (e) {
print("e:$e");
});
};
var file = File("D://test.txt");
file.exists().then((bool exists) {
if (exists) {
writeToFile(file);
} else {
file.createSync();
writeToFile(file);
}
});
}
//使用async/await去掉回调 优化代码可读性
void main() async {
var file = File("D://test.txt");
//Future exists() 下面如果没有await返回的是Future,如果有await返回的就是bool,异步的执行结果
if(!await file.exists()) {
await file.create();
}
await file.writeAsString("${DateTime.now()}\n", mode: FileMode.append);
print("${await file.readAsString()}");
}
// async*和 yield
Stream report(Iterable objects) async* {
for (var object in objects) {
await Future.delayed(oneSecond);
yield 'flies by $object';
}
//await for
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
12.异常 Exceptions throw rethrow
try catch final on 可以组合使用,常见有以下使用方式
//try catch(e) catch也可以把错误堆栈打印了来,用法catch(e,stack)
try {
//todo
} catch(e,stack) {
print("catch: $e $stack");
}
//try cache(e) finally
try {
//todo
} catch(e) {
print("catch: $e");
} finally {
//todo
}
try {
//io操作
} on IOException catch (e) {
print('Could not describe object: $e');
} finally {
//todo
}
try {
//todo
} on IOException catch(e) {
print("Could not describe object: $e");
} catch(e) {
print("catch: $e");
} finally {
//todo
}
try {
//todo
} on IOException catch (e) {
//todo
} on Exception {
//todo
} finally {
//todo
}
rethrow throw 抛出异常
与java不同,throw可以抛出任何值类型的异常
如
抛出字符串异常 throw "this exception"
抛出数字异常 throw 10101
抛出 Exception子类对象 throw FormatException();
这样也是可以的 throw FormatException
void misbehave() {
try {
throw "exception";
} catch (e) {
if (!canHandle(e)) rethrow;//把不处理的异常原样抛出
handle(e);
}
}
异常与异步
try catch可以处理 await语句的异步异常
如果没有await处理,try catch就不可以处理异步异常
Future getExceptionFuture() {
var str = Future.delayed(Duration(seconds: 4),() => throw FormatException);
print("str type :${str.runtimeType}");
return str;
}
main() async {
try {
var result = await getExceptionFuture();
print("$result");
}catch(e) {
print("has exception runtimeType:${e.runtimeType} info:$e");
}
}
其他小知识点
使用可选参数(而不是使用重载)
class Person {
String name;
int age;
int height;
int weight;
Person({this.name, this.age, this.height,this.weight});
@override
String toString() {
return 'Person{name: $name, age: $age, height: $height, weight: $weight}';
}
}
void main() {
print(Person(name: "小明"));
print(Person(name: "小明",height: 170));
print(Person(name: "小明",height: 170,weight: 100));
}
字符串模板#
字符串模板使用
1.使用$并且没有{} 例 "$x"
2.使用$并且有{} 例 "${person.name}"
//stringify(2, 3) should return '2 3'.
String stringify(int x, int y) {
return "$x $y";
}
操作符
x ??= y
如果x为null,则给x赋值y,如果x不为null,则不赋值,不运行y表达式(如y是一个函数)
int a; // The initial value of a is null.
a ??= 3;
print(a); // <-- Prints 3.
a ??= 5;
print(a); // <-- Still prints 3.
value = x ?? y
如果x为null,则赋值y给value,否则赋值x给value
var nullValue;
print(nullValue ?? "this is y");//this is y
print("this is x" ?? "this is y");//this is x
条件属性访问 (避免空指针) ?.
条件属性访问,用于避开空指针异常
myObject?.someProperty
相当于
(myObject != null) ? myObject.someProperty : null
?.可以在同一个表达式中使用多次
myObject?.someProperty?.someMethod()
箭头操作符 =>
箭头操作符,用于直接执行可边的表达式,并返回右边表达式的操作结果,用于替代函数的{}
例
bool hasEmpty = aListOfStrings.any((s) {
return s.isEmpty;
});
可以用 => 简化成
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
级联操作符 ..
级联操作符
myObject..someProperty..someMethod()
只有void修饰的方法可以使用级联,非void的都不可以使用级联
class BigObject {
int anInt = 0;
String aString = '';
List aList = [];
bool _done = false;
void allDone() {
_done = true;
}
//只有void修饰的方法可以使用级联,非void的都不可以使用级联
bool isDone() {
return _done;
}
}
BigObject fillBigObject(BigObject obj) {
if(obj is Null) {
return null;
}
return obj
..anInt = 1
..aString = 'String!'
..aList.add(3)
..allDone();
}
其他比较特殊的运算符操作符
算术运算符
assert(5 ~/ 2 == 2);// ~/ 除并取整
assert(5 % 2 == 1);// % 取模
类型判断运算符
as、is、is! 运算符是在运行时判断对象类型的运算符
// 类型检查,如果类型成立,会智能转换
if (emp is Person) {
emp.firstName = 'Bob';//不需要显式转换就可以使用Person的属性和方法
}
//如果 emp 为 null 或者不为 Person 类型,则会抛出异常。
(emp as Person).firstName = 'Bob';
get 和 set
1.可以使用get set为私有成员变量提供对外访问的方法,如果get set不需要特殊处理,成员变量定义成公开比较好
class MyClass {
int _aProperty = 0;
int get aProperty => _aProperty;
set aProperty(int value) {
if (value >= 0) {
_aProperty = value;
}
}
}
2.也给私有成员变量只使用 get,使变量对外只读
class MyClass {
int _aProperty = 0;
int get aProperty => _aProperty;
}
3.只使用 get 可以使一个函数对外像一个属性一样访问
class MyClass {
List _values = [];
//函数对外像一个属性一样访问,省去() var len = MyClass().count;
int get count {
return _values.length;
}
}
可选位置参数
//普通方法的参数列表
int sumUp(int a, int b, int c) {
return a + b + c;
}
//调用时三个参数都需要传值,缺一不可
int total = sumUp(1, 2, 3);
//可选位置参数
//参数列表可以全部都是可选位置参数,可以使用在构造函数上
//可选位置参数需要用[]括起来,只能放在参数列表后面,可以有一个或多个,
//方法调用时可选位置参数是可选的,不一定需要全部传,
//可选位置参数可以有默认值,如果定义时没有赋默认值,则为null
//可选位置参数不能与可选命名参数共存,有可选位置参数,不能有可选命名参数
int sumUpToFive(int a, [int b, int c=1, int d, int e]) {
int sum = a;
if (b != null) sum += b;
if (c != null) sum += c;
if (d != null) sum += d;
if (e != null) sum += e;
return sum;
}
int total = sumUptoFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
可选命名参数
//参数列表可以全部都是可选命名参数,可以使用在构造函数上
//可选命名参数需要放到参数列表后面,同可选位置参数一样可以有默认值,也可能没有,
//方法调用时可选命名参数是可选的,不一定需要全部传,
//与可选位置参数不同的是,可选命名参数需要使用时,要带上参数名称
//可选命名参数不能与可选位置参数共存,有可选命名参数,不能有可选位置参数
void printName(String firstName,String lastName,{String prefix,String suffix=''}) {
print("$prefix $firstName $lastName ${suffix ?? ""}");
}
printName("Poshmeister", "Moneybuckets",suffix:"IV");
printName("Poshmeister", "Moneybuckets",prefix:"prefix",suffix:"IV");
扩展方法
dart在2.7版本支持了扩展方法(kotlin也有相同的概念)
示例:
void main() {
print('10086'.parseInt());
print('10086'.parseInt().runtimeType);
print(Person(name: 'alibaba',age: 18,weight: 100,height: 200,country: 'zh').isChinesePeople());
}
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
// ···
}
extension PersonMethods on Person {
bool isChinesePeople () {
return 'zh' == country;
}
}
class Person {
String name;
int age;
int weight;
int height;
String country;
Person({this.name, this.age, this.weight, this.height,this.country});
}
dart代码规范
- 关于dart代码规范,可以查看阿里团队总结的规范
<< Flutter Go 开发规范第一版 >>