参阅书籍:
《Flutter跨平台开发入门与实践》-- 向治洪(著)
参阅网站:
https://www.dartcn.com/guides/language/language-tour
3. Dart语言
Dart是谷歌公司于2011年10月发布的一门全新的编程语言,已被欧洲计算机制造商协会(European Computer Manufacture Association,ECMA)认定为标准,初期目标是将其用于Web应用、服务器、移动应用和物联网等领域的开发。
Dart在设计之初参考了Java等面向对象的编程语言,因此Dart既有面向对象编程的特性,也有面向函数编程的特性。除了融合Java和JavaScript所长之外,Dart还提供了一些其他具有表现力的语法,如可选命名参数、级联运算符和条件成员访问运算符等。
使用Dart之前,需要先安装Dart SDK。Dart SDK包含了编写和运行Dart代码所需的一切工具,如虚拟机(Virtual Machine,VM)、库、分析器、包管理工具、文档生成器和代码调试等。
搭建开发环境时安装的Flutter SDK里已经包含了Dart SDK。
3.1 编写 Hello World
和大多数编程语言一样,Dart也把main()作为程序的入口。
首先,新建一个名为hello.dart的文件,添加如下代码。
然后在终端执行dart hello.dart命令,在终端可以看到输出了“Hello World!”。
3.2 变量和常量
变量仅存储对象引用。
3.2.1 声明变量
//用var声明变量,name变量的类型会被推断为String
var name = 'huahua';
//用dynamic声明变量,不限定name变量的类型
dynamic name = 'huahua';
//显示声明变量,限定name变量的类型为String
String name = 'huahua';
注:显示声明的变量必须初始化后才能使用。
3.2.2 默认值
在Dart中,一切皆为对象,未初始化的变量默认值是null。
3.2.3 声明常量
在Dart中,声明使用过程中不会被修改的变量(即常量)可以使用final或const关键字。
final变量的值只能被设置一次,const变量的值在编译时就已经固定。
实例变量可以是final变量,但不能是const变量。
const还可以用来创建常量值,以及声明创建常量值的构造函数。
3.3 内置数据类型
3.3.1 Number
Number有两种类型:int(整型) 和 double(浮点型)。
从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型。
字符串和数字相互转换的方法:
// String -> int
var one = int.parse('1');
// String -> double
var onePointOne = double.parse('1.1');
// int -> String
String oneAsString = 1.toString();
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
3.3.2 String
String是一组UTF-16的单元序列,通过单引号或双引号进行声明。
字符串可以通过 ${expression} 的方式内嵌表达式。如果表达式是一个标识符,则 {} 可以省略。
var s = 'string interpolation';
print('Dart has $s, which is very handy.');//Dart has string interpolation, which is very handy.
print('That deserves all caps. ' + '${s.toUpperCase()} is very handy!');//That deserves all caps. STRING INTERPOLATION is very handy!
使用连续三个单引号或者三个双引号实现多行字符串对象的创建。
使用 r 前缀,可以创建原始raw字符串。
3.3.3 Boolean
Dart使用 bool 类型表示布尔值。Dart只有字面量 true 和 false 是布尔类型,这两个对象都是编译时常量。
3.3.4 List
在Dart中,List表示列表,和数组是同一概念。Dart中的List类型和JavaScript中的Array类型是类似的。
//通过字面量创建List
var list = [1, 2, 3];
3.3.5 Set
在Dart中Set是一个元素唯一且无序的集合。
//通过字面量创建Set
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
//创建一个空集
var names = <String>{};
//或Set<String> names = {};
//这样会创建一个Map,而不是Set
var names = {};
3.3.6 Map
通常来说, Map 是用来关联 keys 和 values 的对象。keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次,但是 value 可以出现多次。
//通过字面量创建Map
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
//通过构造函数创建Map
var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
3.3.7 Rune
在 Dart 中,Rune 用来表示字符串中的 UTF-32 编码字符。
表示 Unicode 编码的常用方法是 \uXXXX, 这里 XXXX 是一个4位的16进制数。对于特殊的非 4 个数值的情况,把编码值放到大括号中即可。
var clapping = '\u{1f44f}';
print(clapping);//👏
Runes input = new Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));//♥ 😅 😎 👻 🖖 👍
3.3.8 Symbol
一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符。通过字面量 Symbol ,也就是标识符前面添加一个 # 号,来获取标识符的 Symbol 。
print(#radix);//Symbol("radix")
print(#bar);//Symbol("bar")
3.4 函数
Dart 是一门真正面向对象的语言, 其中的函数也是对象,并且有它的类型 Function。这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。也可以把 Dart 类的实例当做方法来调用。
3.4.1 main() 函数
任何应用都必须有一个顶级 main() 函数,作为应用服务的入口。main() 函数返回值为空,参数为一个可选的 List<String> 。
Flutter应用的main():
void main() => runApp(MyApp());
3.4.2 函数参数
函数的参数类型有两种,即必传参数和可选参数。通常,可选参数写在必传参数的后面,可选参数使用命名参数或位置参数进行传值。
可选命名参数用 {} 包裹。
void main() {
//调用函数时,使用指定命名参数 paramName: value
param(2, age: 30);//num: 2, name: huahua, age: 30
}
void param(int num, {String name = 'huahua', int age = 20}){
print('num: $num, name: $name, age: $age');
}
可选位置参数用 [] 包裹。
void main() {
param(2, 'huanhuan');//num: 2, name: huanhuan, age: 20
}
void param(int num, [String name = 'huahua', int age = 20]){
print('num: $num, name: $name, age: $age');
}
list 或 map 可以作为默认值传递。
void main() {
doStuff(list: [4, 5, 6]);
/*
* list: [4, 5, 6]
* gifts: {first: paper, second: cotton, third: leather}
*/
}
void doStuff (
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
3.4.3 返回值
所有函数都会返回一个值,如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句。
void main() {
foo(){}
print(foo());//null
}
3.4.4 匿名函数
和其他编程语言一样,Dart也支持匿名函数。
void main() {
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));
/*
* 0: apples
* 1: bananas
* 2: oranges
*/
}
3.5 运算符
Dart支持各种类型的运算符,部分运算符支持重载操作。
3.5.1 算数运算符
+(加法)、-(减法)、*(乘法)、/(除法)、~/(除法,取整)、-expr(取负)、%(取余)
print(3 + 2);//5
print(3 - 2);//1
print(3 * 2);//6
print(3 / 2);//1.5
print(3 ~/ 2);//1
print(-3);//-3
print(3 % 2);//1
Dart也支持自增和自减运算符。
int num = 3;
print(num++);//3
print(++num);//5
print(num--);//5
print(--num);//3
3.5.2 关系运算符
==(相等)、!=(不相等)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)
int a = 3, b = 2;
print(a == b);//false
print(a != b);//true
print(a > b);//true
print(a < b);//false
print(a >= b);//true
print(a <= b);//false
3.5.3 类型判断运算符
as:类型转换,也被用于指定库前缀。
is:对象具有指定的类型,返回true。
is!:对象具有指定的类型,返回false。
3.5.4 位运算符
&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、>>(右移)、<<(左移)
final value = 0x22;
final bitmask = 0x0f;
print((value & bitmask) == 0x02); //true
print((value & ~bitmask) == 0x20); //true
print((value | bitmask) == 0x2f); //true
print((value ^ bitmask) == 0x2d); //true
print((value << 4) == 0x220); //true
print((value >> 4) == 0x02); //true
3.5.5 逻辑运算符
&&(与)、||(或)、!(非)
3.5.6 赋值运算符
=、+=、-=、*=、/=、%=、~/=、>>=、<<=、^=、&=、!=
使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它。
var a;
var b = 10;
print(a ??= b); //10
3.5.7 条件表达式
三目表达式:condition ? expr1 : expr2
//如果条件为true,执行并返回expr1的值;如果条件为false,执行并返回expr2的值。
var isPublic = true;
var visibility = isPublic ? 'public' : 'private';
print('visibility: $visibility');//visibility: public
??运算符:expr1 ?? expr2
//如果expr1是non-null,返回expr1的值;否则,执行并返回expr2的值。
String playName(var name) => name ?? 'huahua';
print(playName('huanhuan'));//huanhuan
3.5.8 级联运算符
级联运算符 (..) 可以实现对同一个对象进行一系列的操作。除了调用函数, 还可以访问同一对象上的字段属性。
3.6 流程控制语句
Dart的流程控制语句主要由if-else、for、while与do-while、break与continue、switch与case、assert等构成。
assert用于表示断言,用于判断语句是否满足条件。如果assert修饰的语句中的判断结果为false,那么正常的程序执行流程会被中断;如果assert修饰的判断条件结果为true,则继续执行后面的语句。
var text = 'hello';
assert(text != null);
print('$text world!');//hello world!
注:Flutter中的assert只在Debug模式中生效,在生产环境是无效的。
3.7 类
3.7.1 类的成员变量
在面向对象编程中,类的对象通常由函数和数据组成。方法的调用需要通过对象来完成,被调用的方法还可以访问其对象的函数和数据。我们使用点操作符来引用对象的变量和方法。
void main() {
Point p = new Point();
p.x = 2;
p.y = 2;
print(p.x);//2
print(p.y);//2
}
class Point {
var x;
var y;
}
注:使用 ?. 来代替 . , 可以避免因为左边对象可能为 null 导致的异常。
3.7.2 构造函数
构造函数是一种特殊的函数,主要用来在创建对象时初始化对象,即初始化对象成员变量,构造函数的函数名必须要与类名相同。
void main() {
Point p = new Point(1, 2);
print(p.x);//1
print(p.y);//2
}
class Point {
dynamic x;
dynamic y;
Point (int x, int y) {
this.x = x;
this.y = y;
}
}
3.7.3 继承类
和其他编程语言一样,Dart使用extends关键字来创建一个子类,使用super关键字来引用继承的父类。
void main() {
var student = new Student();
student.name = 'huahua';
student.study();//Student huahua is study...
student.run();//Student huahua is run...
}
//父类
class Person {
String name = 'huanhuan';
int age = 30;
void run() {
print('Person is run...');
}
}
//子类
class Student extends Person {
@override
void run() {
print('Student ${name} is run...');
}
void study() {
print('Student ${name} is study...');
}
}
3.7.4 抽象类
使用abstract修饰定义的类被称为抽象类,抽象类只能被继承,不能实例化。Dart的抽象类可以用来定义接口和部分接口实现,子类可以继承抽象类也可以实现抽象类接口。
void main() {
var dog = Dog();
dog.eat();//小狗在啃骨头...
dog.run();//小狗在跑...
dog.printInfo();//抽象类中的普通方法...
}
// 抽象类
abstract class Animal {
eat();
run();
printInfo(){
print('抽象类中的普通方法...');
}
}
//继承抽象类
class Dog extends Animal {
@override
eat(){
print('小狗在啃骨头...');
}
@override
run(){
print('小狗在跑...');
}
}
3.7.5 枚举类型
枚举类型是一种特殊的类,用来表示相同类型的一组常量值。枚举类型使用enum关键字进行定义,枚举类型中的每个值都有一个index的getter方法,用来标记元素在枚举类型中的位置。
void main() {
print(Color.red.index);//0
print(Color.green.index);//1
print(Color.blue.index);//2
}
enum Color {red, green, blue}
使用枚举类型的values常量可以获取所有枚举值列表。
void main() {
List<Color> colors = Color.values;
print(colors);//[Color.red, Color.green, Color.blue]
}
enum Color {red, green, blue}
注:在Dart中,枚举类型不能被子类化、继承或实现,不能被显示实例化。
3.7.6 Mixin
Mixin是复用类代码的一种途径,复用的类可以在不同层级,并且复用的类之间可以不存在任何继承关系。
Dart的Mixin相当于多继承,也就是说一个子类可以继承多个父类。Dart使用with关键字来实现Mixin的功能。
void main() {
var engineer = Engineer();
engineer.doWork();//工程师在写代码...
engineer.fixComputer();//工程师在修电脑...
}
class SoftEngineer {
void doWork() {
print('工程师在写代码...');
}
}
class HardEngineer {
void fixComputer() {
print('工程师在修电脑...');
}
}
class Engineer extends SoftEngineer with HardEngineer {
}
3.8 泛型
泛型的本质是数据类型的参数化,它给强类型编程语言增加了灵活性,并且使用泛型可以减少重复代码,提高代码的质量。
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
//可以通过创建一个带有泛型参数的接口来代替ObjectCache和StringCache接口
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
泛型可以用于集合中List、Set和Map类型的参数化。
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
在调用构造函数时,可以在类名字后面使用尖括号来指定泛型类型。
var nameSet = Set<String>.from(names);
在使用泛型的时候,也可以使用extends关键字来限定参数的类型。
void main() {
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
print(someBaseClassFoo);//Instance of 'Foo<SomeBaseClass>'
print(extenderFoo);//Instance of 'Foo<Extender>'
}
class SomeBaseClass {}
class Foo<T extends SomeBaseClass> {
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {}
除了作用于类外,还可以使用泛型来定义泛型方法。
void main() {
var names = <String>['huahua', 'huanhuan'];
print(printFirstName(names));//huahua
}
T printFirstName<T> (List<T> ts) {
T tmp = ts[0];
return tmp;
}
3.9 元数据
元数据又称中介数据、中继数据,主要用来描述数据的属性信息,如存储位置、历史数据、文件记录等。元数据注释以字符@开头,后接对编译时常量的引用或对常量构造函数的调用。
目前,Dart支持3种元数据注解:
@deprecated:用来表示被标注的元素已过时;
@override:用来表示需要覆盖父类方法;
@proxy:可以用来在编译时避免错误警告。
3.10 异步编程
Dart是目前少数几个支持异步编程的语音之一,可以使用异步函数或await表达式来实现异步编程。
异步函数指的是被async标记符标记的函数,该函数会返回Future对象。
void main() {
lookUpVersion() async => '1.0.0';
print(lookUpVersion());//Instance of 'Future<String>'
}
3.10.1 Future
在Java并发编程中,经常会使用Future来处理异步或延时任务,在Dart中通用也使用Future来处理异步任务。
默认情况下,Future执行的是一个事件队列任务,Future提供了多个API和特性来辅助任务队列的完成。
Dart的Future与JavaScript的Promise非常类似,主要用来处理异步任务的最终完成结果。异步任务处理成功就执行成功的操作,异步任务处理失败就捕获错误或停止后续操作。
在Dart中,常见的创建Future的函数有:
Future():默认构造函数,返回值可以是普通值或Future对象;
Future.microtask():将Future对象添加到异步任务队列;
Future.sync():创建一个同步任务,该任务会被立即执行;
Future.delayed():创建一个延时任务,延时不一定准确;
Future.error():创建一个Future对象,返回结果为错误。
在异步任务中,Future中的任务完成后需要添加一个回调函数,用于处理回调的结果,回调会被立即执行,不会被添加到事件队列。
如果要捕获异步任务的异常,可以使用catchError()。
then()也提供了一个可选参数onError来捕获异常。
如果要执行多个任务反馈执行的结果,可以使用Future.wait()。
3.10.2 async/await
使用async/await可以编写更简洁的异步代码,且不需要再调用Future的相关API。
3.10.3 Stream
除了Future外,Stream也可以用于接收异步事件数据。Stream除了可以接收单个异步事件数据外,还可以接收多个异步任务的结果。在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。
Stream常见的创建方式:
Stream<T>.fromFuture:接收一个Future对象来创建Stream;
Stream<T>.fromFutures:接收一个Future集合对象来创建Stream;
Stream<T>.fromIterable:接收一个集合对象来创建Stream;
Stream<T>.periodic:接收一个Duration对来创建Stream。
根据数据流监听器个数的不同,Stream数据流可以分为单订阅流和多订阅流。实际开发中,创建Stream数据流使用StreamController。
用StreamController创建单订阅流:
使用StreamController创建多订阅量可以直接创建或将单订阅流转成多订阅流。
import 'dart:async';
void main() {
//直接创建多订阅流
StreamController<String> s1 = StreamController.broadcast();
s1.stream.listen(
(data) => print(data),
onError: (error) => print(error.toString())
);
s1.stream.listen((data) => print(data));
s1.add('a');
//将单订阅流转成多订阅流
StreamController<String> s2 = StreamController();
Stream stream = s2.stream.asBroadcastStream();
stream.listen((data) => print(data));
stream.listen((data) => print(data));
s2.sink.add('a');
s2.close();
}
注:为了避免造成资源浪费,数据流用完后调用close()关闭Stream数据流。
在Dart中,Stream和Future是异步编程的两个核心API。Future用于处理异步或延迟任务等,返回值是一个Future对象。Future通常用于获取一次异步获得的数据,而Stream则可以通过多次触发成功或失败事件来获取数据。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有