給自己看的Dart筆記,看不懂別找我

Default value(默認值)

沒有初始化的變量自動獲取一個默認值爲 null。類型爲數字的 變量如何沒有初始化其值也是 null,不要忘記了 數字類型也是對象。

int lineCount;
assert(lineCount == null);
// Variables (even if they will be numbers) are initially null.

Final and const

如果你以後不打算修改一個變量,使用 final 或者 const。 一個 final 變量只能賦值一次;一個 const 變量是編譯時常量。 (Const 變量同時也是 final 變量。) 頂級的 final 變量或者類中的 final 變量在 第一次使用的時候初始化。

注意: 實例變量可以爲 final 但是不能是 const 。

const 關鍵字不僅僅只用來定義常量。 還可以用來創建不變的值, 還能定義構造函數爲 const 類型的,這種類型 的構造函數創建的對象是不可改變的。任何變量都可以有一個不變的值。

Strings(字符串)

通過提供一個 r 前綴可以創建一個 “原始 raw” 字符串:

var s = r"In a raw string, even \n isn't special.";

原始 raw 字符串:字符串裏的所有字符都會被當成字符輸出。上面的字符串 s,輸出如下:In a raw string, even \n isn’t special.,裏面的換行符 “\n” 也會被輸出,而不是輸出一個空行。

Lists(列表)

list 字面量之前添加 const 關鍵字,可以 定義一個不變的 list 對象(編譯時常量):

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.

Runes

Dart 中,runes 代表字符串的 UTF-32 code points

Unicode 爲每一個字符、標點符號、表情符號等都定義了 一個唯一的數值。 由於 Dart 字符串是 UTF-16 code units 字符序列, 所以在字符串中表達 32-bit Unicode 值就需要 新的語法了。

通常使用 \uXXXX 的方式來表示 Unicode code point, 這裏的 XXXX 是4個 16 進制的數。 例如,心形符號 (♥)\u2665。 對於非 4 個數值的情況, 把編碼值放到大括號中即可。 例如,笑臉 emoji (😆)\u{1f600}

String 類 有一些屬性可以提取 rune 信息。 codeUnitAtcodeUnit 屬性返回 16-bit code units, 使用 runes 屬性來獲取字符串的 runes 信息。

Optional parameters(可選參數)

可選參數可以是命名參數或者基於位置的參數,但是這兩種參數不能同時當做可選參數。

Optional named parameters(可選命名參數)

調用方法的時候,你可以使用這種形式 paramName: value 來指定命名參數。例如:

enableFlags(bold: true, hidden: false);

在定義方法的時候,使用 {param1, param2, …} 的形式來指定命名參數:

/// Sets the [bold] and [hidden] flags to the values
/// you specify.
enableFlags({bool bold, bool hidden}) {
  // ...
}

在定義方法的時候,可以使用 = 來定義可選參數的默認值。 默認值只能是編譯時常量。 如果沒有提供默認值,則默認值爲 null

/// Sets the [bold] and [hidden] flags to the values you
/// specify, defaulting to false.
void enableFlags({bool bold = false, bool hidden = false}) {
  // ...
}

// bold will be true; hidden will be false.
enableFlags(bold: true);
Optional positional parameters(可選位置參數)

把一些方法的參數放到 [] 中就變成可選 位置參數了:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

對於可選命名參數和可選位置參數的區別,目前來看僅僅是調用的時候可選命名參數需要寫上名字,而可選位置參數是不需要的。

Cascade notation (..)(級聯操作符)

級聯操作符 (..) 可以在同一個對象上 連續調用多個函數以及訪問成員變量。 使用級聯操作符可以避免創建 臨時變量, 並且寫出來的代碼看起來 更加流暢:

例如下面的代碼:

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

第一個方法 querySelector() 返回了一個 selector 對象。 後面的級聯操作符都是調用這個對象的成員, 並忽略每個操作 所返回的值。

Note:
1、方法的返回如果爲void,使用級聯不起作用;
2、String的相關方法,使用級聯不起作用;

Constructors

Default constructors(默認構造函數)

如果你沒有定義構造函數,則會有個默認構造函數。 默認構造函數沒有參數,並且會調用超類的 沒有參數的構造函數。

Constructors aren’t inherited(構造函數不會繼承)

子類不會繼承超類的構造函數。 子類如果沒有定義構造函數,則只有一個默認構造函數 (沒有名字沒有參數)。

Named constructors(命名構造函數)

使用命名構造函數可以爲一個類實現多個構造函數, 或者使用命名構造函數來更清晰的表明你的意圖:

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

注意:構造函數不能繼承,所以超類的命名構造函數 也不會被繼承。如果你希望 子類也有超類一樣的命名構造函數,你必須在子類中自己實現該構造函數。

Redirecting constructors(重定向構造函數)

有時候一個構造函數會調動類中的其他構造函數。 一個重定向構造函數是沒有代碼的,在構造函數聲明後,使用 冒號調用其他構造函數。

class Point {
  num x;
  num 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(常量構造函數)

如果你的類提供一個狀態不變的對象,你可以把這些對象 定義爲編譯時常量。要實現這個功能,需要定義一個 const 構造函數, 並且聲明所有類的變量爲 final。

class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);
}
Factory constructors(工廠方法構造函數)

如果一個構造函數並不總是返回一個新的對象,則使用 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) {
    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);
    }
  }
}

注意: 工廠構造函數無法訪問 this。

使用 new 關鍵字來調用工廠構造函數。

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

Invoking a non-default superclass constructor(調用超類構造函數)

默認情況下,子類的構造函數會自動調用超類的 無名無參數 的默認構造函數。 超類的構造函數在子類構造函數體開始執行的位置調用。 如果提供了一個 initializer list(初始化參數列表) ,則初始化參數列表在超類構造函數執行之前執行。 下面是構造函數執行順序:

  1. initializer list(初始化參數列表)
  2. superclass’s no-arg constructor(超類的無名構造函數)
  3. main class’s no-arg constructor(主類的無名構造函數)

如果超類沒有無名無參數構造函數, 則你需要手工的調用超類的其他構造函數。 在構造函數參數後使用冒號 “:” 可以調用 超類構造函數。
初始化列表非常適合用來設置 final 變量的值。 下面示例代碼中初始化列表設置了三個 final 變量的值。

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);
}

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 = 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.

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(Person('Kathy')));
  print(greetBob(Impostor()));
}

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

The covariant keyword

Some (rarely used) coding patterns rely on tightening a type by overriding a parameter’s type with a subtype, which is invalid. In this case, you can use the covariant keyword to tell the analyzer that you are doing this intentionally. This removes the static error and instead checks for an invalid argument type at runtime.

Version note: The covariant keyword was introduced in 1.22. It replaces the @checked annotation.

The following shows how you might use covariant:

class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  void chase(covariant Mouse x) { ... }
}

Although this example shows using covariant in the subtype, the covariant keyword can be placed in either the superclass or the subclass method. Usually the superclass method is the best place to put it. The covariant keyword applies to a single parameter and is also supported on setters and fields.

noSuchMethod()

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

class Foo {
  foo(x) {}
}

class MockFoo implements Foo {
  // PS: Make sure that a tear-off of `_mockFoo` has the same type
  // as a tear-off of `Foo.foo`.
  _mockFoo(x) {
    // ... implement mock behavior for `foo` here.
  }

  noSuchMethod(Invocation i) {
    if (i.memberName == #foo) {
      if (i.isMethod &&
          i.positionalArguments.length == 1 &&
          i.namedArguments.isEmpty) {
        return _mockFoo(i.positionalArguments[0]);
      } else if (i.isGetter) {
        return _mockFoo;
      }
    }
    return super.noSuchMethod(i);
  }
}

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

  1. The receiver has the static type dynamic.
  2. 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.

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 and declares no constructors. Unless you want your mixin to be usable as a regular class, use the mixin keyword instead of class. For example:

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

To specify that only certain types can use the mixin — for example, so your mixin can invoke a method that it doesn’t define — use on to specify the required superclass:

mixin MusicalPerformer on Musician {
  // ···
}

Version note: Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead. For more information on 2.1 mixin changes, see the Dart SDK changelog and 2.1 mixin specification.

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 = 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.

Asynchrony support

Handling Futures

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

  1. Use async and await.
  2. Use the Future API, as described in the library tour.

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.

Callable classes

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

void main() {
  var demo = CallableClass();
  print(demo(1, 2));
}

class CallableClass {
  int call(int a, int b) => a + b;
}

Async and await

The following diagram shows the flow of execution through the code. Each number corresponds to a step below.

diagram showing flow of control through the main() and printDailyNewsDigest functions
在這裏插入圖片描述

  1. The app begins executing.
  2. The main() function calls the async function printDailyNewsDigest(),
    which begins executing synchronously.
  3. printDailyNewsDigest() uses await to call the function
    gatherNewsReports(), which begins executing.
  4. The gatherNewsReports() function returns an uncompleted future (an
    instance of Future<String>).
  5. Because printDailyNewsDigest() is an async function and is awaiting
    a value, it pauses its execution and returns an uncompleted future
    (in this case, an instance of Future<void>) to its caller (main()).
  6. The remaining print functions execute. Because they’re synchronous,
    each function executes fully before moving on to the next print
    function. For example, the winning lottery numbers are all printed
    before the weather forecast is printed.
  7. When main() has finished executing, the asynchronous functions can
    resume execution. First, the future returned by gatherNewsReports()
    completes. Then printDailyNewsDigest() continues executing, printing
    the news.
  8. When the printDailyNewsDigest() function body finishes executing,
    the future that it originally returned completes, and the app exits.

Note that an async function starts executing right away (synchronously). The function suspends execution and returns an uncompleted future when it reaches the first occurrence of any of the following:

  • The function’s first await expression (after the function gets the uncompleted future from that expression).
  • Any return statement in the function.
  • The end of the function body.

只有執行完main方法下面的代碼後纔會執行那些await的操作!

async、async*、sync*

async與await配合使用;
sync*、async*與yield配合使用;

DO annotate with Object instead of dynamic to indicate any object is allowed.

Some operations work with any possible object. For example, a log() method could take any object and call toString() on it. Two types in Dart permit all values: Object and dynamic. However, they convey different things. If you simply want to state that you allow all objects, use Object, as you would in Java or C#.

Using dynamic sends a more complex signal. It may mean that Dart’s type system isn’t sophisticated enough to represent the set of types that are allowed, or that the values are coming from interop or otherwise outside of the purview of the static type system, or that you explicitly want runtime dynamism at that point in the program.

void log(Object object) {
  print(object.toString());
}

/// Returns a Boolean representation for [arg], which must
/// be a String or bool.
bool convertToBool(dynamic arg) {
  if (arg is bool) return arg;
  if (arg is String) return arg == 'true';
  throw ArgumentError('Cannot convert $arg to a bool.');
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章