Hello World Dart的入口也是main函数:
1 2 3 main(List<String> args) { print("Hello World"); }
变量声明 显式变量声明的方式如下:
1 2 3 4 //变量类型 变量名称 = 赋值; String name = "dart fans"; int age = 20;
另外,也可以省略变量类型,由Dart进行类型推断(类似swift)。
1 var/dynamic/const/final 变量名称 = 赋值;
Dart本身是一个强类型语言,任何变量都是有确定类型的,声明变量后,只可以修改变量值,不能修改变量类型。
var 使用示例:
1 2 3 4 5 var language = "Dart"; //runtimeType用于获取变量当前的类型 print(language.runtimeType); //String
dynamic和Object dynamic与var一样都是关键词,声明的变量可以赋值任意对象,功能类似于Objective-C
中的id
类型和Swift
中的Any
。
1 2 3 4 5 dynamic level = 28; print(level.runtimeType); //int level = "top"; print(level.runtimeType); //String
Object 是Dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象。
dynamic与var一样都是关键词,声明的变量可以赋值任意对象。 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型。
dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。
final和const final和const用于定义常量,定义之后的值都不可修改。使用final或者const修饰的变量,变量类型可以省略。
两者区别在于:
const为编译时常量,在编译期间就必须确认赋值;
final支持运行时赋值,可以在第一次使用时进行初始化;
数据类型 数字类型 Dart中的int和double可表示的范围并不是固定的,它取决于运行Dart的平台。
字符串和数字之间的转化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 1.字符串转数字 var one = int.parse('111'); var two = double.parse('12.22'); print('${one} ${one.runtimeType}'); // 111 int print('${two} ${two.runtimeType}'); // 12.22 double // 2.数字转字符串 var num1 = 123; var num2 = 123.456; var num1Str = num1.toString(); var num2Str = num2.toString(); var num2StrD = num2.toStringAsFixed(2); // 保留两位小数 print('${num1Str} ${num1Str.runtimeType}'); // 123 String print('${num2Str} ${num2Str.runtimeType}'); // 123.456 String print('${num2StrD} ${num2StrD.runtimeType}'); // 123.46 String
布尔类型 Dart提供了一个bool的类型, 取值为true和false。要注意的是,Dart中不能判断非0即真, 或者非空即真。
1 2 3 4 5 var message = 'Hello Dart'; // 错误的写法 if (message) { print(message) }
字符串类型 Dart字符串是UTF-16编码单元的序列。可以使用单引号或双引号创建一个字符串, 可以使用三个单引号或者双引号表示多行字符串:
1 2 3 4 5 6 7 var s1 = 'Hello World'; var s2 = "Hello Dart"; var s3 = ''' 哈哈哈 呵呵呵 嘿嘿嘿 ''';
集合类型 对于集合类型,Dart则内置了最常用的三种:List / Set / Map。
List使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 1.使用类型推导定义 var letters = ['a', 'b', 'c', 'd']; print('$letters ${letters.runtimeType}'); //[a, b, c, d] List<String> print(letters.length); //4 // 2.明确指定类型 List<int> numbers = [1, 2, 3, 4]; print('$numbers ${numbers.runtimeType}'); //[1, 2, 3, 4] List<int> print(numbers.length); //4 // 3.常用操作 numbers.add(5); //[1, 2, 3, 4, 5] numbers.remove(1); //[2, 3, 4, 5] numbers.removeAt(1); //2, 4, 5] numbers.contains(2); //true
Set使用:
1 2 3 4 5 6 7 8 9 // 1.使用类型推导定义 var lettersSet = {'a', 'b', 'c', 'd'}; print('$lettersSet ${lettersSet.runtimeType}'); //{a, b, c, d} _CompactLinkedHashSet<String> // 2.明确指定类型 Set<int> numbersSet = {1, 2, 3, 4}; print('$numbersSet ${numbersSet.runtimeType}'); //{1, 2, 3, 4} _CompactLinkedHashSet<int>
Map使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 1.使用类型推导定义 var infoMap1 = {'name': 'why', 'age': 18}; print('$infoMap1 ${infoMap1.runtimeType}'); // 2.明确指定类型 Map<String, Object> infoMap2 = {'height': 1.88, 'address': '北京市'}; print('$infoMap2 ${infoMap2.runtimeType}'); // 3.Map的操作 // 根据key获取value print(infoMap1['name']); // why // 获取所有的keys print('${infoMap1.keys} ${infoMap1.keys.runtimeType}'); // (name, age) _CompactIterable<String> // 获取所有的values print('${infoMap1.values} ${infoMap1.values.runtimeType}'); // (why, 18) _CompactIterable<Object> // 判断是否包含某个key或者value print('${infoMap1.containsKey('age')} ${infoMap1.containsValue(18)}'); // true true // 根据key删除元素 infoMap1.remove('age'); print('${infoMap1}'); // {name: why}
函数 Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function 。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。
函数的定义:
1 2 3 4 返回值 函数的名称(参数列表) { 函数体 return 返回值 }
Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic 处理,并不会进行类型推断。
对于只包含一个表达式的函数,可以使用简写语法:
1 2 sum(num1, num2) => num1 + num2;
Dart中没有函数重载。
函数的可选参数 函数的可选参数分为可选的位置参数 和可选的命名参数 。但是两者不能同时使用。
可选的位置参数 包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:
1 2 3 4 5 6 7 8 9 // 定义 printInfo2(String name, [int age, double height]) { print('name=$name age=$age height=$height'); } // 调用 printInfo2('why'); // name=why age=null height=null printInfo2('why', 18); // name=why age=18 height=null printInfo2('why', 18, 1.88); // name=why age=18 height=1.88
可选的命名参数 定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。可选命名参数在Flutter中使用非常多。例如:
1 2 3 4 5 6 7 8 9 10 // 定义 printInfo1(String name, {int age, double height}) { print('name=$name age=$age height=$height'); } // 调用 printInfo1('why'); // name=why age=null height=null printInfo1('why', age: 18); // name=why age=18 height=null printInfo1('why', age: 18, height: 1.88); // name=why age=18 height=1.88 printInfo1('why', height: 1.88); // name=why age=null height=1.88
可选参数的默认值 只有可选参数才可以设置默认值。
1 2 3 printInfo1(String name, {int age = 18, double height = 1.88}) { print('name=$name age=$age height=$height'); }
Frist class citizen 函数在Dart中属于一等公民,这就是说可以将函数赋值给一个变量,也可以将函数作为另外一个函数的参数或者返回值来使用。
函数作为变量使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void say(String msg) { print("hello ${msg}"); } var func1 = say; func1("dart1"); var func2 = (msg){ print("hello $msg"); }; func2("dart2"); var func3 = (msg)=>print("hello $msg"); func3("dart3");
函数作为参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int add(int num1, int num2) { return num1 + num2; } int subtract(int num1, int num2) { return num1 - num2; } typedef Calculate = int Function(int num1, int num2); int calculate(int num1, int num2, Calculate cal) { return cal(num1, num2); } var ret1 = calculate(20, 10, add); print(ret1); //30 var ret2 = calculate(10, 20, subtract); print(ret2); //-10 var ret3 = calculate(10, 20, (num1, num2) => num1 * num2); print(ret3); //200 var ret4 = calculate(20, 10, (num1, num2){ return num1 ~/ num2; }); print(ret4); //2
函数作为返回值 1 2 3 Function getFunc() { return add; }
运算符 除法与取模运算符 1 2 3 4 var num = 7; print(num / 3); // 除法操作, 结果2.3333.. print(num ~/ 3); // 整除操作, 结果2; print(num % 3); // 取模操作, 结果1;
赋值运算符??= 规则:
当变量为null时,使用后面的内容进行赋值
当变量有值时,使用自己原来的值
1 2 3 4 5 6 7 8 main(List<String> args) { var name1 = 'coderwhy'; print(name1); // var name2 = 'kobe'; var name2 = null; name2 ??= 'james'; print(name2); // 当name2初始化为kobe时,结果为kobe,当初始化为null时,赋值了james }
条件运算符?? expr1 ?? expr2
,规则:
如果expr1是null,则返回expr2的结果
如果expr1不是null,直接使用expr1的结果
1 2 3 4 5 var temp = 'why'; var temp = null; var name = temp ?? 'kobe'; print(name);
级联运算符 可以使用级联运算符..
对一个对象进行连续操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Person { String? name; void run() { print("${name} is running"); } void eat() { print("${name} is eating"); } void swim() { print("${name} is swimming"); } } main(List<String> args) { final p1 = Person(); p1.name = 'why'; p1.run(); p1.eat(); p1.swim(); final p2 = Person() ..name = "why" ..run() ..eat() ..swim(); }
流程控制 if,for,swich语法都与其他语言一致。
类和对象 类的定义 使用class
关键字定义一个类:
1 2 3 4 5 6 class 类名 { 类型 成员名; 返回值类型 方法名(参数列表) { 方法体 } }
可以在成员变量命名前加上下划线_
表明该成员变量为私有变量。
构造方法 普通构造方法
当类中没有明确指定构造方法时,将默认拥有一个无参的构造方法
自定义构造方法后,默认的构造方法将会失效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { String? name; int? age; Person(String name, int age) { this.name = name; this.age = age; } @override String toString() { return 'name=$name age=$age'; } }
上面的构造方法也可以简写:
1 2 3 4 5 6 Person(String name, int age) { this.name = name; this.age = age; } // 等同于 Person(this.name, this.age);
命名构造方法 由于不支持方法(函数)的重载,所以没办法创建相同名称的构造方法, 此时可以使用命名构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Person { String?name; int?age; Person() { name = ''; age = 0; } // 命名构造方法 Person.withArgments(String name, int age) { this.name = name; this.age = age; } @override String toString() { return 'name=$name age=$age'; } } // 创建对象 var p1 = new Person(); print(p1); var p2 = new Person.withArgments('why', 18); print(p2);
初始化列表 下面这种初始化变量的方法, 称为初始化列表(Initializer list):
1 2 3 4 5 6 7 8 9 10 11 12 13 class Point { final num x; final num y; final num distance; // 错误写法 // Point(this.x, this.y) { // distance = sqrt(x * x + y * y); // } // 正确的写法 Point(this.x, this.y) : distance = sqrt(x * x + y * y); }
重定向构造方法 重定向构造方法是指在一个构造方法中去调用另外一个构造方法:
1 2 3 4 5 6 7 class Person { String? name; int? age; Person(this.name, this.age); Person.fromName(String name) : this(name, 0); }
常量构造方法 默认情况下,创建对象时,即使传入相同的参数,创建出来的也不是同一个对象。
在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法。拥有常量构造方法的类中,所有的成员变量必须是final修饰的。
1 2 3 4 5 6 7 8 9 10 11 main(List<String> args) { var p1 = const Person('why'); var p2 = const Person('why'); print(identical(p1, p2)); // true } class Person { final String name; const Person(this.name); }
工厂构造方法 Dart提供了factory关键字, 用于通过工厂去获取对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 main(List<String> args) { var p1 = Person('why'); var p2 = Person('why'); print(identical(p1, p2)); // true } class Person { String? name; static final Map<String, Person> _cache = <String, Person>{}; factory Person(String name) { if (_cache.containsKey(name)) { return _cache[name]; } else { final p = Person._internal(name); _cache[name] = p; return p; } } Person._internal(this.name); }
getter和setter getter和setter可以用来监控类的属性被访问的过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 main(List<String> args) { final d = Dog("黄色"); d.setColor = "黑色"; print(d.getColor); } class Dog { String? color; String get getColor { return color; } set setColor(String color) { this.color = color; } Dog(this.color); }
类的继承 Dart中的继承使用extends关键字,子类中使用super来访问父类。
父类中的所有成员变量和方法都会被继承,,但是构造方法除外。子类可以拥有自己的成员变量, 并且可以对父类的方法进行重写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 main(List<String> args) { var p = Person(); p.age = 18; p.run(); print(p.age); } class Animal { int? age; run() { print('在奔跑ing'); } } class Person extends Animal { String? name; @override run() { print('$name在奔跑ing'); } }
子类中可以调用父类的构造方法,对某些属性进行初始化:
子类的构造方法在执行前,将隐含调用父类的无参默认构造方法(没有参数且与类同名的构造方法)。
如果父类没有无参默认构造方法,则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Animal { int age; Animal(this.age); run() { print('在奔跑ing'); } } class Person extends Animal { String name; Person(String name, int age) : name=name, super(age); @override run() { print('$name在奔跑ing'); } @override String toString() { return 'name=$name, age=$age'; } }
抽象类 使用abstract
声明一个抽象类。抽象类不能实例化,抽象类中的抽象方法必须被子类实现, 抽象类中的已经被实现方法, 可以不被子类重写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 abstract class Shape { getArea(); } class Circle extends Shape { double r; Circle(this.r); @override getArea() { return r * r * 3.14; } } class Reactangle extends Shape { double w; double h; Reactangle(this.w, this.h); @override getArea() { return w * h; } }
隐式接口 Dart中没有一个专门的关键字来声明接口。Dart不支持多继承,可以使用抽象类实现接口的功能。当将一个类当做接口使用时, 那么实现(implements )这个接口的类, 必须实现这个接口中所有方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 abstract class Runner { run(); } abstract class Flyer { fly(); } class SuperMan implements Runner, Flyer { @override run() { print('超人在奔跑'); } @override fly() { print('超人在飞'); } }
Mixin混入 在通过implements实现某个类时,类中所有的方法都必须被重新实现(无论这个类原来是否已经实现过该方法)。
要想服用之前类的实现,可以使用Mixin混入方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 main(List<String> args) { var superMan = SuperMain(); superMan.run(); superMan.fly(); } mixin Runner { run() { print('在奔跑'); } } mixin Flyer { fly() { print('在飞翔'); } } // implements的方式要求必须对其中的方法进行重新实现 // class SuperMan implements Runner, Flyer {} class SuperMain with Runner, Flyer { }
类成员和方法 使用static关键字来定义类成员和方法。
枚举类型 使用enum关键字来进行定义,枚举类型中有两个比较常见的属性:
index: 用于表示每个枚举常量的索引, 从0开始
values: 包含每个枚举值的List
1 2 3 4 5 6 7 8 9 10 11 main(List<String> args) { print(Colors.red.index); //0 print(Colors.values); //[Colors.red, Colors.green, Colors.blue] } enum Colors { red, green, blue }
泛型 List和Map List和Map中泛型的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 创建List的方式 var names1 = ['why', 'kobe', 'james', 111]; print(names1.runtimeType); // List<Object> // 限制类型 var names2 = <String>['why', 'kobe', 'james', 111]; // 最后一个报错 List<String> names3 = ['why', 'kobe', 'james', 111]; // 最后一个报错 // 创建Map的方式 var infos1 = {1: 'one', 'name': 'why', 'age': 18}; print(infos1.runtimeType); // _InternalLinkedHashMap<Object, Object> // 对类型进行显示 Map<String, String> infos2 = {'name': 'why', 'age': 18}; // 18不能放在value中 var infos3 = <String, String>{'name': 'why', 'age': 18}; // 18不能放在value中
类的泛型 定义一个泛型类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 main(List<String> args) { Location l2 = Location<int>(10, 20); print(l2.x.runtimeType); // int Location l3 = Location<String>('aaa', 'bbb'); print(l3.x.runtimeType); // String } } class Location<T> { T x; T y; Location(this.x, this.y); }
还可以限制T只能是指定类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 main(List<String> args) { Location l2 = Location<int>(10, 20); print(l2.x.runtimeType); // 错误的写法, 类型必须继承自num Location l3 = Location<String>('aaa', 'bbb'); print(l3.x.runtimeType); } class Location<T extends num> { T x; T y; Location(this.x, this.y); }
方法的泛型 1 2 3 4 5 6 7 8 9 10 main(List<String> args) { var names = ['why', 'kobe']; var first = getFirst(names); print('$first ${first.runtimeType}'); // why String } T getFirst<T>(List<T> ts) { return ts[0]; }
库的使用 Dart中任何一个dart文件都是一个库,即使你没有用关键字library
声明。
库的导入 使用import
导入一个库:
导入系统库:
导入项目中其他库:
1 2 //当然,你也可以用相对路径或绝对路径的dart文件来引用 import 'lib/student/student.dart';
导入Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package:
1 import 'package:flutter/material.dart';
库命名冲突 当各个库有命名冲突的时候,可以使用as
关键字来使用命名空间:
1 2 3 import 'lib/student/student.dart' as Stu; Stu.Student s = Stu.Student();
库文件的显示和隐藏 如果希望只导入库中某些内容,或者刻意隐藏库里面某些内容,可以使用show
和hide
关键字:
1 2 3 // 屏蔽其他 import 'lib/student/student.dart' show Student, Person; import 'lib/student/student.dart' hide Person;
库的定义 使用关键字library
定义一个库。当一个库包含多个文件时,可以使用关键字export
进行管理:
1 2 3 4 5 6 7 // 定义: library utils; export "mathUtils.dart"; export "dateUtils.dart"; // 使用: import "lib/utils.dart";
异步支持 Dart类库有非常多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。
async
和await
关键词支持了异步编程,允许写出和同步代码很像的异步代码。
Future Future
表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。
Future
的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。
Future.then 使用Future.delayed
创建了一个延时任务,即2秒后返回结果字符串”hi world!”,然后我们在then中接收异步结果:
1 2 3 4 5 Future.delayed(new Duration(seconds: 2),(){ return "hi world!"; }).then((data){ print(data); });
Future.catchError 如果异步任务发生错误,可以在catchError中捕获错误,将上面示例改为:
1 2 3 4 5 6 7 8 9 10 Future.delayed(new Duration(seconds: 2),(){ //return "hi world!"; throw AssertionError("Error"); }).then((data){ //执行成功会走到这里 print("success"); }).catchError((e){ //执行失败会走到这里 print(e); });
then方法还有一个可选参数onError,我们也可以它来捕获异常:
1 2 3 4 5 6 7 8 Future.delayed(new Duration(seconds: 2), () { //return "hi world!"; throw AssertionError("Error"); }).then((data) { print("success"); }, onError: (e) { print(e); });
Future.whenComplete whenComplete
中的代码无论成功或失败都会被执行:
1 2 3 4 5 6 7 8 9 10 11 12 Future.delayed(new Duration(seconds: 2),(){ //return "hi world!"; throw AssertionError("Error"); }).then((data){ //执行成功会走到这里 print(data); }).catchError((e){ //执行失败会走到这里 print(e); }).whenComplete((){ //无论成功或失败都会走到这里 });
Future.wait 等待多个异步任务都执行结束后才进行一些操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Future.wait([ // 2秒后返回结果 Future.delayed(new Duration(seconds: 2), () { return "hello"; }), // 4秒后返回结果 Future.delayed(new Duration(seconds: 4), () { return " world"; }) ]).then((results){ print(results[0]+results[1]); }).catchError((e){ print(e); });
Async/await 如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then
回调中套回调情况。
async
用来表示函数是异步的,定义的函数会返回一个Future
对象,可以使用then
方法添加回调函数。
await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走;await
必须出现在async
函数内部。
1 2 3 4 5 6 7 Future<String> getNetworkData() async { var result = await Future.delayed(Duration(seconds: 3), () { return "network data"; }); return "请求到的数据:" + result; }