Flutter学习之Dart语言

441 阅读6分钟

前言

学习 Flutter 就必须先学习下 Dart 语言,如果会 Java 语言或者 Kotlin 语言,那么学起来就更简单了,它们语法有很多相似的地方,也有很多不同的地方,这里说说它们不同的地方。当然,也可以直接官网学习Dart学习指南

基本语法

  • 所有变量引用的都是对象,下面结合 java 和 kotlin 语法来学习下 dart 的区别
void main(){
  int a = 1;
  //错误,没有 long 类型,int 包括 long 类型
  //long a = 1;
  a = 1000000000000000000;
  double b = 2.0;
  //错误,没有 float 类型,double 包括 float 类型,
  // float a1 = 1;
  b = 1.012323232233333332332432423323223321322323323232233;
  // 如果需要bigDecimal运算,需要导包 https://pub.dev/packages/decimal
  //Decimal.parse('0.2') + Decimal.parse('0.1')
  //num 包括 int 和 double 类型,运行时类型
  num c = 1;
  c = 1.0;
  String d = "";
  //类型后面加?,代表可空,其他类型同理
  String? d1 = null;
  //三目运算法
  String? d2 = d1 == null ? "a" : null;
  //强制不为空
  String d3 = d2!;
  //null 也是对象
  Null e;
  //返回 true
  print(e == null);

  String f1 = "aa";
  String f2 = "aa";
  //true。比较两个字符串直接 ==
  print(f1 == f2);
  //错误,不能这样构建创建
  //String f3 = String("aa");

  String f4 = "aa";
  String f5 = "aa";
  //错误,没有equal
 // print(f4.equal(f2));
  //错误,没有 public、protected 和 private 成员访问限定符
 // public String g1 = "xx";
  //代表任意类型
  Object? h = null;
  var i = null;
  //判断是否是 null
  print(i == null);
  int? j = i??0;
  j??=4;
  List<int>? list = j == null ? null : [1,2,3];
  int add = 4;
  // 当list 为空的时候,不加入到list2里面,如果不为空时候则加入
  List<int> list2 = [...?list];
  print(list2);
 
  late String lateStr;
  lateStr = "xxx";
  
   int ss = 1;
   String intSS = 1.toString();
   
   int result = int.parse("1");
}

/*错误,没有 public、protected 和 private 成员访问限定符
public int add(){

}*/
//下划线命名表示私有
int _add(int? a,int? b){
  return a??0 + (b??0);
}

总结下:

  1. 和 java 一样语句需要逗号结尾,和 kotlin 不一样,这还是有些遗憾
  2. 所有引用类型皆对象,没有基本数据类型
  3. 去除了 long 和 float 类型,增加了 num 类型,涵盖了 int 和 float 类型
  4. 和 kotlin 一样,类型具有有空安全功能,在申明和引用的时候加 ? 来表示,如 e5?.foo()
  5. 字符串判断直接用 == ,没有 equal,和 kotlin 一致
  6. 去除了成员访问限定符,改用命名下划线的方式限定
  7. 增加了 e2 ?? e3,语法,e2 不为空则为 e2,e2为空的时候设置为 e3,类型 kotlin 的 ?:
  8. 增加了 x ??= e4 语法,x 不为空时候为 x,否则 x = e4
  9. 增加了 [...? e6] 语法,如果 e6 不为空的时候,则加入到集合里面,如果为空则不加入
  10. 增加了 Null 类型,对应着 null 的对象类
  11. 增加了 late 关键词,类型 kotlin 的 lateinit 的用法,必须初始化再使用,否则会抛出异常。
  12. int 转换为 string ,如 1.toString(),String 转换为 int,int.parse("1"),其他类似

final 和 const 的区别

接着说说 Dart 语言里面的 final 和 const 的区别

void main(){
  const int a = 1;
  final int b = 1;
  //报错
 // a = 2;
  //报错
  //b = 2;
 
  // int c = 1;
  //错误
  // const int d = c
  const int c = 1;
  const int d = c;

  const String conStr = "aaa";
  final String finalStr = "aaa";
    
  //如果 Person 变量都是 const 或者 final 不报错,否则报错类
  const Person conStr1 = Person();
  //报错
  //const Person conStr1 = new Person();
  //不报错
  final Person finalStr1 = Person();
  //报错
  //final const conStr2 =  getHello();
  //不报错
 // final String finalStr2 =  getHello();
}

String getHello(){
  return "hello, dart";
}

class Person {
  static const String TAG = "Person";
  final String finalTAG = "Person";
  //报错,没加final
  // String name = "xx";

  const Person();
}

总结下:

相同点

两者定义时候必须初始化,且初始化后的值不可变。

不同点

  1. const 修饰的是编译时常量,可以修饰字符串,数字,不可修饰变量和实例,final 修饰的是运行时常量,可以是对象,字符串,数字等
  2. final 只可用来修饰变量, const 可修饰变量和常量构造函数,当 const 修饰 变量时候需要 加 static 修饰,当 const 修饰类的构造函数时,它要求该类的所有成员都必须是 final 或 const 的。

扩展操作符(...)和空感知扩展操作符(...?)

在 Dart 里面这种操作符会经常使用到,上面也提到过,这里举几个例子看看

void main(){
  var list = [1,2,3];
  var list2 = [0,...list];
  print(list2);

  var list3 = ["x","2"];
  var list4 = [0,...list3];
  print(list4);

  int a = 1;
  var list5 = a == 0 ? null : [1];
  var list6 = [...?list5];
  print(list6);

  var list7 = [1,2];
  //这样list8 会是一个二级数组,应该使用 ...list7 ,才能把 list7 加到 list8 集合里面
  var list8 = [list7];
  var list9 = [...list7];
  print(list8);
  print(list9);
  
  var b = 1;
  var c = 2;
  //错误
  // var list10 = [...b,...c];

}

如果操作符右边是非空变量使用...,如果是可空变量,则使用 ...?,并且操作符右边必须是一个实现 Iterable 的实例。

集合

  • List Dart 里面的 list 和 Java 语言的集合还是有点不同,它支持集合中的 if 和 for 操作
import 'dart:math';

void main(){
  var a = Random().nextInt(2);
  print(a);
  var list = [1,2,3,if(a == 1) 4];
  print(list);

  var list2 = [0,0,0,for(var i in list) i];
  print(list2);
}
  • Map

Map 新增一种初始化的方式

void main(){
  var info = {
    "peter":"23",
    "baby":"21",
    "gigi":"24",
  };
  print(info);
  info["gigi"] = "25";
  print(info);
}

通过一对 map 字面量来创建。

Symbols

在标识符前加 # 前缀来获取 Symbol,当代码压缩后,标识符的名称会改变,但是它们的 Symbol 会保持不变。所以可以用于根据名称获取标识符的情况。

函数

void main(){
  show(true,true);
  show2(true,true);
  show3(true);
  show3(true,true);
  show3(true,true,true);
  show4(true);
  print("show4 is null ${show5() == null}");
}
/**
 * 两个参数必传
 */
void show(bool isTop,bool isClick){
  print("show---$isTop");
  print("show---$isClick");
}
/**
 * 和 kotlin 不一样,这里两个参数也是必传,只是参数是可空类型
 */
void show2(bool? isTop,bool? isClick){
  print("show2---$isTop");
  print("show2---$isClick");
}
/**
 * 可选参数通过 [] 包裹添加
 */
void show3(bool? isTop,[bool? isClick,bool? isEnable]){
  print("show3---$isTop");
  print("show3---$isClick");
  print("show3---$isEnable");
}

/**
 * 使用 = 给参数默认值,默认值必须是常量
 */
void show4(bool? isTop,[bool? isClick = false,bool? isEnable = false]){
  print("show4---$isTop");
  print("show4---$isClick");
  print("show4---$isEnable");
}
/**
 * 不使用 void 返回值时候,默认后面会 return null
 */
show5(){

}

以上就是 Dart 函数的使用,可选参数通过 [] 包裹,如果函数加 void 代表没有返回值,如果省略,默认返回 null ,如果加了 return 语句,则以 return 语句类型为准。

级联运算符

运算符为 (..)(?..),注意要和扩展操作符区分,使用方法如下

void main() {
  var person = Person()
    ..name = "GiGi"
    ..age = 12;
}

class Person {
  var name;
  var age;
}

类似构建者模式,和 kotlin 的 apply 关键词用法类型。

异常

Dart 异常增加了 on 关键词

void main() {
  try {
    var a = int.parse("x");
  } on FormatException {
  } catch (e) {

  } finally {
    print("end");
  }

  try {
    var a = int.parse("x");
  } on FormatException {
    rethrow;
  } finally {
    print("end");
  }
}

指定异常类型时候使用 on ,多个异常时候使用 catch。如果需要再次抛出异常则通过 rethrow 关键词来实现。

  • 获取对象的类型

Dart 语言中,通过 runtimeType 属性来获取对象的类型,比如

void main() {
  var list = {"1"};
  //输出 _CompactLinkedHashSet<String>
  print(list.runtimeType);
}
  • 类的实例变量

除了 const 和 final 的变量都会隐式的申明一个 Setter 和 Getter 方法,late final 修饰的变量如果没有初始化之前也会声明一个隐式的 Setter 方法。如

void main() {
  var car = Car("BMW",500000);
  var type = car.type;
  var price = car._price;
  print("$type");
  print("$price");
}

class Car {
  var type;
  var _price;

  /**
   * this 的方式赋值,语法糖简化操作代码
   */
  Car(this.type, this._price);
}
  • 常量构造函数
class Constant {
  static const String version = "1.0";
  final String name = "app";
  //常量构造函数
  const Constant();
}

常量构造函数里面的所有变量都需要是 final 类型.

  • 工厂构造函数

主要通过 factory 关键词来实现,如

void main() {
  var car1 = Car(20000);
  print("type:${car1.type} price:${car1._price}");
  var car2 = Car(100000);
  print("type:${car2.type} price:${car2._price}");
  var car3 = Car(20000);
  print("car3:${car3 == car1}");
}

class Car {
  var type;
  var _price;

  static final Map<String, Car> _cache = <String, Car>{}; 

  Car._internal(this.type, this._price);

  /**
   * 并不一定返回新的对象
   */
  factory Car(int money){
    if(money >= 50000){
      return _cache.putIfAbsent("BMW", () => Car._internal("BMW",500000));
    }
    return _cache.putIfAbsent("BYD", () => Car._internal("BMW",200000));
  }
}

注意的一点就是 factory 返回的对象并不一定是新的对象。

  • 抽象方法
abstract class Person{
  void name();
}

抽象方法必须要在抽象类里面申明,直接使用分号(;)替代方法体即可声明一个抽象方法,不需要再用 abstract 修饰。

  • 隐式接口

Dart 语言里面,类即是接口,每个类都实现了一个隐式的接口,这个接口包含这个类的所有实例成员以及这个类所实现的其它接口。如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。例如老婆的资产等于老公的资产+自己的,就可以这样写

void main(){
  var wife = Wife();
  print("wife:${wife.money}");
}

class Husband{
  var money  = 100;
}

class Wife implements Husband {
  @override
  int money = 100 + 10000;

}
  • noSuchMethod 方法

这个方法需要配合 dynamic 关键词来使用,使用如下

void main(){
  dynamic person = Person();
  print("${person.age}");
  person.say();
}

class Person{
  @override
  noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: '
        '${invocation.memberName}');
  }
}

当返回类型是 dynamic 类型,如果访问类不存在的方法或者属性,则会执行 noSuchMethod 方法,我们可以在里面做一些处理操作。

  • Mixin

Mixin 是通过多重继承中复用某个类中代码的方法模式。

使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式,使用如下

void main(){
  var g = GiGi();
  g.say();
}

class Mother {
  void say1(){
    print("Mother");
  }
}

class Teacher {
  void say2(){
    print("Teacher");
  }
}

class Daughter {
  void say3(){
    print("Daughter");
  }
}

class GiGi extends Mother with Teacher,Daughter {
  void say(){
    say1();
    say2();
    say3();
  }
}

库和可见性

Dart 语言不在以 public,protected,private 做为成员的限定符了,而是以库来限定,如果以下划线(_)开头命名的成员只能在库里面访问,每一个 dart 程序都是一个库。我们使用库的方式通过 import 导入的方式,如果是 dart:xxx 格式的则代表内置库,如果以package:xxx 格式的则为包管理器管理的。

  • 指定库前缀

如果 lib1 和 lib2 有冲突的标识符,则可以通过 as 的方式指定前缀,使用的时候带上前缀即可访问

import 'package:lib2/lib2.dart' as lib2;

lib2.Element element2 = lib2.Element();
  • 导入库的一部分
//只导入 foo
import 'package:lib1/lib1.dart' show foo;
//除了 foo 全部导入
import 'package:lib2/lib2.dart' hide foo;

  • 延时加载库
//延时加载代码库的用法
import 'package:greetings/hello.dart' deferred as hello;

使用的时候

Future<void> greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

await 等待库加载完成,这里即使 loadLibrary 执行多次库也只加载一次。

异步

在 Dart 语言中,通过 await 和 async 来实现异步编程。例如

void main() async{
  var result = await download();
  print(result);
}

download(){
  sleep(Duration(seconds: 1));
  return "ok";
}

这里结合 kotlin 协程来理解就好理解些,用法类似。

生成器

当我们需要延时的生成一串数据时候,我们可以使用 Dart 生成器来生成

同步生成器

void main(){
  print(naturalsTo(100));
}


Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

异步生成器,它返回的是一个 Stream,需要通过 await for 来接收

void main() async {
  var result = asynchronousNaturalsTo(10);
  await for (final request in result) {
    print(request);
  }
}

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

call 方法(可调用类)

通过实现类的 call() 方法,允许使用类似函数调用的方式来使用该类的实例。例如

void main(){
  var call = CallTest();
  call("ok");
}

class CallTest {
  void call(String info){
    print("执行成功$info");
  }
}

注解

Dart 的注解声明如下

class Deprecated {

    final String message;
    const Deprecated(this.message);

}

通过常量构造器方式实现。然后使用的时候类似这样:

@Deprecated('Use `message` instead. Will be removed in Dart 3.0.0')

至此,Dart 语言基础先学习到这里,还有很多知识,后续使用到时候再进一步学习。总体来说入门还是挺简单的。