Dart 语言是Flutter的特色之一,学习过JavaScript、Java或者Kotlin的开发者,在学习Dart上几乎没什么难度。Dart语言本身的上手难度也不高,它综合了动态语言和静态语言的一些特性,属于伪动态语言。Dart虽然是一种面向对象的语言,但是也支持函数式编程
基础语法
- Dart不像Java等语言,它没有public、private等关键词用于表示作用域的声明,而是以“_”符号开头的方法或者成员变量表示私有,通过@protected注解表示调用保护的作用。带有@protected注解的方法,如果在其他路径下被外部调用,会有“The member ‘functionName’ can only be used within instance members of subclasses of 'package:xxxx/xxxx/xxx.dart' .”的警告。
- Dart作为面向对象的语言,所有的成员都是对象,包括数字、接口、函数等都继承自Object对象,默认值为null。
setter/getter
- Dart不需要给变量设置setter/getter方法,因为Dart中的所有基础类型、类等都继承自Object,而Object自带setter/getter方法。
- 如果声明中带有
final
或者const
时,那么它只有一个getter方法。 - Dart在需要的时候还可以重载setter/getter。下面的代码定义了一个私有的_ratio成员变量,然后对外暴露公开 ratio变量,通过重写公开的ratio变量的set/get方法,就可以自定义私有_ratio变量的对外行为。既可以保护内部私有_ratio变量不暴露,又可以再公开的ratio变量被调用时,通过set/get方法做自定义判断处理。
double _ratio; double get ratio => _ratio; set ratio(double value) { if (_ratio != value) { _ratio = value; } } changeValue() { /// read 默认调用get var data = ratio; /// write 默认调用set ratio = data + 1; }
final/const
- Dart中用
final
和const
做常量声明,被final
和const
声明的变量值,无法被修改,const
用于表示编译时常量,比如final name = 'jack'; const value = 1000000;
。 - 同时,
static const
组合代表了静态常量,其中const
的值在编译期确定,final
的值要到运行时才能确定。
import
- import关键字用于实现类导入,如果将material相关的控件导入当前dart文件中,同时还阔以用“as”增加导入的自定义别名:
import 'package:flutter/material.dart';
、import 'dart:math' as math;
基础数据类型
- Dart中主要支持数字、字符串、布尔、数组(列表)、集合映射等数据类型,并且美中数据类型都包含内置的封装方法。
- 数字类型在Dart中包括整型和浮点型,不如
int i = 0; double y = 20.1;
。其中int
类型的取值范围为-2^53 ~ 2^53
,double
类型为64位双精度浮点型,同时int
和double
都是num
类型的子类,所以也可以写成num i = 0;
。 - 字符串是一系列的字符文本,可以用
''
、""
表示,同时字符串支持${expression}
表达式,在表达式中如果添加的是标识符,还可以忽略{}
符号,并且字符串支持用+
号或者'''
换行拼接。
需要注意的是,Dart下的数值在作为字符串使用是,是需要显示指定toString()的。比如:String name = 'jack'; String title = 'title'; int i = 13; String content = "hello world $name ${i/2}"; String a = 'title'; String b = 'name'; String c = a + b; String d = ''' hello world I'm jack ''';
int i = 0; print('aaaa' + i);
这样的使用是不支持的,需要使用print('aaaa' + i.toString());
当然,如果是${expression}
表达式,默认会对参数调用toString()
方法。 - 布尔类型在Dart中表示为
bool
,常用语if判断、assert断言等地方,并且if语句只支持bool类型,不支持直接使用空数据判断,例如:String g = getValue(); /// 不合法判断 if (g) {} /// 合法判断 if (g != null) {}
- 列表类型在Dart中也是数组,用
List
或者[]
表示,列表允许存在重复的元素,允许任意数量的空值,每个元素顺序插入。同时在Dart 2.3 中引入了扩展运算符...
和空值判断扩展运算符...?
List list1 = [1, 2, 3, 4]; List list2 = [-1, 0, ...list1]; var list3 = null; /// 填充时有?做判空判断 var list4 = [10, 11, ...?list3];
- 集合类型在Dart中用
Set
或者{}
表示,和列表不同的是,Set
不允许存在重复的元素,Set
最多允许一个空值,且Set
中的元素都是无序的。 - 映射类型在Dart中用
Map
或者{}
表示,Map
是以键值对形式存储元素的,对Map
而言,key
是唯一的,而value
可以重复。 - Dart还支持枚举类型,可以通过
enum
生命枚举enum UserType { guest, vip, }
逻辑语句与操作符
- Dart支持
if else switch while
等常规逻辑语句。 -
if
语句只支持bool
类型,switch
语句支持String
、enum
、num
。 - Dart还支持其他丰富的操作符,比如级联操作符,通过级联操作符可以对类的内部成员进行链式调用
Event event = Event(); event ..id = 1 ..type = '' ..actor = '';
- 赋值操作符可以让开发者减少一些逻辑判断代码。赋值操作符
??
、??=
、~/
及判空操作符?
AA ?? '999' /// 表示如果AA为空,则返回 999 AA ??= '999' /// 表示如果AA为空,则将AA设置为999 AA ~/ 999 /// AA对于999整除 Event event = Event(); event?.fix(); /// 如果event为空,则不执行 fix
- Dart还可以对某些操作符进行重载,如下面代码所示,Vector通过对操作符进行自定义,实现了Vector类的加减功能。
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); } void main() { final v = Vector(2, 3); final w = Vector(2, 2); assert(v + w == Vector(4, 5)); assert(v - w == Vector(0, 1)); }
var与dynamic
- Dart属于强类型语言,但可以用var声明变量,Dart对于var声明会自推导出数据类型
- 实际上var是编译期的语法糖,而dynamic声明才表示动态类型,dynamic被编译后是一个object类型,在编译期间不对任何的类型进行检查,而是在运行时对类型进行检查。
var i; /// 当var声明时,未指定类型和赋值,默认是dynamic init() { i = ''; /// 自推导出i为String类型 i++; /// 运行时报错 "int is not a subtype of type String" print(i); } init2() { var j = ''; /// 当var声明时,赋值后,会自推导出其类型,当前为String j++; /// 编译器会报错 print(j); }
- 所以,在Dart中药十分注意它属于强类型语言,并且var并不是真正的动态声明,dynamic才是。
函数方法
- Dart的方法可以设置参数默认值和指定名称
- 方法的参数类型,在声明时可以指定或者不指定
- 方法还可以被当做参数船体,这样的实现形式可更灵活地组织闭包代码
getRepositoryDetailDao(String userName, reposName, {branch = 'master'}) { } /// branch 可以不用设置 getRepositoryDetailDao('aaa', 'cccc'); /// branch 设置为dev getRepositoryDetailDao('aaa', 'cccc', branch: 'dev'); /// 方法当做参数传递 doWhat(String name) { return 'this $name'; } doNext(int data) { print('### $data'); } doSomeThing(doWhat, doNext); doSomeThing(String doWhat(String name), void doNext(int data)) { var result = doWhat('jack'); print(result); doNext(10); }
类、接口和继承
- Dart 中没有接口关键字,类都可以作为接口,把某个类当做接口实现时,只需要使用implements继承实现,然后复写父类方法,implements可以实现多个接口
abstract class Interface { void doA(); void doB(); } class InterfaceClass { void doC() {}; void doD() {}; } class Name implements Interface, InterfaceClass { @override void doA() { } @override void doB() { } @override void doC() { } @override void doD() { } }
mixins
- Dart支持混入的模式,混入时的基础顺序,是从右到左一次执行,而且和super方法是否执行有关
- 当多种关键字同时实现时,按照出现顺序应该为 extends(继承)、mixins、implements,事实上,Flutter的启动类就是使用mixins方式实现的。
- 下面举个例子帮助理解,代码如下:
abstract class Base { a() { print('base a()'); } b() { print('base b()'); } c() { print('base c()'); } } class A extends Base { a() { print('A.a()'); // super.a(); } b() { print('A.b()'); super.b(); } } class A2 extends Base { a() { print('A2.a()'); super.a(); } } class B extends Base { a() { print('B.a()'); super.a(); } b() { print('B.b()'); super.b(); } c() { print('B.c()'); super.c(); } } class G extends B with A, A2 {} testMixins() { G g = new G(); g.a(); g.b(); g.c(); } // A2.a() // A.a() // B.a() // base a() // A.b() // B.b() // base b() // B.c() // base c()
- 以上代码实现的功能如下:
- G继承了B,然后混入了A和A2;
- B继承了Base的方法,并重载了a、b、c三个方法;
- A2同样继承了Base,但是只重载了a方法;
- A同样继承了Base,并且只重载了a、b两个方法,但是在a方法中没有super
- 从最终输出结果可以看到:
- 在执行g.a()方法是,输出是从最右边的A2.a()开始的,之后才是A.a()、B.a()、base a();
- 在执行g.b()方法时,因为A2没有b()方法存在,所以按照顺序,只能输出了A.b()、B.b()、base b();
- 在执行g.c()方法时,因为A2和A都没有实现c方法,所以只输出了B.c()和base c();
构造方法
- Dart支持多个构造方法,在下面的代码中,ModelA默认构造方法只能有一个,可以通过“this.name, this.tag”实现成员变量的初始化;而通过ModelA.empty()方法,可以自定义一个空参数的构造方法;其他构造方法,都是通过“类型.xxx()”的模式实现。
- 构造方法中,也支持
{}
可选配置,比如ModelA.forOther方法class ModelA { String name; String tag; /// 默认狗仔方法,赋值给name和tag ModelA(this.name, this.tag); /// 返回一个空的ModelA ModelA.empty(); /// 返回一个设置了name的ModelA ModelA.forName(this.name); /// 返回一个带默认参数的ModelA ModelA.forOther({this.name = '', this.tag = ''}); }
异常处理
- Dart中遇到错误时会抛出异常,每个异常都是类
Exception
的子类,开发者可以通过throw
主动排除异常,并且可以通过try/catch
实现异常捕获,也可以继承Exception
实现自定义的异常抛出。 - 代码
on
可以指定需要catch的异常类型,若直接使用Exception
表示catch
全部,最后可以再finally
里做异常处理的退出处理try { } on FormatException catch (e) { // 当字符串或者某些其他数据没有预期格式且无法解析或者处理时,抛出异常 } on IntegerDivisionByZeroException catch (e) { // 除以0的错误 } finally { }
Isolate
- Dart在单线程模式中,增加了Isolate提供跨线程的真异步操作。
- 因为在Dart中线程不会共享内存,所以也不存在死锁,从而也导致了Isolate之间的数据智能通过port的端口方式发送接口,所以Isolate也称为隔离的执行。
- 同时,Dart还提供了compute的封装接口,方便调用Isolate的快速使用。
- Isolate主要在
import 'dart:isolate'
包中,其中:- Isolate用于Dart执行上下文隔离;
- ReceivePort与SendPort一起,是唯一的通信方式;
- SendPort将消息发送到其他ReceivePort。
- 在下面代码中,spawn帮助简化了Isolate的创建过程,通过ReceivePort创建后可以用于listen监听数据,而Isolate.spawn通过echoResult的异步执行逻辑,可以再echoResult中通过sendPort将数据发送回来。
Isolate isolate; void doIsolate() async { var receive = ReceivePort(); isolate = await Isolate.spawn(echoResult, receive.sendPort); receive.listen((message) { print('Receive: $message'); }) } void echoResult(SendPort port) { final msg = 'do you know me'; Timer.periodic(const Duration(seconds: 1), (_) { port.send(msg); }) } void kill() { isolate?.kill(priority: Isolate.immediate); isolate = null; }
- Dart还提供了简化版的处理方法
compute
。compute方法通过外部传入方法和数据,然后执行得到返回结果。需要注意的是,compute中运行的方法必须是顶级方法或者static方法,比如decodeListResult必须是全局方法或者static方法List<dynamic> eventMap = await compute(CodeUtils.decodeListResult, data as String);
Zone
- Dart可通过Zone表示指定的代码执行环境,类似一个代码运行沙盒的概念。
- 在Flutter中,C++运行Dart也是通过在_runMainZoned方法内执行runZoned方法来启动Dart程序的。同样在开发过程中,也可以通过Zone指定环境运行,下面的代码就是利用runZoned在运行环境内捕获全局异常等信息。
runZoned(() { runApp(FlutterReduxApp()); }, onError: (Objects obj, StackTrace stack) { print(obj); print(stack); });
- 另外,也可以给runZoned注册方法,在需要时执行回调,例如下面代码所示,在一个Zone内任何地方,只要能获取onData这个ZoneUnaryCallback,就都可以调用到handleData.
/// 最终需要处理的地方 handleData(result) { print(result); } /// 返回得到一个ZoneUnaryCallback var onData = Zone.current.registerUnaryCallback<dynamic, int> (handleData); /// 执行ZoneUnaryCallback 返回数据 Zone.current.runUnary(onData, 2);
- 异步逻辑同样可以通过Zone的scheduleMicrotask方法插入异步执行,例如下面的代码.关于Zone相关的概念在后续张杰中还会详细介绍
Zone.current.scheduleMicrotask(() { // todo something });
异步执行
- 在Flutter中支持 async/await表示异步执行,如下面代码所示,request方法和doSomething方法都是异步执行,这里的异步有别与Isolate执行,属于“假异步”的实现逻辑。
- 在Dart中,通过单线程的任务调度实现而不是并发实现,具体实现后续张杰会详细介绍。这里只需要知道的是async/await是语法糖,最终还是通过编译器转为Future对象执行,而Future对象除了await等待异步结果,还可以通过then链式回调执行下一步。
///模拟等待两秒,返回OK request() async { await Future.delayed(Duration(seconds: 1)); return "ok!"; } ///得到"ok!"后,将"ok!"修改为"ok from request" doSomething() async { String data = await request(); data = "ok from request"; return data; } ///打印结果 renderSome() { doSomething().then((value) { print(value); ///输出ok from request }); }
- 简单地说,Future就是对Zone的封装使用,如下面代码所示,Future.microtask中主要是执行了Zone.scheduleMicrotask方法,而result._complete最后调用的是_zone.runUnary等方法(关于runUnary相关的内容,后续讲解Stream的张杰会详细介绍)。
factory Future.microtask(FutureOr<T> computation()) { _Future<T> result = new _Future<T>(); scheduleMicrotask(() { try { result._complete(computation()); } catch (e, s) { _completeWithErrorCallback(result, e, s); } }); return result; }
- 所以,Dart可以通过async/await实现异步执行逻辑,也可以通过Future实现医院的操作,并且await关键字智能在async中配对使用,他们具体的实现逻辑后续张杰会详细分析,这里暂时知道他们的使用方法即可。
- Dart还有另外一种异步操作:async */yield或者Stream。async */ yield 也只是语法糖,最终通过编译期转为Stream实现处理。但是Stream除了异步执行,还支持同步操作,事实上Stream不是单纯用于异步逻辑,而是事件流处理模型。
- Stream 中主要有StreamController、StreamSink、Stream和StreamSubscription四个关键对象,大致总结如下:
- StreamController:如类名描述,用于整个Stream过程的控制,提供各类接口用于创建各种事件流。
- StreamSink:一般作为时间的入口,提供如add和addStream等操作
- Stream:事件源本身,一般可用于监听时间或者对事件进行转换,如listen和where等操作。
- StreamSubscription:时间订阅后的对象,表面上用于管理订阅等各类操作,如cancel和pause等,同时在内部也是事件中转的关键。
- 一般开发中会通过StreamController创建Stream,通过StreamSink添加事件,通过Stream监听时间,通过StreamSubscription管理订阅。另外Stream中支持各种变化,比如map、expand、where和take等操作,还支持转换为Future执行返回结果。
- 下面的代码展示了如何使用Stream封装出简单的EventBus逻辑。EventBus主要利用了StreamController创建出Stream对象,之后通过Stream流对HttpErrorEvent事件进行监听,然后利用fire方法将事件发送到对应的监听上进行处理。另外,对于事件还可以做where、take等二次变换,这里的具体使用和操作逻辑,在后续篇章中会详细介绍,可以暂时先了解Stream的基础概念。
class EventBus { StreamController _streamController; StreamController get streamController => _streamController; EventBus({boos sync = false}) : _streamController = StreamController.broadcast(sync: sync); EventBus.customController(StreamController controller) : _streamController = controller; Stream<T> on<T>() { if (T == dynamic) { return streamController.stream; } else { return streamController.stream.where((event) => event is T).cast<T>(); } } void fire(event) { streamController.add(event); } void destroy() { _streamController.close(); } } EventBus eventBus = new EventBus(); eventBus.on<HttpErrorEvent>().listen((event) { errorHandleFunction(event.code, event.message); }) eventBus.fire(new HttpErrorEvent(code, message));
拓展方法
- Dart2.7还支持拓展方法,它使用户可以向任何类型(甚至是无法控制的类型)添加新功能,并具有常规方法调用的简洁性和自动完成性,例如下面的代码:
extension ExtendsFun on String { int parseInt() { return int.parse(this); } double parseDouble() { return double.parse(this); } } main() { int i = '42'.parseInt(); print(i); }