Dart 学习笔记

255 阅读17分钟

前言

本文并不会罗列所有 Dart 语言的细节,本文是从一个前端工程师的角度来总结 Dart 语言相对于 JavaScriptTypeScript 的一些特性或者不同点,如果没讲的,那基本上就是和 TS(JS) 特性相同,默认读者是有 JavaScriptTypeScript 基础的。如果没有这两门语言基础,建议去看其他角度讲解 Dart 语言的文档或者官方文档

基础用法上的区别点

main 方法

程序的地点必须是一个 main 方法。

void main() {
  // ...
}

打印与分号

使用 print 函数打印内容到控制台。Dart 中每语句结束都需要加分号。

print("xxxxxx");

数据类型及 is 关键字

  • var 可以定义任意类型的变量,类型可变
  • bool 布尔类型
  • int 整数类型
  • double 浮点数类型
  • String 字符串类型
  • List 列表类型,类似数组
  • Map 对象类型,类似JS里面的Map
  • Set 集合类型,类似 JS 里面的Set
  • Records 记录类型
void main() {
  var a = 'a';
  bool b = true;
  int c = 1;
  double d = 1.0;
  String e = "e";

  List f = <int>[1, 2, 3]; // int 也可以不写
  List<String> h = ['1'];
  List<int> i = [1];
  var j = List<String>.filled(2, 'j'); // ["j", "j"]

  var k = {"key": "value"};
  print(k["key"]); // "value"
  var l = new Map();
  l["key"] = 123;
    
  var m = Set();
  m.addAll({'a', 'b', 'b', 'c'});
  print(m); // {a, b, c}
    
  var record = ('first', a: 2, b: true, 'last');
  print(record.$1); // first
  print(record.$2); // last
  print(record.a); // 2
  print(record.b); // true
}

不常用类型:

  • Runes
  • Symbol
void main() {
  const string = 'Dart';
  final runes = string.runes.toList();
  print(runes); // [68, 97, 114, 116]

  const emojiHeart = '♥';
  print(emojiHeart.runes); // (9829)

  Symbol obj = Symbol('name');
  print(obj); // Symbol("name")
}

我们可以使用 is 关键字来判断数据类型,也可以用于判断是不是某个类。

void main() {
  var a = null;
  
  if (a is String) {
    print('String');
  } else {
    print('null'); // null
  }
}

类型别名

typedef IntList = List<int>;
IntList il = [1, 2, 3];

类型转换

字符串转数字

void main() {
  String a = '100';
  String b = '100.123';

  print(int.parse(a)); // 100
  print(double.parse(b)); // 100.123
}
void main() {
  String a = '';
  String b = '';

  print(int.parse(a)); // 报错
  print(double.parse(b)); // 报错
}

上述错误可以用 try...catch 语法解决。

数字转字符串

void main() {
  var a = 123;
  var b = a.toString();
  print(b); // 123
}

字符串判断是否为空

void main() {
  var a = '';
  var b = 'b';
  print(a.isEmpty); // true
  print(b.isEmpty); // false
}

字符串插值、拼接

可以直接使用 + 拼接,也可以使用字符串插值,类似 JS/TS 的模板字符串。

void main() {
  var a = 'a';
  var b = 'b';

  print('$a - $b');
  print("$a - $b");
  print('${a} - ${b}');
}

注意上面的 {} 是非必须的,但是在某些情况下必须加,比如在类里面使用某个成员变量 this.xxxx 就必须用 {} 阔起来。

运算符

算术运算符——取整

void main() {
  var a = 100;
  var b = 7;

  print(a / b); // 14.285714285714286
  print(a ~/ b); // 14
}

关系运算符

==!=,不存在三等于的情况。

赋值运算符

??= 前面为空的时候,使用后面的值给前面赋值

void main() {
  var a = 100;
  a ??= 10;
  var b;
  b ??= 10;

  print(a); // 100
  print(b); // 10
}

~/= 取整后赋值

void main() {
  var a = 100;
  var b = 6;

  print(a ~/= b); // 16
}

循环语言

do while

void main() {
  int i = 1;
  int sum = 0;

  do {
    sum += i;
    i++;
  } while (i <= 100);

  print(sum); // 5050
}

函数

函数的参数类型及返回值类型

函数的参数类型,可以指定,也可以不指定,返回值也是,可以指定可以不指定。

myfunc(param) {
  return param;
}
int myfunc(int param) {
  return param;
}

函数的可选参数和参数默认值

将可选参数使用中括号括起来,不传的时候是个空值所有 int 后面需要加一个 ?,这样数据类型才正确。

int myfunc(String param1, [int? param2]) {
  if (param2 == null) {
    return 20;
  }
  return param2;
}
void main() {
  print(myfunc('')); // 20
  print(myfunc('', 22)); // 22
}

参数默认值,一般放在可选参数前,也要放在中括号里面。

int myfunc(String param1, [String param2 = 'param2', int? param3]) {
  print(param2);
  if (param3 == null) {
    return 20;
  }
  return param3;
}
void main() {
  print(myfunc('')); // param2 20
  print(myfunc('', 'xxxx', 22)); // xxxx 22
}

函数的命名参数

调用函数传参时需要知道参数的名称,一并传入,才能完成赋值。

// required 代表该参数是必须要要传的,如果写 int? 就代表是可选参数
int myfunc({required int param1, required int param2}) {
  print(param2);
  return param1;
}
void main() {
  print(myfunc(param2: 123, param1: 234)); // 123 234
}

匿名函数

var printNum = (int n) {
  if (n > 10) {
    print(n);
  } else {
    print(n + 10);
  }
};

void main() {
  printNum(5); // 15
  printNum(50); // 50
}

箭头函数

比如,我们现在要打印一个列表的数据。

常规写法(匿名函数):

void main() {
  List list = ['Apple', 'Banana', 'Peach'];

  list.forEach((element) { // 这里和JS写法比,少了一个function
    print(element);
  });
}

箭头函数写法:箭头函数之后只能写一个语句,也不能写分号。

void main() {
  List list = ['Apple', 'Banana', 'Peach'];

  list.forEach((element) => print(element)); // print 这里也能用 {} 括起来
}

自执行函数

void main() {
  (() {
    print("我是自执行函数");
  })();

  ((int n) {
    print(n); // 123
  })(123);
}

Dart 所有的东西都是对象,所有的对象都继承自 Object 类。

Dart 是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是 Object 的子类。

最基本的类定义和实例化

类定义不能定义在 main 函数中。一般来说一个文件写一个类,要用的时候再使用 import 导入即可。

可以使用 new 关键字实例化类,也可以将其省略,都是一样的效果。

class Person {
  int age = 18;
  String name = 'YY';

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

void main() {
  var p1 = new Person();
  p1.getInfo(); // YY---18

  var p2 = Person();
  p2.getInfo(); // YY---18
}

默认构造函数

默认构造函数,名称与类名一样,且默认在实例化的时候执行。

class Person {
  // late 表示定义的时候不用初始化,创建实例的时候再去初始化
  late int age;
  late String name;

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

void main() {
  var p1 = new Person('ABC', 20);
  p1.getInfo(); // ABC---20

  var p2 = Person('CBA', 22);
  p2.getInfo(); // CBA---22
}

对默认构造函数进行简写

class Person {
  int age;
  String name;

  Person(this.name, this.age);

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

命名构造函数

构造函数可以写多个,我们可以选择性的使用构造函数,比如下面我就创建了一个名称为 now 的构造函数,并用他实例化了一个类。

class Person {
  late int age;
  late String name;

  Person(this.name, this.age);

  Person.now() {
    print("我是命名构造函数");
  }

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

void main() {
  var p2 = Person.now(); // 我是命名构造函数
}

factory 构造函数

在创建类实例的时候,如果不想每次 new 都创建一个新的实例 ,那么 使用 factory 关键字修饰构造函数。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

void main() {
  var logger1 = Logger('UI');

  var logger2 = Logger('UI2');

  var logger3 = Logger('UI');

  // identical 用于判断两个对象是否相等
  print(identical(logger1, logger2)); // false
  print(identical(logger1, logger3)); // true
}

访问修饰符

Dart 里面没有 publicprivateprotect 这样的访问修饰符。

Dart 里面使用 _ 开头来命名确定成员是否为私有成员。

不过有一点值得注意的是,如果类和实例化放在一个文件里面,那么你的私有成员的私有化是不生效的,下面看个例子,这个例子里面的私有成员是可以被调用和打印出来的,而且不会报错。

class Person {
  late int age;
  late String name;
  int _height = 170;

  Person(this.name, this.age);

  Person.now() {
    print("我是命名构造函数");
  }

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

void main() {
  var p2 = Person.now(); // 我是命名构造函数
  print(p2._height); // 170
}

下面我们把 Person 类分离到 Person.dart 文件中,再用 import 引入进来:

import 'Person.dart';

void main() {
  var p2 = Person.now(); // 我是命名构造函数
  print(p2._height); // 报错
}

getter、setter

语法与 JS/TS 有细微的差异。

class Rect {
  int height;
  int width;

  Rect(this.height, this.width);

  set areaHeight(value) {
    this.height = value * 2;
  }

  get area {
    return this.height * this.width;
  }
}

void main() {
  var r = Rect(10, 10);
  print(r.area); // 100

  r.areaHeight = 15;
  print(r.area); // 300
}

构造函数运行之前初始化实例变量

class Rect {
  int height;
  int width;

  Rect()
      : height = 10,
        width = 10 {}
}

void main() {
  var r = Rect(); // 不能再传参了
  print(r.height); // 10
  print(r.width); // 10
}

静态成员的定义和访问

class Person {
  late int age;
  static String name = 'YY';

  Person(this.age);
  
  // 非静态方法可以访问静态成员及非静态成员
  void show() {
    // 类中访问静态成员不能加 this
    print(name);
    // 类中访问非静态成员 this 可加可不加
    print(age);
  }

// 静态方法可以访问静态成员,但是不能访问非静态成员
  static void showName() {
    // 类中访问静态成员不能加 this
    print(name);
  }
}

void main() {
  var p1 = Person(20);
  p1.show(); // YY 20
  Person.showName(); // YY
}

级联操作符 ..

class Person {
  int age = 18;
  String name = 'YY';

  Person(this.name, this.age);
  
  void show() {
    print(name);
    print(age);
  }
}

void main() {
  var p1 = Person('YY', 20);
  p1..age = 25
  ..name = 'KK'
  ..show(); // KK 25
}

继承

子类可以继承父类的可见属性和方法,但是构造函数不会被继承,如果构造函数需要传参就要用到 super 关键字。

class Person {
  int age = 18;
  String name = 'YY';

  Person(this.name, this.age);
  Person.xxx(this.name, this.age);
  
  void show() {
    print(name);
    print(age);
  }
}

class Superman extends Person {
  int power = 10;
  Superman(super.name, super.age, this.power);
  // 另一种写法
  // Superman(String name, int age, this.power): super(name, age);
  Superman.xxx(String name, int age, this.power): super.xxx(name, age);
}

void main() {
  var s1 = Superman('YY', 20, 10000);
  s1..age = 25
  ..name = 'Superman'
  ..show(); // Superman 25
}

重写父类方法

class Person {
  int age = 18;
  String name = 'YY';

  Person(this.name, this.age);
  
  void show() {
    print(name);
    print(age);
  }
}

class Superman extends Person {
  int power = 10;
  Superman(String name, int age, this.power): super(name, age);

  // override 可以不写,但是建议写上
  @override
  show() {
    super.show();
    print(power);
  }
}

void main() {
  var s1 = Superman('YY', 20, 10000);
  s1..age = 25
  ..name = 'Superman'
  ..show(); // Superman 25 10000
}

抽象类

abstract class Animal {
  void eat(); // 不需要加 abstract
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗吃东西');
  }
}

class Cat extends Animal {
  @override
  eat() {
    print('小猫吃东西');
  }
}

void main() {
  var dog = new Dog();
  dog.eat(); // 小狗吃东西

  var cat = new Cat();
  cat.eat(); // 小猫吃东西
}

多态

通常意义上的多态是指父类定义一个方法,子类各有各的实现,每个子类有不同的表现。

上面的例子其实就是我们通常意义上的多态了。

但是 Dart 里面,允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。子类的实例赋值给父类的引用。

abstract class Animal {
  void eat(); // 不需要加 abstract
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗吃东西');
  }

  run() {
    print('小狗跑');
  }
}

class Cat extends Animal {
  @override
  eat() {
    print('小猫吃东西');
  }

  run() {
    print('小猫跑');
  }
}

void main() {
  Animal dog = new Dog();
  dog.eat(); // 小狗吃东西
  dog.run(); // 报错

  Animal cat = new Cat();
  cat.eat(); // 小猫吃东西
  cat.run(); // 报错
}

接口

Dart 语言并没有像 JS/TS 里面的 interface 关键字,靠 implements 类或者抽象类来当接口,可以同时实现多个接口。

abstract class Animal {
  void eat();

  printInfo() {
    print("我是父类1方法");
  }
}

class Dog implements Animal {
  @override
  eat() {
    print('小狗吃东西');
  }

  run() {
    print('小狗跑');
  }

  @override
  printInfo() {
    print("我必须把父类方法全都实现一遍,不能像继承一样拿过来直接用");
  }
}

void main() {
  var dog = new Dog();
  dog.eat(); // 小狗吃东西
  dog.run(); // 小狗跑
  dog.printInfo(); // 我必须把父类方法全都实现一遍,不能像继承一样拿过来直接用
}

使用 mixins 实现“多继承”

其实 Dart 并不支持多继承,使用 mixins 特性去实现的也只是一个类似多继承的功能。

class A {
  late String name;

  printInfoA() {
    print("A");
  }
}

class B {
  late String address;

  printInfoB() {
    print("B");
  }
}

abstract class C {
  late int age;

  printInfoC() {
    print("C");
  }
}

class D extends A with B, C {}

void main() {
  var d = D();
  d.printInfoA(); // A
  d.printInfoB(); // B
  d.printInfoC(); // C

  print(d is A); // true
  print(d is B); // true
  print(d is C); // true
}

上面的例子中,使用 withmixins B类 和 C类都不能有构造函数,否则会报错。如果混入的类方法重名,那么谁后混入就最终会使用哪个类的方法。

枚举

基础用法

enum Color { red, green, blue }

void main() {
  var c = Color.blue;

  print(c); // Color.blue

  List<Color> colors = Color.values;
  print(colors[2] == Color.blue); // true
  print(Color.red.index == 0); // true
  print(Color.green.index == 1); // true
  print(Color.blue.index == 2); // true

  print(Color.blue.name); // 'blue'
}

声明增强的枚举

Dart 还允许枚举声明,声明具有字段、方法和常量构造函数的类,这些类仅限于固定数量的已知常量实例。

要声明增强的枚举,请遵循与普通类类似的语法,但有一些额外要求:

  • 实例变量必须是最终变量,包括由mixin添加的实例变量。
  • 所有生成构造函数都必须是常量。
  • 工厂构造函数只能返回一个固定的已知枚举实例。
  • 没有其他类可以扩展,因为Enum是自动扩展的。
  • 索引、hashCode和相等运算符 == 不能有重写。
  • 不能在枚举中声明名为value的成员,因为它会与自动生成的静态值getter冲突。
  • 枚举的所有实例都必须在声明的开头声明,并且必须至少声明一个实例。

增强枚举中的实例方法可以使用此引用当前枚举值。

下面是一个示例,它声明了一个具有多个实例、实例变量、getter和一个已实现接口的增强枚举:

enum Vehicle implements Comparable<Vehicle> {
  car(tires: 4, passengers: 5, carbonPerKilometer: 400),
  bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
  bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);

  const Vehicle({
    required this.tires,
    required this.passengers,
    required this.carbonPerKilometer,
  });

  final int tires;
  final int passengers;
  final int carbonPerKilometer;

  int get carbonFootprint => (carbonPerKilometer / passengers).round();

  bool get isTwoWheeled => this == Vehicle.bicycle;

  @override
  int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}

泛型

泛型方法

// 第一个T是返回值类型
T getData<T>(T value) {
  return value;
}

void main() {
  getData<String>('text');
  getData<int>(123);
}

泛型类

List 就是一个典型的泛型类。

void main() {
  List list = <int>[1, 2, 3];
}

自定义泛型类

class MyList<T> {
  List list = <T>[];
}

void main() {
  MyList list = MyList<String>();
}

泛型接口

abstract class ObjectCache {
  getByKey(String key);
  void setByKey(String key, Object value);
}

abstract class StringCache {
  getByKey(String key);
  void setByKey(String key, Object value);
}

// 泛型写法就可以代替上面两个接口
abstract class Cache<T> {
  getByKey(String key);
  void setByKey(String key, T value);
}

class MemoryCache<T> implements Cache<T> {
  @override
  getByKey(String key) {
    return key;
  }

  @override
  void setByKey(String key, T value) {
    print("我是内存缓存${key} value=${value}");
  }
}

void main() {
  var m = MemoryCache<String>();

  m.setByKey("key", "0xff"); // 我是内存缓存key value=0xff
}

const、final、identical

const 与 final 的区别

constfinal 都可以用于定义常量,区别在于初始化的时机不一样,final 是运行时常量,在运行时进行赋值,且只能赋值一次,而 const 是在编译时就要确定值。

// 这种用法 const 与 final 并无什么区别
void main() {
  const PI = 3.14;
  final PIE = 3.14;
}
// 这种用法就体现出区别了
void main() {
  const PI = 3.14;
  final TIME = new DateTime.now(); // 这里如果用 const 会报错
}

使用 identical 判断对象是否相同

identicalDart 语言 core 中的函数,他可以用于判断两个引用是否指向同一个对象(同一个存储空间)。

void main() {
  var obj1 = Object();
  var obj2 = Object();
  print(identical(obj1, obj2)); // false
  print(identical(obj1, obj1)); // true
}
void main() {
  var obj1 = 1;
  var obj2 = 1;
  var obj3 = 2;
  var obj4 = [1, 2, 3];
  var obj5 = [1, 2, 3];
  print(identical(obj1, obj2)); // true
  print(identical(obj1, obj3)); // false
  print(identical(obj4, obj5)); // false
}

使用 const 创建相同对象

多个地方在实例化相同对象的时候,如果在前面增加 const 关键字,那么内存中只会保留一个对象,类似单例。

void main() {
  var obj1 = const Object();
  var obj2 = const Object();
  print(identical(obj1, obj2)); // true
  print(identical(obj1, obj1)); // true
}
// 对象的值一样的情况下才会只保留一个对象,否则还是会不一样的
void main() {
  var obj1 = const [1, 2, 3];
  var obj2 = const [1, 2, 3];
  var obj3 = const [4, 5, 6];
  var obj4 = const [4, 5, 6];
  print(identical(obj1, obj2)); // true
  print(identical(obj1, obj1)); // true
  print(identical(obj2, obj3)); // false
  print(identical(obj3, obj4)); // true
}

常量构造函数

常量构造函数特点:

  • 其构造函数使用 const 关键字修饰
  • 成员变量必须以 final 关键字修饰
  • 实例化的时候仍然需要加 const 关键字,否则实例化的对象也不是常量实例(非常量构造函数,用 const 关键字实例化会报错)
  • 实例化的时候,多个地方创建这个对象,如果传入的参数相同,只会创建一个对象
  • flutter 中大量使用了 const 关键字,这是一个比较重要的特性,可以大大减少组件构建时的内存开销,在改变组件状态时不会重新构建 const 声明的组件,减少不必要的重渲染
class Container {
  final int width;
  final int height;
  const Container({required this.width, required this.height});
}

void main() {
  var obj1 = const Container(width: 100, height: 100);
  var obj2 = const Container(width: 100, height: 100);
  var obj3 = const Container(width: 200, height: 200);
  var obj4 = Container(width: 100, height: 100);
  var obj5 = Container(width: 100, height: 100);
  print(identical(obj1, obj2)); // true
  print(identical(obj2, obj3)); // false
  print(identical(obj2, obj3)); // false
  print(identical(obj4, obj5)); // false
  print(identical(obj1, obj4)); // false
}

Dart 的各种库及包管理系统

自定义库

这个比较简单,就是你自己写的一些 dart 文件,抽离出去,然后在你的代码中 import 进来即可。

import 'lib/xxx.dart'

dart 中,模块不需要 export

系统内置库

dart 内置了很多库,可以直接引入使用。

dart.cn/libraries

import 'dart:math';

void main() {
  print(max(10, 32)); // 32
}

Pub 包管理系统中的库

通过 pub 包管理系统,可以引入第三方模块。

官网地址如下:

pub.dev/packages

pub-web.flutter-io.cn/packages

要使用这个库,我们需要先定义一个 pubspec.yaml,文件大致内容如下:

name: flutterdemo
description: "A new Flutter project."
environment:
  sdk: '>=3.3.1 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.6

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0

flutter:

  uses-material-design: true

  • name 是项目名称
  • description 是项目描述
  • environment 是我们使用的 dart 的版本
  • dependencies 是运行时的依赖
  • dev_dependencies 是开发依赖

如果想要安装一个第三方模块,可以去官方网站找到该模块,然后获得其依赖引入的关键字和版本号,如:

dependencies:
  http: ^1.2.1

然后执行如下命令进行获取:

pub get

使用:

import 'package:http/http.dart' as http;
// 或
import 'package:http/http.dart';

部分导入

// 只导出某一个函数
import 'package:lib1/lib1.dart' show foo;

// 只不导出某一个函数
import 'package:lib1/lib1.dart' hide foo;

延迟加载

也称为懒加载,可以在需要的时候再进行加载,减少 APP 的启动时间。

import 'package:deferred/hello.dart' deferred as hello;

greet() async {
  // 需要用loadLibrary方法来加载
  await hello.loadLibrary();
  hello.printGreeting();
}

Null safety

空安全,Dart2.12.0 之后提供的特性。主要是为了在编译期防止意外访问 null 变量的错误产生。

例如,如果一个方法需要整型结果,却接收到了 null,你的应用会出现运行时错误。这类空引用错误在以前调试是非常困难的。

空安全的原则

Dart 的空安全支持基于以下两条核心原则:

  • 默认不可空。除非你将变量显式声明为可空,否则它一定是非空的类型。我们在研究后发现,非空是目前的 API 中最常见的选择,所以选择了非空作为默认值。
  • 完全可靠。Dart 的空安全是非常可靠的,意味着编译期间包含了很多优化。如果类型系统推断出某个变量不为空,那么它 永远 不为空。当你将整个项目和其依赖完全迁移至空安全后,你会享有健全性带来的所有优势—— 更少的 BUG、更小的二进制文件以及更快的执行速度。

可空类型

pubspec.yaml 配置 2.12.0 之前的版本,下面这种写法是不报错的,但是配置版本 大于等于 2.12.0 则会有报错。

void main() {
  String name = '';
  name = null;
}

name 的类型是 字符串类型,并不能包含 null 类型,所以就会报错。如果要让 name 可以为 null 那么我们就需要显示声明,告诉我们的编译器,这个变量可以为空,写法如下:

void main() {
  String? name = '';
  name = null;
}

? 表示可空类型。

String? getData(apiUrl) {
  if (apiUrl != null) {
    return 'data';
  }

  return null;
}

非空断言

! 表示非空断言

void main() {
  String? str = "data";
  str = null;
  print(str!.length);
}

延迟初始化

late 关键字延迟初始化。

class Person {
  // late 表示定义的时候不用初始化,创建实例的时候再去初始化
  late int age;
  late String name;

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

required

required 关键字主要用于允许根据需要标记任何命名参数(函数或类),使得他们不为空。因为可选参数中必须有个 requeired

String printUserInfo(String name, { required int age, required String sex, }) {
  if (age != 0) {
    return "姓名: $name---性别: $sex---年龄: $age";
  }

  return "姓名: $name---性别: $sex---年龄保密";
}

void main() {
  print(printUserInfo("YY", age: 25, sex: 'Male')); // 姓名: YY---性别: Male---年龄: 25
}
class Person {
  int? age;
  String name;

  Person({required this.name, this.age}); // 表示 name 必须传入 age 可以不传

  getInfo() {
    print("${this.name}---${this.age}");
  }
}

void main() {
  var p = Person(age: 23, name: 'YY');
  p.getInfo(); // YY---23
}

异步

asyncawait 套装,Future 类似 Promise

import 'dart:convert';
import 'dart:io';

const String filename = 'with_keys.json';

void main() async {
  // Read some data.
  final fileData = await _readFileAsync();
  final jsonData = jsonDecode(fileData);

  // Use that data.
  print('Number of JSON keys: ${jsonData.length}'); // Number of JSON keys: 1
}

Future<String> _readFileAsync() async {
  final file = File(filename);
  final contents = await file.readAsString();
  return contents.trim();
}

with_keys.json

{
  "abc": "123"
}