Flutter - Dart语言

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中用finalconst做常量声明,被finalconst声明的变量值,无法被修改,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位双精度浮点型,同时intdouble都是num类型的子类,所以也可以写成num i = 0;
  • 字符串是一系列的字符文本,可以用''""表示,同时字符串支持${expression}表达式,在表达式中如果添加的是标识符,还可以忽略{}符号,并且字符串支持用+号或者'''换行拼接。
    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
    ''';
    
    需要注意的是,Dart下的数值在作为字符串使用是,是需要显示指定toString()的。比如: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语句支持Stringenumnum
  • 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);
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351