Dart2基础

本文是Dart2语言的学习笔记,涵盖了入门须知、变量、内建类型、函数及其特性,包括可选参数、main函数、匿名函数和闭包,以及运算符、switch case、异常处理等内容。深入探讨了Dart的类和对象,如构造函数、对象类型、泛型和异步支持。此外,还讨论了Isolates和Typedefs等高级概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习Flutter的第一步是学习Dart语言,然后才是学习Flutter本身。如果你学习Kotlin Or Java ,那么学习起来很容易入门。因为你会看到很多你熟悉的语法。

仅做学习笔记,详细内容可品尝:

英文官网:https://dart.dev/guides/language/language-tour

中文:https://dartcn.com/guides/language/language-tour

英文6的可以直接阅读英文官网的学习资源。

一、入门须知

  • Dart是一门强类型的语言。在Dart中定义一个变量,既可以直接指定变量的类型,也可以通过var声明一个变量,尽管Dart是强类型的,但是Dart可以推断类型,所以类型注释是可选的。当然,如果要明确说明不需要任何类型,需要使用特殊类型dynamic - 代表动态类型。
String name = "Tom";

var name = "Tom";
  • Dart中一切皆对象。无论是数字,函数(Function类型)和null。所有对象均继承自Object类。
  • Dart支持泛型,比如List<int>或者List<dynamic>(任何类型的对象列表)。
  • Dart支持顶级函数、顶级变量。
  • 与Java不同,Dart中没有关键字public、protected和private。如果想定义私有方法或者私有变量等,则以"_"开头即可。

二、变量

未初始化的变量默认值是null。即使变量是数字类型默认值也是null,因为Dart中一切皆对象,数字类型也不例外。

tip:开发环境可以使用assert()来断言一个变量是否是null,比如

int count  = null
assert(count==null)

生产环境代码中assert()函数会被忽略,不会被调用。在开发过程中assert(condition)会在false条件下抛出异常。

final 与 const的变量声明:

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

const bar = 1000000; // 压力单位 (dynes/cm2)
const double atm = 1.01325 * bar; // 标准气压

三、内建类型

  • Number

      - Dart语言的Number类型有两种:int、double;注:从Dart2.1开始,必要的时候int字面量会自动转换成double类型【在 2.1 之前,在 double 上下文中使用 int 字面量是错误的】;

  • String

      - 通过单引号或者双引号创建;支持${expression}的方式内嵌表达式;

        使用连续三个单引号或者三个双引号实现多行字符串对象的创建;

var s1 = '''
You can create
multi-line strings like this one.
''';

       使用r前缀,可以创建“原始raw”字符串:

void main() {
  var s = r"In a raw string, even \n isn't special.";
  print(s);
}

输出:
In a raw string, even \n isn't special.
  • Boolean
  • List (也被称为 Array)

      在Dart中的Array就是List对象;Dart中的List字面量非常像JS中的array字面量。示例:

var lits = [1,2,3]

Dart推断list的类型是List<int>.如果尝试将非整数对象添加到此List中,则分析器或者运行时将会引发错误

List的访问:list[0]
  • Map
Map的创建:

var gifts = {
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
}

或者

var gifts = Map(); - Dart创建对象, new关键字是可选的
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

Dart推断gifts的类型是Map<String, String>,尝试添加错误的类型会导致报错
  • Set
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Dart 推断halogens 类型为Set<String>,如果尝试为它添加一个错误类型,分析器或者运行时会报错。

要创建一个空集,使用前面带有类型参数的{},或者将{}赋值给Set类型的变量:

var names = <String>{};

或者

Set<String> name = {}

注意:var names = {}这样创建的是一个Map,而不是Set。
-----------------------------------------------------------
Map和Set的字面量语法非常相似。因为先有的Map字面量语法,所以{}默认是Map类型。如果忘记在{}上注释类型或者赋值到一个未声明类型的变量上,那么Dart会创建一个类型为Map<dynamic, dynamic>的对象
  • Rune (用于在字符串中表示 Unicode 字符,例如'\u{1f44f}')
  • Symbol - 表示 Dart 程序中声明的运算符或者标识符。

四、函数

Dart是一门真正面向对象的语言,甚至函数也是对象。这也意味着函数可以被赋值给变量或者作为参数传递(函数是一等对象)给其他函数。

如果函数只有一句表达式,则可以使用简写的语法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> expr 语法是 { return expr; } 的简写。 => 符号 有时也被称为 箭头 语法。

(在kotlin中,这样的函数被称为单表达式函数)

4.1、可选参数

可选参数可以是命名参数或者是位置参数。支持设置默认值。

命名可选参数(推荐)

定义:

void xxxx({Type1 name1,Type2 name2,......})

如果在Type2前面添加了@required,则name2是必须传入的

使用:

xxxx(name1:xx) 或 xxxx(name2:xx) 或 xxxx(name1:xx,name2:xx)

调用函数时,可以使用指定命名参数paramName:value。例如

enableFlags(bold:true);

注:Flutter创建实例的表达式可能很复杂, 因此Widget构造函数仅使用命名参数。 这样创建实例的表达式更易于阅读。

位置可选参数

将参数放到[]中来标记参数是可选的:

String say(String msg,[String extra])

如上,其中extra就是位置可选参数。

4.2、main函数

任何应用都必须有一个顶级main函数,作为应用服务的入口。main函数的返回值为空,参数为一个可选的List<String>。

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}
//注:..是级联操作符,是一个语法糖,可以简化在一个对象上执行的多个操作。

上述函数体中的代码等价于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

4.3、匿名函数

创建没有名字的函数,这种函数被称为匿名函数,有时候也称为lambda或者closure(闭包)。

([[Type] param1[, …]]) { 
  codeBlock; 
}; 


示例:
var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });


等价于

list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

4.4、词法闭包

一个函数对象,即使函数对象的调用在它原始作用域之外,依然可以访问在它词法作用域内的变量。

函数可以封闭定义到它作用域内的变量。

如下示例,无论在什么时候执行返回函数,函数都会使用捕获的addBy变量。

/// 返回一个函数,返回的函数参数与 [addBy] 相加。
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // 创建一个加 2 的函数。
  var add2 = makeAdder(2);

  // 创建一个加 4 的函数。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

tip:所有的函数都会返回一个值。如果没有明确指定返回值,函数体会隐式的添加return null;语句。例如:

foo() {}

5、运算符

仅列出几个比较特殊的运算符,其余的运算符都比较常见,和Java或Kotlin的一样。

  • ..   - 级联运算符,4.2示例中有具体用法;
  • ?? 
?? 是if null的意思

示例:
// 如果b为空时,将变量赋值给b,否则,b的值保持不变。
b ??= value;

-------------------------------

(1)expr1 ?? expr2  

如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。

(2)condition ? expr1 : expr2

==> 如果赋值根据布尔值,考虑使用2;如果赋值基于判定是否为null,考虑使用1  
  • ?. 与Koltin中的用法一毛一样
  • as
(emp as Person).firstName = 'Tom';

如果emp是null,则会抛出异常
  • is 
if(emp is Person){
......
}
如果emp为null 或者 不是Person,则不执行

6、switch case

Dart中switch语句使用==比较整数、字符串、或者编译时常量。枚举类型也可以用于switch语句。

除break外,支持continue,throw或者return。

continue结合Label:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

7、异常

在Java和OC中,如果程序发生异常且没有被捕获,那么程序将会终止,但在Dart或JavaScript中则不会,究其原因,这和它们的运行机制有关系,Java和OC都是多线程模型的编程语言,任意一个线程触发异常且没被捕获时,整个进程就退出了。但Dart和JavaScript不同,它们都是单线程模型,运行机制很相似(但有区别),下面我们通过Dart官方提供的一张图来看看dart大致运行原理:

Dart中,try-catch-finally的具体用法示例:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  //指定的异常类型
  buyMoreLlamas();
} on Exception catch (e) {
  // 其他任何异常
  print('Unknown exception: $e');
} catch (e) {
  // 没有指定的类型,处理所有异常
  print('Something really unknown: $e');
} finally{
print('......')
}

或者

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}
和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。Dart程序可以抛出任何非null对象,不仅限Exception和Error对象。示例:

throw FormatException('Expected at least 1 section');

也可以抛出任意的对象:

throw 'Out of llamas!';

提示:高质量的生产环境代码通常是实现Error或Exception类型的异常抛出

可以通过rethrow重新抛出异常。catch可以指定1到两个参数,第一个参数为抛出的异常对象,第二个为堆栈信息。

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
  rethrow;
}

8、类

Dart是一种基于mixin继承机制的面向对象的语言。每个对象都是一个类的实例,所有的类都继承于Object。基于*Mixin继承*意味着每个类(除了Object外)都只有一个超类,一个类中的代码可以在其他多个继承类中重复使用。

8.1、构造函数 - 概述

构造函数的名字可以是ClassName 或者 ClassName.identifier。后者被称为命名构造函数。

var p1 = Point(1,2);
var p2 = Point.fromJson({'x':1,'y':2});
var p3 = Point.fromMap(map);
var p4 = Point.origin();
以上均是通过构造函数创建一个Point实例。
------------------------------------------
class Point {
  num x, y;

  // 命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }

  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }

Dart为我们提供了语法糖,如下所示:
 
  // 在构造函数体执行前,
  // 语法糖已经设置了变量 x 和 y。
  Point(this.x, this.y); //与上面的构造函数(Point(num x, num y))效果完全一致

}

常量构造函数是在构造函数名之前加const关键字。构造两个相同的编译时常量会产生一个唯一的,标准的实例:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它们是同一个实例。
-------------------------------------------
在常量上下文中,构造函数或者字面量前的const可以省略。

// 这里有很多的 const 关键字。
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

等价于

// 仅有一个 const ,由该 const 建立常量上下文。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果常量构造函数在常量上下文之外,且省略了const关键字,此时创建的对象是非常量对象:

var a = const ImmutablePoint(1, 1); // 创建一个常量对象
var b = ImmutablePoint(1, 1); // 创建一个非常量对象

assert(!identical(a, b)); // 两者不是同一个实例!

子类不会继承父类的构造函数。 子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) 。

切记,构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数。

8.2、对象类型

使用对象的runtimeType属性即可获取。

8.3、实例变量

class Point{
num x;
num y;
}

所有的实例变量都生成隐式的gettter方法。非final的实例变量同样会生成隐式setter方法。

8.4、初始化列表

默认情况下,无名构造函数的执行顺序如下所示:

  1. initializer list (初始化参数列表)
  2. superclass’s no-arg constructor (父类的无名构造函数)
  3. main class’s no-arg constructor (主类的无名构造函数)

如果父类中没有无名构造函数,则需要手动调用父类的其他构造函数。如下示例:

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

由于父类的构造函数参数在构造函数之前执行,所以参数可以是一个表达式或一个方法调用。

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

除了调用超类构造函数之外,还可以在构造函数体执行之前初始化实例变量。各参数的初始化用逗号分隔。

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
---------------------------------
开发期间,可以使用assert验证输入的参数
Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

8.5、常量构造函数

如果该类生成的对象是固定不变的,那么就可以把这些对象定义为编译时常量。为此,需要定义一个const构造函数,并且声明所有的实例变量为final。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

注:常量构造函数创建的实例并不总是常量。详见8.1节最后部分。

8.6、工厂构造函数

在构造函数前添加factory关键字即为工厂构造函数。

当执行构造函数并不总是创建这个类的一个新实例时,则可以使用工厂构造函数。

8.7、Getter和Setter

Getter 和 Setter 是用于对象属性读和写的特殊方法。 回想之前的例子,每个实例变量都有一个隐式 Getter ,通常情况下还会有一个 Setter 。 使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算属性: right 和 bottom。
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

最开始实现 Getter 和 Setter 也许是直接返回成员变量; 随着需求变化, Getter 和 Setter 可能需要进行计算处理而使用方法来实现; 但是,调用对象的代码不需要做任何的修改。

8.8、隐式接口

每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。

如果要创建一个类A,A要支持B类的API,但是不需要继承B的实现,那么可以通过A实现B的隐式接口。

Dart中实现接口用implements,继承一个类用extends。和Java的用法一毛一样。

// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
  // 包含在接口里,但只在当前库中可见。
  final _name;

  // 不包含在接口里,因为这是一个构造函数。
  Person(this._name);

  // 包含在接口里。
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// person 接口的实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

8.9、为类添加功能:Mixin

Mixin是复用类代码的一种途径复用的类可以在不同层级,之间可以不存在继承关系。

通过with后面跟一个或者多个混入的名称,来使用Mixin。

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

通过创建一个继承自Object且没有构造函数的类,来实现一个Mixin如果Mixin不希望作为常规类被使用,使用关键字mixin替换class。示例:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

指定只有某些类型可以使用的Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用 on 来指定可以使用 Mixin 的父类类型:

mixin MusicalPerformer on Musician {
  // ···
}

9、泛型

Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

相反,Java中的泛型会被 擦除 ,也就是说在运行时泛型类型参数的信息是不存在的。 在Java中,可以测试对象是否为 List 类型, 但无法测试它是否为 List<String>

除此之外,泛型的用法与Java一样。

限制泛型类型使用extends:

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}
可以使用 SomeBaseClass 或其任意子类作为通用参数,指定任何非 SomeBaseClass 类型会导致错误。

10、异步支持

Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 操作)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。

10.1、处理Future

获得Future执行完成的结果:

  • 使用async和await
Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

使用await和async关键字的代码是异步的。虽然看起来有点像同步代码。

要使用await,代码必须在异步函数(使用async标记的函数)。

Future<String> lookUpVersion() async =>'1.0.0';

注意:函数体不需要使用Future Api。如有必要,Dart会自动创建Future对象的。如果函数没有返回值,需要设置返回类型为Future<void>

 虽然异步函数可能会执行耗时的操作, 但它不会等待这些操作。 相反,异步函数只有在遇到第一个 await 表达式时才会执行。 也就是说,它返回一个 Future 对象, 仅在await表达式完成后才恢复执行。

使用try,catch,和finally来处理代码中使用await导致的错误。

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

一个异步函数中可以多次使用await。

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

在await表达式中,表达式的值通常是一个Future对象。如果不是,这时表达式的值会自动包装成一个Future对象。

Future对象指明返回一个对象的承诺。await表达式执行的结果为这个返回的对象。await表达式会阻塞代码的执行,直到需要的对象返回为止。

  • 使用Future Api

future.then().error().catch()。。。。。

10.2、处理Stream

当需要从Stream中获取数据值时,可以通过以下两种方式:

  • 使用async和一个异步循环(await for);
  • 使用Stream API

注意:在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。

11、Isolates

大多数计算机中,甚至在移动平台上,都在使用多核CPU。 为了有效利用多核性能,开发者一般使用共享内存数据来保证多线程的正确执行。 然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

所有 Dart 代码都在隔离区( isolates )内运行,而不是线程。 每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问。

12、Typedefs

在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用 typedef ,或者 function-type alias 为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Initial, broken implementation. // broken ?
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // 虽然知道 compare 是函数,
  // 但是函数是什么类型 ?
  assert(coll.compare is Function);
}

当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function 。如果我们使用显式的名字并保留类型信息, 这样开发者和工具都可以使用这些信息:

typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

提示: 目前,typedefs 只能使用在函数类型上.

由于 typedefs 只是别名, 他们还提供了一种方式来判断任意函数的类型。例如:

typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;

void main() {
  assert(sort is Compare<int>); // True!
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值