Dart 基础重点截取 Dart 2 20180417

2018/04/17 12:08
阅读数 274

官网教程 https://www.dartlang.org/guides/language/language-tour

dart是一个单线程的语言,没有多线程

Final and const

If you never intend to change a variable, use final or const, either instead of var or in addition to a type. A final variable can be set only once; a const variable is a compile-time constant. (Const variables are implicitly final.) A final top-level or class variable is initialized the first time it’s used.

Note: Instance variables can be final but not const.

Here’s an example of creating and setting a final variable:

final name = 'Bob'; // Without a type annotation // name = 'Alice'; // Uncommenting this causes an error final String nickname = 'Bobby';

Use const for variables that you want to be compile-time constants. If the const variable is at the class level, mark it static const. Where you declare the variable, set the value to a compile-time constant such as a number or string literal, a const variable, or the result of an arithmetic operation on constant numbers:

const bar = 1000000; // Unit of pressure (dynes/cm2) const double atm = 1.01325 * bar; // Standard atmosphere

The const keyword isn’t just for declaring constant variables. You can also use it to create constant values, as well as to declare constructors that create constant values. Any variable can have a constant value.

// Note: [] creates an empty list.
// const [] creates an empty, immutable list (EIL).
var foo = const []; // foo is currently an EIL. final bar = const []; // bar will always be an EIL. const baz = const []; // baz is a compile-time constant EIL. // You can change the value of a non-final, non-const variable, // even if it used to have a const value. foo = []; // You can't change the value of a final or const variable. // bar = []; // Unhandled exception. // baz = []; // Unhandled exception.

For more information on using const to create constant values, see ListsMaps, and Classes.

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

Default parameter values

Your function can use = to define default values for both named and positional parameters. The default values must be compile-time constants. If no default value is provided, the default value is null.

void doStuff(
    {List<int> list = const [1, 2, 3], Map<String, String> gifts = const { 'first': 'paper', 'second': 'cotton', 'third': 'leather' }}) { print('list: $list'); print('gifts: $gifts'); }

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

The main() function

Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List<String>parameter for arguments.

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) { print(arguments); assert(arguments.length == 2); assert(int.parse(arguments[0]) == 1); assert(arguments[1] == 'test'); }

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

Operators

if null             ??

cascade            ..

Arithmetic operators

/               Divide
~/                Divide, returning an integer result

print(5 / 2 == 2.5); // Result is a double
print(5 ~/ 2 == 2); // Result is an int

Type test operators

as             Typecast
is             True if the object has the specified type
is!             False if the object has the specified type

Assignment operators

As you’ve already seen, you can assign values using the = operator. To assign only if the assigned-to variable is null, use the ??= operator.

// Assign value to a
a = value; // Assign value to b if b is null; otherwise, b stays the same b ??= value;

Conditional expressions

condition ? expr1 : expr2  If condition is true, evaluates expr1 (and returns its value); otherwise, evaluates and returns the value of expr2.

expr1 ?? expr2        If expr1 is non-null, returns its value; otherwise, evaluates and returns the value of expr2.

Cascade notation (..)

Cascades (..) allow you to make a sequence of operations on the same object. In addition to function calls, you can also access fields on that same object. This often saves you the step of creating a temporary variable and allows you to write more fluid code.

Consider the following code:

querySelector('#confirm') // Get an object. ..text = 'Confirm' // Use its members. ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!'));

The first method call, querySelector(), returns a selector object. The code that follows the cascade notation operates on this selector object, ignoring any subsequent values that might be returned.

The previous example is equivalent to:

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

You can also nest your cascades. For example:

final addressBook = (new AddressBookBuilder() ..name = 'jenny' ..email = 'jenny@example.com' ..phone = (new PhoneNumberBuilder() ..number = '415-555-0100' ..label = 'home') .build()) .build();

Be careful to construct your cascade on a function that returns an actual object. For example, the following code fails:

var sb = new StringBuffer(); sb.write('foo') ..write('bar'); // Error: method 'write' isn't defined for 'void'.

The sb.write() call returns void, and you can’t construct a cascade on void.

Note: Strictly speaking, the “double dot” notation for cascades is not an operator. It’s just part of the Dart syntax.

Other operators

?.           Conditional member access Like ., but the leftmost operand can be null; example: foo?.bar selects property bar from expression foo unless foo is null (in which case the value of foo?.bar is null)

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

Exceptions

Your Dart code can throw and catch exceptions. Exceptions are errors indicating that something unexpected happened. If the exception isn’t caught, the isolate that raised the exception is suspended, and typically the isolate and its program are terminated.

In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods do not declare which exceptions they might throw, and you are not required to catch any exceptions.

Dart provides Exception and Error types, as well as numerous predefined subtypes. You can, of course, define your own exceptions. However, Dart programs can throw any non-null object—not just Exception and Error objects—as an exception.

Throw

Here’s an example of throwing, or raising, an exception:

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

You can also throw arbitrary objects:

throw 'Out of llamas!';

Note: Production-quality code usually throws types that implement Error or Exception.

Because throwing an exception is an expression, you can throw exceptions in => statements, as well as anywhere else that allows expressions:

void distanceTo(Point other) => throw new UnimplementedError();

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

Classes

Dart is an object-oriented language with classes and mixin-based inheritance. Every object is an instance of a class, and all classes descend from Object. Mixin-based inheritance means that although every class (except for Object) has exactly one superclass, a class body can be reused in multiple class hierarchies.

 

To create an object, you can use the new keyword with a constructor for a class. Constructor names can be either ClassName or ClassName.identifier. For example:

 

var jsonData = jsonDecode('{"x":1, "y":2}'); // Create a Point using Point(). var p1 = new Point(2, 2); // Create a Point using Point.fromJson(). var p2 = new Point.fromJson(jsonData);

 

Dart 2 note: You can omit the new before the constructor. Example: p1 = Point(2, 2)..

Some classes provide constant constructors. To create a compile-time constant using a constant constructor, use const instead of new:

var p = const ImmutablePoint(2, 2);

Dart 2 note: You can omit the const before the constructor. Example: p = ImmutablePoint(2, 2).

Constructing two identical compile-time constants results in a single, canonical instance:

var a = const ImmutablePoint(1, 1); var b = const ImmutablePoint(1, 1); assert(identical(a, b)); // They are the same instance!

To get an object’s type at runtime, you can use Object’s runtimeType property, which returns a Type object.

print('The type of a is ${a.runtimeType}');

Instance variables

Here’s how you declare instance variables:

class Point { num x; // Declare instance variable x, initially null. num y; // Declare y, initially null. num z = 0; // Declare z, initially 0. }

All uninitialized instance variables have the value null.

All instance variables generate an implicit getter method. Non-final instance variables also generate an implicit setter method. For details, see Getters and setters.

class Point { num x; num y; } void main() { var point = new Point(); point.x = 4; // Use the setter method for x. assert(point.x == 4); // Use the getter method for x. assert(point.y == null); // Values default to null. }

If you initialize an instance variable where it is declared (instead of in a constructor or method), the value is set when the instance is created, which is before the constructor and its initializer list execute.

Constructors

Declare a constructor by creating a function with the same name as its class (plus, optionally, an additional identifier as described in Named constructors). The most common form of constructor, the generative constructor, creates a new instance of a class:

class Point { num x, y; Point(num x, num y) { // There's a better way to do this, stay tuned. this.x = x; this.y = y; } }

The this keyword refers to the current instance.

Note: Use this only when there is a name conflict. Otherwise, Dart style omits the this.

The pattern of assigning a constructor argument to an instance variable is so common, Dart has syntactic sugar to make it easy:

class Point { num x, y; // Syntactic sugar for setting x and y // before the constructor body runs. Point(this.x, this.y); }

Default constructors

If you don’t declare a constructor, a default constructor is provided for you. The default constructor has no arguments and invokes the no-argument constructor in the superclass.

Named constructors

Use a named constructor to implement multiple constructors for a class or to provide extra clarity:

class Point { num x, y; Point(this.x, this.y); // Named constructor Point.origin() { x = 0; y = 0; } }

Remember that constructors are not inherited, which means that a superclass’s named constructor is not inherited by a subclass. If you want a subclass to be created with a named constructor defined in the superclass, you must implement that constructor in the subclass.

Invoking a non-default superclass constructor

By default, a constructor in a subclass calls the superclass’s unnamed, no-argument constructor. The superclass’s constructor is called at the beginning of the constructor body. If an initializer list is also being used, it executes before the superclass is called. In summary, the order of execution is as follows:

  1. initializer list
  2. superclass’s no-arg constructor
  3. main class’s no-arg constructor

If the superclass doesn’t have an unnamed, no-argument constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any).

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

Note: When using super() in a constructor’s initialization list, put it last. For more information, see the Dart usage guide.

Warning: Arguments to the superclass constructor do not have access to this. For example, arguments can call static methods but not instance methods.

Initializer list

Besides invoking a superclass constructor, you can also initialize instance variables before the constructor body runs. Separate initializers with commas.

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json) : x = json['x'], y = json['y'] { print('In Point.fromJson(): ($x, $y)'); }

Warning: The right-hand side of an initializer does not have access to this.

During development, you can validate inputs by using assert in the initializer list.

Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)'); }

Initializer lists are handy when setting up final fields. 

Redirecting constructors

Sometimes a constructor’s only purpose is to redirect to another constructor in the same class. A redirecting constructor’s body is empty, with the constructor call appearing after a colon (:).

class Point { num x, y; // The main constructor for this class. Point(this.x, this.y); // Delegates to the main constructor. Point.alongXAxis(num x) : this(x, 0); }

Constant constructors

If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.

class ImmutablePoint { static final ImmutablePoint origin = const ImmutablePoint(0, 0); final num x, y; const ImmutablePoint(this.x, this.y); }

Factory constructors

Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype.

The following example demonstrates a factory constructor returning objects from a cache:

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) { if (_cache.containsKey(name)) { return _cache[name]; } else { final logger = new Logger._internal(name); _cache[name] = logger; return logger; } } Logger._internal(this.name); void log(String msg) { if (!mute) print(msg); } }

Note: Factory constructors have no access to this.

To invoke a factory constructor, you use the new keyword:

var logger = new Logger('UI'); logger.log('Button clicked');

Methods

Methods are functions that provide behavior for an object.

Getters and setters

Getters and setters are special methods that provide read and write access to an object’s properties. Recall that each instance variable has an implicit getter, plus a setter if appropriate. You can create additional properties by implementing getters and setters, using the get and set keywords:

class Rectangle { num left, top, width, height; Rectangle(this.left, this.top, this.width, this.height); // Define two calculated properties: right and 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 = new Rectangle(3, 4, 20, 15); assert(rect.left == 3); rect.right = 12; assert(rect.left == -8); }

With getters and setters, you can start with instance variables, later wrapping them with methods, all without changing client code.

Note: Operators such as increment (++) work in the expected way, whether or not a getter is explicitly defined. To avoid any unexpected side effects, the operator calls the getter exactly once, saving its value in a temporary variable.

Abstract methods

Instance, getter, and setter methods can be abstract, defining an interface but leaving its implementation up to other classes. Abstract methods can only exist in abstract classes.

To make a method abstract, use a semicolon (;) instead of a method body:

abstract class Doer { // Define instance variables and methods... void doSomething(); // Define an abstract method. } class EffectiveDoer extends Doer { void doSomething() { // Provide an implementation, so the method is not abstract here... } }

Calling an abstract method results in a runtime error.

Overridable operators

You can override the operators shown in the following table. For example, if you define a Vector class, you might define a + method to add two vectors.

Abstract classes

Use the abstract modifier to define an abstract class—a class that can’t be instantiated. Abstract classes are useful for defining interfaces, often with some implementation. If you want your abstract class to appear to be instantiable, define a factory constructor.

Implicit interfaces

Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B’s API without inheriting B’s implementation, class A should implement the B interface.

A class implements one or more interfaces by declaring them in an implements clause and then providing the APIs required by the interfaces. For example:

// A person. The implicit interface contains greet().
class Person { // In the interface, but visible only in this library. final _name; // Not in the interface, since this is a constructor. Person(this._name); // In the interface. String greet(String who) => 'Hello, $who. I am $_name.'; } // An implementation of the Person interface. 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(new Person('Kathy'))); print(greetBob(new Impostor())); }

Here’s an example of specifying that a class implements multiple interfaces:

class Point implements Comparable, Location { // ··· }

Extending a class

Use extends to create a subclass, and super to refer to the superclass:

class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } // ··· } class SmartTelevision extends Television { void turnOn() { super.turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } // ··· }

Overriding members

Subclasses can override instance methods, getters, and setters. You can use the @override annotation to indicate that you are intentionally overriding a member:

class SmartTelevision extends Television { @override void turnOn() { // ··· } // ··· }

To narrow the type of a method parameter or instance variable in code that is type safe, you can use the covariant keyword.

noSuchMethod()

To detect or react whenever code attempts to use a non-existent method or instance variable, you can override noSuchMethod():

class A { // Unless you override noSuchMethod, using a // non-existent member results in a NoSuchMethodError. @override void noSuchMethod(Invocation invocation) { print('You tried to use a non-existent member: ' + '${invocation.memberName}'); } }

You can’t invoke an unimplemented method unless one of the following is true:

  • The receiver has the static type dynamic.

  • The receiver has a static type that defines the unimplemented method (abstract is OK), and the dynamic type of the receiver has an implemention of noSuchMethod() that’s different from the one in class Object.

For more information, see the informal nosuchMethod forwarding specification.

Enumerated types

Enumerated types, often called enumerations or enums, are a special kind of class used to represent a fixed number of constant values.

Using enums

Declare an enumerated type using the enum keyword:

enum Color { red, green, blue }

Each value in an enum has an index getter, which returns the zero-based position of the value in the enum declaration. For example, the first value has index 0, and the second value has index 1.

assert(Color.red.index == 0); assert(Color.green.index == 1); assert(Color.blue.index == 2);

To get a list of all of the values in the enum, use the enum’s values constant.

List<Color> colors = Color.values; assert(colors[2] == Color.blue);

You can use enums in switch statements, and you’ll get a warning if you don’t handle all of the enum’s values:

var aColor = Color.blue; switch (aColor) { case Color.red: print('Red as roses!'); break; case Color.green: print('Green as grass!'); break; default: // Without this, you see a WARNING. print(aColor); // 'Color.blue' }

Enumerated types have the following limits:

  • You can’t subclass, mix in, or implement an enum.
  • You can’t explicitly instantiate an enum.

For more information, see the Dart Language Specification.

Adding features to a class: mixins

Mixins are a way of reusing a class’s code in multiple class hierarchies.

To use a mixin, use the with keyword followed by one or more mixin names. The following example shows two classes that use mixins:

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

To implement a mixin, create a class that extends Object, declares no constructors, and has no calls to super. For example:

abstract class 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'); } } }

Note: As of 1.13, two restrictions on mixins have been lifted from the Dart VM:

  • Mixins allow extending from a class other than Object.
  • Mixins can call super().

These “super mixins” are not yet supported in dart2js and require the --supermixin flag in dartanalyzer.

For more information, see the article Mixins in Dart. 

Class variables and methods

Use the static keyword to implement class-wide variables and methods.

Static variables

Static variables (class variables) are useful for class-wide state and constants:

class Queue { static const int initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }

Static variables aren’t initialized until they’re used.

Note: This page follows the style guide recommendation of preferring lowerCamelCase for constant names.

Static methods

Static methods (class methods) do not operate on an instance, and thus do not have access to this. For example:

import 'dart:math'; class Point { num x, y; Point(this.x, this.y); static num distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } void main() { var a = new Point(2, 2); var b = new Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }

Note: Consider using top-level functions, instead of static methods, for common or widely used utilities and functionality.

You can use static methods as compile-time constants. For example, you can pass a static method as a parameter to a constant constructor.

 

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

 

Generics

If you look at the API documentation for the basic array type, List, you’ll see that the type is actually List<E>. The <…> notation marks List as a generic (or parameterized) type—a type that has formal type parameters. By convention, type variables have single-letter names, such as E, T, S, K, and V.

Why use generics?

Generics are often required for type safety, but they have more benefits than just allowing your code to run:

  • Properly specifying generic types results in better generated code.
  • You can use generics to reduce code duplication.

If you intend for a list to contain only strings, you can declare it as List<String> (read that as “list of string”). That way you, your fellow programmers, and your tools can detect that assigning a non-string to the list is probably a mistake. 

Using collection literals

List and map literals can be parameterized. Parameterized literals are just like the literals you’ve already seen, except that you add <type> (for lists) or <keyType,valueType> (for maps) before the opening bracket. Here is example of using typed literals:

var names = <String>['Seth', 'Kathy', 'Lars']; var pages = <String, String>{ 'index.html': 'Homepage', 'robots.txt': 'Hints for web robots', 'humans.txt': 'We are people, not machines' };

Using parameterized types with constructors

To specify one or more types when using a constructor, put the types in angle brackets (<...>) just after the class name. For example:

var names = new List<String>(); names.addAll(['Seth', 'Kathy', 'Lars']); var nameSet = new Set<String>.from(names);

The following code creates a map that has integer keys and values of type View:

var views = new Map<int, View>();

Generic collections and the types they contain

Dart generic types are reified, which means that they carry their type information around at runtime. For example, you can test the type of a collection:

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

Note: In contrast, generics in Java use erasure, which means that generic type parameters are removed at runtime. In Java, you can test whether an object is a List, but you can’t test whether it’s a List<String>.

Restricting the parameterized type

When implementing a generic type, you might want to limit the types of its parameters. You can do this using extends.

// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> { // ··· } class Extender extends SomeBaseClass { // ··· }

It’s OK to use SomeBaseClass or any of its subclasses as generic argument:

var someBaseClassFoo = new Foo<SomeBaseClass>(); var extenderFoo = new Foo<Extender>();

It’s also OK to specify no generic argument:

var foo = new Foo();

Specifying any non-SomeBaseClass type results in an error:

var foo = new Foo<Object>();

Using generic methods

Initially, Dart’s generic support was limited to classes. A newer syntax, called generic methods, allows type arguments on methods and functions:

T first<T>(List<T> ts) { // Do some initial work or error checking, then... T tmp = ts[0]; // Do some additional checking or processing... return tmp; }

Here the generic type parameter on first (<T>) allows you to use the type argument T in several places:

  • In the function’s return type (T).
  • In the type of an argument (List<T>).
  • In the type of a local variable (T tmp).

For more information about generics, see Using Generic Methods.

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

Libraries and visibility

The import and library directives can help you create a modular and shareable code base. Libraries not only provide APIs, but are a unit of privacy: identifiers that start with an underscore (_) are visible only inside the library. Every Dart app is a library, even if it doesn’t use a library directive.

Libraries can be distributed using packages. See Pub Package and Asset Manager for information about pub, a package manager included in the SDK.

Using libraries

Use import to specify how a namespace from one library is used in the scope of another library.

For example, Dart web apps generally use the dart:html library, which they can import like this:

import 'dart:html';

The only required argument to import is a URI specifying the library. For built-in libraries, the URI has the special dart: scheme. For other libraries, you can use a file system path or the package: scheme. The package: scheme specifies libraries provided by a package manager such as the pub tool. For example:

import 'package:test/test.dart';

Note: URI stands for uniform resource identifier. URLs (uniform resource locators) are a common kind of URI.

Specifying a library prefix

If you import two libraries that have conflicting identifiers, then you can specify a prefix for one or both libraries. For example, if library1 and library2 both have an Element class, then you might have code like this:

import 'package:lib1/lib1.dart'; import 'package:lib2/lib2.dart' as lib2; // Uses Element from lib1. Element element1 = new Element(); // Uses Element from lib2. lib2.Element element2 = new lib2.Element();

Importing only part of a library

If you want to use only part of a library, you can selectively import the library. For example:

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

Lazily loading a library

Deferred loading (also called lazy loading) allows an application to load a library on demand, if and when it’s needed. Here are some cases when you might use deferred loading:

  • To reduce an app’s initial startup time.
  • To perform A/B testing—trying out alternative implementations of an algorithm, for example.
  • To load rarely used functionality, such as optional screens and dialogs.

To lazily load a library, you must first import it using deferred as.

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

When you need the library, invoke loadLibrary() using the library’s identifier.

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

In the preceding code, the await keyword pauses execution until the library is loaded. For more information about async and await, see asynchrony support.

You can invoke loadLibrary() multiple times on a library without problems. The library is loaded only once.

Keep in mind the following when you use deferred loading:

  • A deferred library’s constants aren’t constants in the importing file. Remember, these constants don’t exist until the deferred library is loaded.
  • You can’t use types from a deferred library in the importing file. Instead, consider moving interface types to a library imported by both the deferred library and the importing file.
  • Dart implicitly inserts loadLibrary() into the namespace that you define using deferred as namespace. The loadLibrary() function returns a Future.

Implementing libraries

See Create Library Packages for advice on how to implement a library package, including:

  • How to organize library source code.
  • How to use the export directive.
  • When to use the part directive.

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

Asynchrony support

Dart libraries are full of functions that return Future or Stream objects. These functions are asynchronous: they return after setting up a possibly time-consuming operation (such as I/O), without waiting for that operation to complete.

The async and await keywords support asynchronous programming, letting you write asynchronous code that looks similar to synchronous code.

Handling Futures

When you need the result of a completed Future, you have two options:

Code that uses async and await is asynchronous, but it looks a lot like synchronous code. For example, here’s some code that uses await to wait for the result of an asynchronous function:

await lookUpVersion();

To use await, code must be in an async function—a function marked as async:

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

Note: Although an async function might perform time-consuming operations, it doesn’t wait for those operations. Instead, the async function executes only until it encounters its first await expression (details). Then it returns a Future object, resuming execution only after the await expression completes.

Use trycatch, and finally to handle errors and cleanup in code that uses await:

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

You can use await multiple times in an async function. For example, the following code waits three times for the results of functions:

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

In await expression, the value of expression is usually a Future; if it isn’t, then the value is automatically wrapped in a Future. This Future object indicates a promise to return an object. The value of await expression is that returned object. The await expression makes execution pause until that object is available.

If you get a compile-time error when using await, make sure await is in an async function. For example, to use await in your app’s main() function, the body of main() must be marked as async:

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

Declaring async functions

An async function is a function whose body is marked with the async modifier.

Adding the async keyword to a function makes it return a Future. For example, consider this synchronous function, which returns a String:

String lookUpVersion() => '1.0.0';

If you change it to be an async function—for example, because a future implementation will be time consuming—the returned value is a Future:

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

Note that the function’s body doesn’t need to use the Future API. Dart creates the Future object if necessary.

If your function doesn’t return a useful value, make its return type Future<void>.

Handling Streams

When you need to get values from a Stream, you have two options:

  • Use async and an asynchronous for loop (await for).
  • Use the Stream API, as described in the library tour.

Note: Before using await for, be sure that it makes the code clearer and that you really do want to wait for all of the stream’s results. For example, you usually should not use await for for UI event listeners, because UI frameworks send endless streams of events.

An asynchronous for loop has the following form:

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

The value of expression must have type Stream. Execution proceeds as follows:

  1. Wait until the stream emits a value.
  2. Execute the body of the for loop, with the variable set to that emitted value.
  3. Repeat 1 and 2 until the stream is closed.

To stop listening to the stream, you can use a break or return statement, which breaks out of the for loop and unsubscribes from the stream.

If you get a compile-time error when implementing an asynchronous for loop, make sure the await for is in an async function. For example, to use an asynchronous for loop in your app’s main() function, the body of main() must be marked as async:

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

For more information about asynchronous programming, in general, see the dart:async section of the library tour. Also see the articles Dart Language Asynchrony Support: Phase 1 and Dart Language Asynchrony Support: Phase 2, and the Dart language specification.

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

Generators

When you need to lazily produce a sequence of values, consider using a generator function. Dart has built-in support for two kinds of generator functions:

  • Synchronous generator: Returns an Iterable object.
  • Asynchronous generator: Returns a Stream object.

To implement a synchronous generator function, mark the function body as sync*, and use yield statements to deliver values:

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

To implement an asynchronous generator function, mark the function body as async*, and use yield statements to deliver values:

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

If your generator is recursive, you can improve its performance by using yield*:

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

For more information about generators, see the article Dart Language Asynchrony Support: Phase 2.

Callable classes

To allow your Dart class to be called like a function, implement the call() method. 

Isolates

Most computers, even on mobile platforms, have multi-core CPUs. To take advantage of all those cores, developers traditionally use shared-memory threads running concurrently. However, shared-state concurrency is error prone and can lead to complicated code.

Instead of threads, all Dart code runs inside of isolates. Each isolate has its own memory heap, ensuring that no isolate’s state is accessible from any other isolate.

For more information, see the dart:isolate library documentation.

Typedefs

In Dart, functions are objects, just like strings and numbers are objects.typedef, or function-type alias, gives a function type a name that you can use when declaring fields and return types. A typedef retains type information when a function type is assigned to a variable.

Consider the following code, which doesn’t use a typedef:

class SortedCollection { Function compare; SortedCollection(int f(Object a, Object b)) { compare = f; } } // Initial, broken implementation. int sort(Object a, Object b) => 0; void main() { SortedCollection coll = new SortedCollection(sort); // All we know is that compare is a function, // but what type of function? assert(coll.compare is Function); }

Type information is lost when assigning f to compare. The type of f is (Object, Object) → int (where → means returns), yet the type of compare is Function. If we change the code to use explicit names and retain type information, both developers and tools can use that information.

typedef int Compare(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 = new SortedCollection(sort); assert(coll.compare is Function); assert(coll.compare is Compare); }

Note: Currently, typedefs are restricted to function types. We expect this to change.

Because typedefs are simply aliases, they offer a way to check the type of any function. For example:

typedef int Compare<T>(T a, T b); int sort(int a, int b) => a - b; void main() { assert(sort is Compare<int>); // True! }

New function type syntax: Dart 1.24 introduced a new form of function types, the generic function type alias. You might use this feature if you pass around generic methods, define fields that are function types, or define arguments with generic function types. Here’s an example of using the new syntax:

typedef F = List<T> Function<T>(T);

Metadata

Use metadata to give additional information about your code. A metadata annotation begins with the character @, followed by either a reference to a compile-time constant (such as deprecated) or a call to a constant constructor.

Two annotations are available to all Dart code: @deprecated and @override. For examples of using @override, see Extending a class. Here’s an example of using the @deprecated annotation:

class Television { /// _Deprecated: Use [turnOn] instead._ @deprecated void activate() { turnOn(); } /// Turns the TV's power on. void turnOn() { // ··· } }

You can define your own metadata annotations. Here’s an example of defining a @todo annotation that takes two arguments:

library todo;

class Todo { final String who; final String what; const Todo(this.who, this.what); }

And here’s an example of using that @todo annotation:

import 'todo.dart'; @Todo('seth', 'make this do something') void doSomething() { print('do something'); }

Metadata can appear before a library, class, typedef, type parameter, constructor, factory, function, field, parameter, or variable declaration and before an import or export directive. You can retrieve metadata at runtime using reflection.

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

Comments

Dart supports single-line comments, multi-line comments, and documentation comments.

Single-line comments

A single-line comment begins with //. Everything between // and the end of line is ignored by the Dart compiler.

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

Multi-line comments

A multi-line comment begins with /* and ends with */. Everything between /* and */ is ignored by the Dart compiler (unless the comment is a documentation comment; see the next section). Multi-line comments can nest.

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

Documentation comments

Documentation comments are multi-line or single-line comments that begin with /// or /**. Using /// on consecutive lines has the same effect as a multi-line doc comment.

Inside a documentation comment, the Dart compiler ignores all text unless it is enclosed in brackets. Using brackets, you can refer to classes, methods, fields, top-level variables, functions, and parameters. The names in brackets are resolved in the lexical scope of the documented program element.

Here is an example of documentation comments with references to other classes and arguments:

/// 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) { // ... } }

In the generated documentation, [Food] becomes a link to the API docs for the Food class.

To parse Dart code and generate HTML documentation, you can use the SDK’s documentation generation tool. For an example of generated documentation, see the Dart API documentation. For advice on how to structure your comments, see Guidelines for Dart Doc Comments.

Summary

This page summarized the commonly used features in the Dart language. More features are being implemented, but we expect that they won’t break existing code. For more information, see the Dart Language Specification and Effective Dart.

To learn more about Dart’s core libraries, see A Tour of the Dart Libraries.

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
在线直播报名
返回顶部
顶部