Dart语言入门四

Posted MobileDev小园地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dart语言入门四相关的知识,希望对你有一定的参考价值。

库和可见性


import和library指令可以用来创建模块和可复用的代码库。库不仅能够提供API,也同时是一个私有单元:以下划线_开头的变量只有在库中是可见的。每一个Dart的应用都是一个库,即使没有使用library指令。

库可以用使用包(package) 来进行分发。参照<Pub Package and Asset Manager>来获取关于pub (SDK中的一个包管理器)的知识。


使用库

使用import关键字来指定一个库的命名空间如何在另一个库的范围内使用。

例如,Dart web应用经常使用dart:html库,它可以通过这种方式来导入:

import 'dart:html';


唯一需要指定给import的参数就是这个库的URI。对于内建库,这个URI有一个固定的前缀写法:dart:。对于其他库,你可以使用一个文件系统的路径或者package:语法。这种package:写法是指由包管理器(如pub工具)指定的库。例如:

import 'package:test/test.dart';


注意:URI是指统一资源标识符。URLs(统一资源定位符)是一种常用的URI。


指定一个库前缀

如果导入的两个库中有名称冲突,可以为其中一个库或者两个库指定前缀。例如,库1和库2都有一个Elemenet类,你可以这么来写代码:

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

// Uses Element from lib1
Element element1 = Element();

// Uses Elemenet from lib2
Element element2 = lib2.Element();


只导入库的部分内容

如果只想使用库的部分内容,可以选择性地导入库。例如:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;


懒加载一个库

延迟加载(或者叫做懒加载)允许程序在需要时才加载一个类。下面是可能使用懒加载的一些情况:

  • 减少一个应用的启动时间

  • 执行A/B测试 - 例如尝试一个算法的两种不同实现

  • 加载一些不经常使用的功能,例如可选的屏幕和对话框类


要懒加载一个库,首先必须通过在导入库时使用deffered as。

import 'package:greetings/hello.dart' defferred as hello;


当需要使用这个库时,通过库的标识符来调用loadLibrary():

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

在上面的代码中,await会暂停当前程序的执行直到库加载完。参考下面的<异步支持>节来获取更多关于async和await的知识。


loadLibrary()方法可以被多次调用也不会发生问题,无论调用几次,它只加载一次。

当使用延迟加载时,要记住以下要点:

  • 延迟加载的库里的常量在当前导入文件里不是常量。记住,这些常量在库加载完毕之前并不存在。

  • 在当前导入文件里不能使用延迟加载库里的类型。如果出现这种情况,可以考虑把这个类型放到一个第三方库,在当前文件和延迟加载库里都导入这个第三方库。

  • Dart在定义使用deferred as namespace的命名空间里隐式插入loadLibrary()。loadLibrary()方法会返回一个Future。


虚拟机差异:Dart虚拟机允许访问延迟加载库里的成员即使在调用loadLibrary()之前。这种行为可能会改变,所以不能依赖当前这种虚拟机行为。


实现库

参考’创建库包‘文档https://www.dartlang.org/guides/libraries/create-library-packages(后面会有相关文章)来学习如果实现一个库,包括:

  • 如何组织库里的代码

  • 如何使用export指令

  • 何时使用part指令

  • 何时使用library指令


异步支持


Dart的库里有非常多的返回Future或者Stream对象的函数。这些函数是异步的:它们在调用一个可能耗时的操作(如I/O)后会直接返回而不用等着这个耗时操作完成。

await和async关键字用来支持异步编程,使得写异步代码和写同步代码一样方便。


处理Future

有两种方式来获取一个完整的Future的结果:

  • 使用async和await

  • 使用Future的API,在库相关的文档有介绍

使用async和await的代码是异步的,但是它看起来却像是同步代码。例如,下面的代码使用await来等待一个异步方法的返回结果:

await lookUpVersion();

 

使用await的代码必须写在异步函数里-一个标记为async的函数:

Future checkVersion() async {
  var version = await lookupVersion();
  // Do something with version
}


注意:尽管一个异步函数可能执行一些超时的操作,代码并不会等待这些超时的操作。这个异步方法只执行到它遇到第一个await表达式。然后它直接返回,只有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表达式会暂停执行直到这个对象可用。

如果使用await时遇到编译时的错误,确保await是用在一个异步函数里。例如,在程序的main()函数里使用await,main()函数体必须被标记为async:

Future main() async {
  var version = await checkVersion();
  print('In main: version is ${await lookupVersion()}');
}


声明异步函数

异步函数的函数体被标识有async修饰符。

为一个函数添加async关键字使得它返回一个Future。例如,下面的同步代码会返回一个字符串:

String lookUpVersion() => "1.0.0";


如果把它改成一个异步函数 - 例如,因为未来的实现可能是非常耗时的 - 返回值就是一个Future。

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


注意这个函数体并不需要使用Future的API。Dart可以在需要的时候创建Future对象。

如果你的异步函数并不返回一个有用的值,把它的返回值声明为Future<void>。


处理Streams

有两种方式来从一个Stream获取结果:

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

  • 使用Stream的API,在库相关的文档有介绍


注意:在使用await for之前,确保它确实能使代码变得更清晰、你确实要等待所有流的结果。例如,通常情况下不应该在UI事件的监听器里使用await for,因为UI框架会一直发送流事件。


一个异步循环有着下面的形式:

await for (varOrType identifier in expression) {
 // Executes each time the stream emits a value
}


表达式的值必须是Stream类型。执行顺序如下:

  1. 等待,直到这个流发送一个值

  2. 执行for循环的循环体,循环体内变量被赋值为这个发送的值

  3. 重复执行步骤1和2直到流关闭


可以使用break和return语句来停止监听流,它会中断这个循环并且从流上注销。


如果在实现一个异步for循环的时候遇到一个编译错误,确保await for是写在一个异步方法中。例如,如果要在main函数里使用异步for循环,那么main函数需要标记为async:

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}


更多的关于异步编程的信息,可以参考库介绍文档的dart:async部分。也可以参考文章:Dart语言异步支持:第一部分和Dart语言异步支持:第二部分,还有Dart语言规范。


生成器


如果要延迟生成一个值的序列,可以使用生成器函数。Dart有两种内置的生成器函数:

  • 同步生成器:返回一个Iterable对象

  • 异步生成器:返回一个Stream(流)对象


要实现一个同步生成器函数,把函数标记为sync*,并且使用 yield语句来产生值:

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


要实现一个异步生成器函数,把函数标记为async*,并且同样使用yield语句来产生值:

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


如果生成器是递归执行的,可以使用yield*来改善它的性能:

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}


要获取更多的关于生成器的知识,可以参考Dart语言异步支持:第二部分。


可调用的类


要使得Dart中类可以像函数一样被调用,在类中实现一个call()方法。


在下面的例子里,WannableFunction类定义了一个call()方法,这个方法拼接三个字符串参数,用空格分割,并且结尾添加一个叹号。

class WannableFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannableFunction();
  var out = wf("Hi""there""gang");
  print("$out");
}


更多关于把类当做函数的信息,可以参考Emulating Functions in Dart(在Dart里模拟函数)。


Isolates


大多数计算机,甚至移动平台,都有多核的CPU。为了充分利用这些内核,开发者经常使用共享内存的并发线程。但是,共享内存的并发是会经常引发错误,甚至一些复杂的错误。

相比较于线程,所有的Dart代码运行在isolate里。每一个isolate有它自己的存储堆,保证了没有任何一个状态可以被其他的isolate访问。

关于更多isolate的知识,请参考dart:isolate库文档。


Typedefs


在Dart里,函数是对象,就像字符串和数字也是对象。一个typedef,或者称为函数类型别名,给一个函数类型起了一个名字,这个名字可以在声明成员和返回类型时使用。当一个函数类型被赋值给一个变量时,一个typedef会保持着这个类型信息。

考虑下面的代码,没有使用typedef:

class SortedCollection {
  Function compare;

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

// Intial, broken implementation
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);
}

// Intial, 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);
}


注意:当前typedef只能用于函数类型,希望在以后能有所改变。


因为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!
}


元数据(Metadata)


元数据可以用来给你的代码添加更多信息。元数据注释以一个@字符开始,跟着一个编译期的常量引用(例如deprecated)或者一个常量构造函数的调用。

有两个所有Dart代码都可以使用的注释:@deprecated和@override。例如使用@override可以参考类一章()的继承一个类这节。下面是一个使用@deprecated注释的例子:

class Television {
  // _Deprecated: use [turnOn] instead._
  @deprecated
  void activate() {
  }

  // Turns the TV's power on.
  void turnOn() {...}
}


你也可以定义自己的元数据注释。下面是定义一个@todo注释的例子,有两个参数。

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}


下面是一个使用这个@todo注释的例子:

import 'todo.dart'

@Todo('Seth''make this do something')
void doSomething() {
  print('do something!');
}


元数据可以出现在库、类、typedef、类型参数、构造函数、工厂、函数、属性、参数或者变量声明之前,也可以出现在import或者export指令之前。可以在运行中通过反射来获取元数据信息。


注释


Dart语言支持单行注释、多行注释和文档注释。


单行注释

单行注释以//开始,所有//和本行结尾标记之间的内容都是注释,会被Dart编译器忽略。

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}


多行注释

多行注释以/*开始,以*/结束。所有/*和*/之间的内容会被编译期忽略(除非这个注释是一个文档注释;参考下一节)。多行注释可以嵌套使用。

void main() {
  /*
   * This ia a lot of work. Consider raising chickens.
  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */

}


文档注释

文档注释是///或者/**开始的多行或者单行注释。在连续的行上使用///和使用多行文档注释有一样的效果。

在文档注释里,Dart编译期会忽略所有内容除非它在中括号里。使用中括号,可以引用一个类、方法、成员属性、全局变量,函数和参数。中括号里的名字会在文档注释所在代码的词法作用域里识别。

下面是一个引用其他类和参数的文档注释例子:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}


在生成的文档里,[Food]会变成一个指向Food类API文档的链接。

要解析Dart代码并生成HTML文档,可以使用SDK里的文档生成工具。要查看生成文档的例子,可以参考Dart API文档。要获取更多的关于结构化注释的信息,请参考Dart文档注释引导文档。


总结


Dart入门简介文章总结了Dart语言中经常使用的特性。Dart语言正在发展,更多的特性正在被实现,希望有更多的特性出现但不会对现有的代码有影响。更多的信息,可以参考Dart语言规范 (https://www.dartlang.org/guides/language/spec)和Effective Dart (https://www.dartlang.org/guides/language/effective-dart)。

关于Dart语言中更多的关于核心库的知识,参考Dart库文档(https://www.dartlang.org/guides/libraries/library-tour)。




本文主要内容翻译自Dart官方英文文档:www.dartlang.org/guides/language/language-tour 


其他相关文章:











以上是关于Dart语言入门四的主要内容,如果未能解决你的问题,请参考以下文章

Dart 语言基础入门 Dart 语言核心库一览

Dart 语言基础入门 Dart 语言核心库一览

Flutter--实战Dart 语言快速入门

『Flutter开发实战』十分钟入门Dart语言

Flutter入门Dart语言:函数入门指南

Dart语言精简入门介绍