Reference: the dart. Cn/guides/lang…

Overview of the Dart development language

This article introduces you to the main features of the Dart programming language, starting with variables and operators and using classes and libraries, assuming you already have experience programming in other languages.

You can learn more about the Dart core library by viewing the Dart library Overview. For more details on the language’s capabilities, see the Dart programming language specification.

Memo:

You can experience most of the language features of Dart with DartPad (to learn more). Open DartPad.

Embedded in this page are some examples of DartPads,

If you only see blank boxes (and no content), check out the DartPad FAQ page.

A simple Dart program

The following application code uses many of the basic Dart features:

// Define a function.
void printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// This is where the app starts executing.
void main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}
Copy the code

Here are the code snippets used in the above application that apply to all (or nearly all) Dart applications:

// This is a comment.

/ / comment.

A line that begins with a double slash is called a single-line comment. Dart also supports multi-line and document comments. See the comments for more information.

void

A special type indicating that a value is never used. Void functions like main() and printInteger() return types and do not return values.

int

Another data type that represents an integer number. Some other built-in types in Dart include String, List, and bool.

42

Represents a numeric literal. A numeric literal is a compile-time constant.

print()

A convenient way to display information output.

‘… ‘(or “…” )

Represents a string literal.

${$variableName (or expression})

Representation string interpolation: A variable or expression contained in a string literal. Consult the string for more information.

main()

A special and required top-level function from which the Dart application will always start. Consult the main() function for more information.

var

Used to define variables without specifying their type.

Memo:

The code for this site follows the conventions in the Dart Style Guide.

Important concepts

Here are some things to keep in mind as you learn the Dart language:

  • All variables refer toobjectEach object is oneclassThe instance. Numbers, functions, andnullThey’re all objects. All classes inherit fromObjectClass.
  • Although Dart is a strongly typed language, specifying a type when declaring a variable is optional because Dart can do type inference. In the code above, variablesnumberType is inferred to beintType. If you want to explicitly declare an indeterminate type, you canUse special typesdynamic.
  • Dart supports generics, such asList<int>(represents a list of int objects) orList<dynamic>Represents a list of objects of any type.
  • Dart supports top-level functions (e.gmainMethod), but also supports defining functions that belong to classes or objects (i.estatic 和 Instance methods). You can also define functions within functions (nested 或 Local function).
  • Dart supports top-level variables, as well as variables (static and instance variables) that are defined as belonging to classes or objects. Instance variables are sometimes called fields or properties.
  • Dart has nothing like Javapublic,protected 和 privateMember access qualifier. An identifier that begins with an underscore (_) is private in the library. You can refer toLibraries and visibilityFor more information.
  • identifierIt can start with a letter or underscore (_) and can be followed by a combination of characters and digits.
  • In the Dartexpression 和 statementsExpressions have values and statements do not. Such asConditional expression expression condition ? expr1 : expr2Contains the valueexpr1 或 expr2. withIf-else branch statementCompared to theif-elseA branch statement has no value. A statement usually contains one or more expressions, but an expression cannot contain only one statement.
  • The Dart tool displays both warning and error types of problems. Warnings indicate that there may be a problem with the code but do not prevent it from running. Errors are classified as compile-time errors and runtime errors; Compile-time error code does not run; Runtime errors cause exceptions while the code is running.

Memo:

If you’re wondering why Dart uses underscores instead of modifiers such as public or private, see SDK issue #33383.

The keyword

The following table lists the keywords used by the Dart language.

abstract 2 else import 2 super
as 2 enum in switch
assert export 2 interface 2 sync 1
async 1 extends is this
await 3 extension 2 library 2 throw
break external 2 mixin 2 true
case factory 2 new try
catch false null typedef 2
class final on 1 var
const finally operator 2 void
continue for part 2 while
covariant 2 Function 2 rethrow with
default get 2 return yield 3
deferred 2 hide 1 set 2  
do if show 1  
dynamic 2 implements 2 static 2  

You should avoid using these words as identifiers. However, words with superscript can be used as identifiers if necessary:

  • Keywords with superscript 1 are context keywords that make sense only in specific scenarios, and they can be used as valid identifiers anywhere.
  • Keywords with superscript 2 are built-in identifiers, just to make it easier to convert JavaScript code into Dart code. These keywords are valid identifiers most of the time, but they cannot be used as class or type names or as import prefixes.
  • With a superscript3Is used after Dart 1.0 is releasedSupport asynchronousRelated content. Cannot be in by keywordasync,async* 或 sync*Used in the method body of the identityawait 或 yieldAs an identifier.

Other keywords without superscript are reserved and cannot be used as identifiers.

variable

The following sample code creates a variable and initializes it:

var name = 'Bob';
Copy the code

Variables only store references to objects. Here the variable named name stores a reference to a String object, and “Bob” is the value of that object.

The name variable is inferred to be of type String, but you can specify a type for it. If an Object reference is not limited to a single type, it can be specified as Object or Dynamic according to the design guidelines.

dynamic name = 'Bob';
Copy the code

You can also specify the type:

String name = 'Bob';
Copy the code

Memo:

This article follows the advice in the Style advice guide to declare local variables through var rather than using specified types.

The default value

In Dart, uninitialized variables have a default initialization value: NULL. This is true even for numbers, because everything is an object in Dart, and numbers are no exception.

int lineCount;
assert(lineCount == null);
Copy the code

Memo:

Calls to assert() will be ignored in production code. During development, assert(condition) will throw an exception if the condition is judged to be false. See Assert for details.

Final and Const

If you don’t want to change a variable, you can decorate it with the keyword final or const, which can replace the var keyword or precede a specific type. A final variable can be assigned only once; A const variable is a compile-time constant (const variables are also final). A top-level final variable or a class’s final variable is initialized the first time it is used.

Memo:

Instance variables can be final but not const. Final instance variables must be initialized before the constructor starts, for example when the instance variable is declared, as a constructor parameter, or placed in the constructor’s initializer list.

In the following example we create and set two final variables:

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

You cannot change the value of a final variable:

name = 'Alice'; // Error: a final variable can only be set once.
Copy the code

The keyword const decorates a variable to indicate that it is a compile-time constant. If you use const to modify a variable in a class, you must add the static keyword, that is, static const. A const variable can be assigned directly when declared, or it can be assigned using another const variable:

const bar = 1000000; // Assign [Unit of pressure (dynes/cm2)] const double ATM = 1.01325 * bar; // Assign to other const variables (Standard atmosphere)Copy the code

The const keyword can be used not only to define constants, but also to create constant values that can be assigned to any variable. You can also declare constructors to be const. Constructors of this type create objects that are immutable.

var foo = const []; final bar = const []; const baz = []; // Equivalent to 'const []' (Equivalent to 'const []')Copy the code

We can omit the const keyword if we assign a constant using an initialization expression, such as baz above, which omits const. See don’t use const for redundancy.

The value of variables that are not decorated with final or const can be changed, even if they have previously referenced const values.

foo = [1, 2, 3]; // foo Was const [] (Was const [])Copy the code

The value of a constant cannot be modified:

baz = [42]; // Error: constants cannot be assigned. (Error: Constant variables can't be assigned a value.)Copy the code

You can use type checking and casts (is and AS) in constants, if in collections, and expansion operators (… And… ?). :

const Object i = 3; // Where i is a const Object with an int value... const list = [i as int]; // Use a typecast. const map = {if (i is int) i: "int"}; // Use is and collection if. const set = {if (list is List<int>) ... list}; / /... and a spread.Copy the code

Memo: Although a final object cannot be modified, its fields can be changed. In comparison, A const object and its fields cannot be changed: they’re immutable.

Consult Lists, Maps, and Classes for more information on creating constant values using const.

Built-in types

The Dart language supports the following types:

  • numbers
  • strings
  • booleans
  • Lists (also called listsarrays)
  • sets
  • maps
  • Runes (used to represent Unicode characters in strings)
  • symbols

You can initialize the above types directly using literals. For example, ‘This is a string’ is a string literal, true is a Boolean literal.

Since each variable reference in Dart points to an object (an instance of a class), you can usually initialize variables using constructors as well. Some built-in types have their own constructors. For example you can use Map() to create a Map object.

Numbers

Dart supports two Number types:

int

The integer value; The value contains a maximum of 64 characters and varies with platforms. On DartVM, the value is between -263 and 263-1. The Dart compiled into JavaScript uses JavaScript numbers, which are allowed to range from -253 to 253-1.

double

A 64-bit double – precision floating – point number that complies with IEEE 754 standards.

Int and double are subclasses of num. Num defines basic operators such as +, -, *, /, etc., as well as methods such as ABS (), ceil(), and floor() (bitwise operators such as >> are defined in int). If num and its subclasses don’t meet your requirements, look at the API in the DART: Math library.

Integers are numbers without a decimal point. Here are some examples of defining integer literals:

var x = 1;
var hex = 0xDEADBEEF;
Copy the code

If a number contains a decimal point, it is floating-point. Here are some examples of defining dimensions for floating-point numbers:

Var y = 1.1; Var exponents = 1.42 e5;Copy the code

Integer literals are automatically converted to floating-point numbers when necessary:

double z = 1; // Equivalent to double z = 1.0.
Copy the code

Version Tips:

Prior to Dart 2.1, it was an error to use integer literals in the context of floating point numbers.

Here’s how strings and numbers are converted:

// String -> int var one = int.parse('1'); assert(one == 1); // String -> double var onePointOne = double. Parse ('1.1'); Assert (onePointOne = = 1.1); // int -> String String oneAsString = 1.toString(); assert(oneAsString == '1'); // double -> String piAsString = 3.14159. ToStringAsFixed (2); Assert (piAsString = = '3.14');Copy the code

Integer support displacement of traditional operation, such as shift (< <, > >), bitwise and (&), bitwise or (|), such as:

assert((3 << 1) == 6); // 0011 << 1 == 0110 assert((3 >> 1) == 1); // 0011 >> 1 == 0001 assert((3 | 4) == 7); / / 0011 | 0100 = = 0111Copy the code

Numeric literals are compile-time constants. Many arithmetic expressions are compile-time constants as long as their operands are constant.

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;
Copy the code

Strings

The Dart string is a sequence of utF-16 encoded characters. Strings can be created using either single or double quotation marks:

var s1 = 'Single quotes work well for string literals.'; var s2 = "Double quotes work just as well."; var s3 = 'It\'s easy to escape the string delimiter.'; var s4 = "It's even easier to use the other delimiter."; Var s1 = 'creates a string literal using single quotes. '; Var s2 = "Double quotes can also be used to create string literals." ; Var s3 = 'When creating strings with single quotes, you can escape strings that conflict with single quotes with a slash: \'. '; Var s4 = "In double quotes, you don't need to escape strings that conflict with single quotes: '";Copy the code

In a string, use the expression as ${expression}, or omit {} if the expression is an identifier. If the expression results in an object, Dart calls the object’s toString method to get a string.

var s = 'string interpolation'; assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' + 'which is very handy.'); assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy! ' == 'That deserves all caps. ' + 'STRING INTERPOLATION is very handy! '); Var s = 'string interpolation '; Assert ('Dart has $s and is very convenient to use. '== 'Dart has string interpolation and is very handy to use. '); Assert (' use ${s.substring(3,5)} expressions is also handy '==' interpolation expressions are also handy. ');Copy the code

Memo:

The == operator determines whether the contents of two objects are the same. If two strings contain the same sequence of character encodings, they are equal.

You can concatenate strings using the + operator or by placing multiple strings side by side:

var s1 = 'String ' 'concatenation' " works even over line breaks."; assert(s1 == 'String concatenation works even over ' 'line breaks.'); var s2 = 'The + operator ' + 'works, as well.'; assert(s2 == 'The + operator works, as well.'); Var s1 = 'can concatenate' 'strings' "even if they are not on the same line." ; Assert (s1 == 'concatenates strings even if they are not on the same line. '); Var s2 = 'Use the plus sign + operator' + 'to achieve the same effect. '; Assert (s2 == 'Uses the plus + operator to achieve the same effect. ');Copy the code

It is also possible to create a multi-line string using three single quotes or three double quotes:

Var s1 = "" you can create a multi-line string like this. "'; Var s2 = """ This is also a multi-line string. "";Copy the code

Prefixes the string with r to create a “raw” string (that is, a string that will not be processed (such as escaped)) :

var s = r'In a raw string, not even \n gets special treatment.'; Var s = r' In raw strings, the escape string \n will print "\n" directly instead of escaping to a newline. ';Copy the code

You can consult Runes and Grapheme Clusters for more information on how to represent Unicode characters in strings.

A string literal is a compile-time constant, and any compile-time constant can be used as an interpolation of a string literal:

You can concatenate the following three constants into a string literal as string interpolation. (These work in a const string.) const aConstNum = 0; const aConstBool = true; const aConstString = 'a constant string'; // The following three constants cannot be concatenated to a string literal as string interpolation. var aNum = 0; var aBool = true; var aString = 'a string'; const aConstList = [1, 2, 3]; const validConstString = '$aConstNum $aConstBool $aConstString'; // const invalidConstString = '$aNum $aBool $aString $aConstList';Copy the code

See strings and regular expressions for more information on how to use strings.

Booleans

Dart uses the bool keyword to represent Boolean types, which have only two objects, true and false, both of which are compile-time constants.

Dart’s type safety does not allow you to check booleanvalues with code like if (nonbooleanValue) or Assert (nonbooleanValue). Instead, you should always check booleans explicitly, as in the following code:

// Check for an empty string. Var fullName = ''; assert(fullName.isEmpty); // Check whether the value is less than or equal to zero. var hitPoints = 0; assert(hitPoints <= 0); // Check for null. var unicorn; assert(unicorn == null); // Check if it is NaN. var iMeantToDoThis = 0 / 0; assert(iMeantToDoThis.isNaN);Copy the code

Lists

Arrays are the most common collection type in almost all programming languages, and arrays in Dart are represented by List objects. It’s usually called a List.

The List literal in Dart looks like the array literal in JavaScript. Here is an example of a Dart List:

var list = [1, 2, 3];
Copy the code

Memo:

Dart infer that list is of type list

, and an error is reported if you add a non-int object to the array. You can read type inference for more information.

You can add a comma after the last item of Dart’s collection type. The trailing comma does not affect the collection, but it is useful to avoid copy-and-paste errors.

var list = [
  'Car',
  'Boat',
  'Plane',
];
Copy the code

The index of List starts at 0, the index of the first element is 0, and the index of the last element is list.length-1. You can get the length and elements of the List in Dart as we do in JavaScript:

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);
Copy the code

Adding the const keyword to the List literal creates a compile-time constant:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.
Copy the code

Dart introduced the extension operator in 2.3 (…) And null-aware extension operators (… ?). , they provide a neat way to insert multiple elements into a collection.

For example, you can use the extension operator (…) Insert all elements from one List into another List:

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
Copy the code

If the right side of the extension operator may be NULL, you can use the null-aware extension operator (… ?). To avoid generating exceptions:

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
Copy the code

See extension operator advice for more information on how to use extension operators.

Dart also introduces both the if in collections and the for operation in collections, allowing you to use conditional judgments (if) and loops (for) when building collections.

The following example is an example of using if from a collection to create a List, which may contain three or four elements:

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];
Copy the code

Here is an example of adding a modified element from a list to another list using for in a collection:

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
Copy the code

For more details and examples of using if and for in collections, see using Control Flow advice in collections.

The List class has many handy ways to manipulate lists, and you can consult generics and collections for more information about them.

Sets

In Dart, a set is an unordered set of specific elements. Sets supported by Dart are provided by set Literals and set classes.

Version Tips:

Although Set types have always been a core feature of Dart, Set literals were added in Dart 2.2.

Here’s how to create a Set using a Set literal:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Copy the code

Memo:

Dart deduces that the Halogens variable is a Set of type Set

, and an error is reported if you add an incorrectly typed object to that Set. You can check out type inference for more on this.

We can create an empty Set by prefixing {} with a type argument, or assign {} to a variable of type Set:

var names = <String>{}; // create a Set of type +{}. // Set<String> names = {}; // Create Set (This works, too) as a type variable. // This Creates a Map rather than a Set (Creates a Map, not a Set.)Copy the code

 

The Set or the map? The Map literal syntax is similar to the Set literal syntax. Because of the Map literal syntax, {} defaults to Map. If you forget to comment out a type on {} or assign a value to an undeclared variable, Dart creates an object of type Map

.
,>

Add an item to an existing Set using the add() or addAll() method:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
Copy the code

Use.length to get the number of elements in a Set:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);
Copy the code

We can create a compile-time constant by adding the const keyword before a Set literal:

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // This line will cause an error.
Copy the code

Starting with Dart 2.3, sets can support the use of extended operators like lists (… And… ?). And Collection If and Collection For operations. You can consult the List extension operator and the List collection operator for more information.

You can also refer to generics and sets for more information.

Maps

In general, a Map is an object used to associate keys and values. Where keys and values can be objects of any type. Each key can only appear once but the value can be repeated many times. Dart maps provide two types of maps: Map literals and Map types.

Here are a couple of examples of creating a Map using a Map literal:

Var gifts = {// key: value 'first': 'partridge', 'second': 'Turtledoves ', 'fifth': 'golden rings'}; var nobleGases = { 2: 'helium', 10: 'neon', 18: 'argon', };Copy the code

Memo:

Dart deduces the gifts variable type to Map

, and nobleGases type to Map

. If you add incorrect type values to the two Map objects, it will cause a runtime exception. You can read type inference for more information.
,>
,>

You can also create a Map using the Map constructor:

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
Copy the code

Memo: If you come from a language like C# or Java, you might expect to see new Map() instead of just Map(). In Dart, the new keyword is optional. For details, see Using constructors.

If you used C# or Java before, you might want to construct a Map object using new Map(). But in Dart, the new keyword is optional. You can refer to the use of constructors for more information.

Adding key-value pairs to an existing Map is similar to JavaScript:

var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; // Add a key-value pairCopy the code

Retrieving a value from a Map is also similar to JavaScript.

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');
Copy the code

A null is returned if the retrieved Key does not exist in the Map:

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);
Copy the code

Use.length to get the number of key-value pairs in the Map:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);
Copy the code

Adding the const keyword to a Map literal creates a Map compile-time constant:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // This line will cause an error.
Copy the code

Maps, like lists, can support the use of extension operators (… And… ?). And the if and for operations on collections. You can consult the List extension operator and the List collection operator for more information.

You can also check out generics and Maps for more information.

With Runes and grapheme clusters

In Dart, Runes exposes Unicode code points for strings. The characters package is used to access and manipulate user-aware characters, also known as Unicode (extended) Grapheme clusters.

The Unicode encoding defines a unique numeric value for each letter, number, and symbol. Because the string in Dart is a SEQUENCE of UTF-16 characters, a special syntax is required if you want to represent 32-bit Unicode values.

A common way to represent Unicode characters is to use \uXXXX, where XXXX is a four-digit hexadecimal number. For example, the Unicode for the heart character (♥) is \u2665. For hexadecimal numbers that are not four digits, you need to enclose them in braces. For example, the laughing emoji (😆) has Unicode \u{1f600}.

If you need to read and write individual Unicode characters, use the Characters getter defined in the Characters package. It returns the Characters object as a string of Grapheme clusters. Here’s an example using the Characters API:

import 'package:characters/characters.dart'; . Var hi = 'hi 🇩🇰'; print(hi); print('The end of the string: ${hi.substring(hi.length - 1)}'); print('The last character: ${hi.characters.last}\n');Copy the code

The output depends on your environment and looks something like this:

Dart Hi 🇩🇰 The end of The string:?? The last character: 🇩 🇰Copy the code

For more information about manipulating strings with the Characters package, see the sample and API reference for the Characters package.

Memo:

Be careful when using Rune with List. Depending on the language, character set, etc., this can cause string problems. [How do I reverse a String in Dart?] .

Symbols

Symbol represents the declared operator or identifier in Dart. You almost never need symbols, but they are useful for apis that refer to identifiers by name, because when the code is compressed, the identifiers’ names change, but their symbols remain the same.

We can obtain the Symbol by prefixing the identifier with # :

#radix
#bar
Copy the code

The Symbol literal is a compile-time constant.

function

Dart is a true object-oriented language, so even functions are objects and of type Function, which means functions can be assigned to variables or as arguments to other functions. You can also call an instance of the Dart class as if it were a function. See callable classes for details.

Here is an example of defining a function:

bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] ! = null; }Copy the code

Although the Efficient Dart guide recommends defining return types on public apis, this function is still valid even if it is not:

isNoble(atomicNumber) { return _nobleGases[atomicNumber] ! = null; }Copy the code

If the function body contains only one expression, you can use shorthand syntax:

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

Syntax => Expressions are {return expressions; }, => is sometimes called the arrow function.

Memo:

In = > and; Can only be expressions, not statements. For example, you can’t put an if statement in it, but you can put conditional expressions.

parameter

Functions can take two forms of arguments: required and optional. The required parameters are defined before the parameter list, and the optional parameters are defined after the required parameters. Optional arguments can be named or positional.

Memo:

Some apis (especially the constructor of the Flutter control) only use named parameters, even if they are mandatory. See the next section for more information.

You can use [trailing comma] when passing parameters to or defining function parameters.

Named parameters

Named parameters default to optional unless they are specifically marked as required.

When you call a function, you can specify named arguments in the form of parameter names: parameter values. Such as:

enableFlags(bold: true, hidden: false);
Copy the code

When defining a function, use { } to specify named parameters:

When defining functions, use {param1, param2… } to specify named arguments:

/// Set the [bold] and [hidden] flags... /// Sets the [bold] and [hidden] flags... void enableFlags({bool bold, bool hidden}) {... }Copy the code

Although named parameters are one type of optional parameter, you can still use the @required annotation to indicate that a named parameter is required, in which case the caller must provide a value for the parameter. Such as:

const Scrollbar({Key key, @required Widget child})
Copy the code

If the caller wants to construct a Scrollbar object through the constructor of the Scrollbar without providing the child argument, it causes a compilation error.

The @required annotation is defined in the Meta Package and can be used by importing the Package :meta/meta.dart package.

Optional position parameter

Use [] to wrap a series of arguments as positional arguments:

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

Here is an example of calling the above function without optional arguments:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');
Copy the code

Here is an example of calling the above function with optional arguments:

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');
Copy the code

Default Parameter Value

You can use = to define default values for the function’s named and positional arguments. The default value must be a compile-time constant, or null if no default is specified.

The following is an example of setting optional parameter defaults:

/// Set the [bold] and [hidden] flags... /// Sets the [bold] and [hidden] flags ... void enableFlags({bool bold = false, bool hidden = false}) {... } // bold will be true; And hidden is going to be false. enableFlags(bold: true);Copy the code

 

Older versions of Dart code used the colon (:) instead of = to set the default values for named parameters. The reason is that at first the named argument only supported:. However, this support is now obsolete, so we recommend that you specify the default value only with = for now.

The next example will show you how to set defaults for positional parameters:

String say(String from, String msg, [String device = 'carrier pigeon']) { var result = '$from says $msg with a $device';  return result; } assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');Copy the code

List or Map can also be used as defaults. The following example defines a function called doStuff() and specifies a list and Map value for its arguments named List and Gifts.

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

The main () function

Each Dart program must have a main() top-level function as its entry point, which returns void and takes an optional argument of type List

.

Here is an example of the main() function for a Web application:

void main() { querySelector('#sample_text_id') .. text = 'Click me! '.. onClick.listen(reverseText); }Copy the code

Memo:

In the above code.. The syntax is called cascading invocation. Multiple operations can be performed on a single object using cascading access.

Here is an example of using the command line to access the main() function with arguments:

Dart args. Dart 1 test 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'); }Copy the code

You can define and parse command-line arguments by using argument libraries.

Functions are first-class objects

You can pass a function as an argument to another function. Such as:

void printElement(int element) { print(element); } var list = [1, 2, 3]; // Pass printElement as an argument. list.forEach(printElement);Copy the code

You can also assign a function to a variable, as in:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!! '; assert(loudify('hello') == '!!! HELLO !!! ');Copy the code

Anonymous functions are used in this example. More on this in the next section.

Anonymous functions

Most methods have names, such as main() or printElement(). You can create a nameless method called an anonymous function, a Lambda expression, or a Closure Closure. You can assign an anonymous method to a variable and then use it, such as adding the variable to or removing it from a collection.

Anonymous methods look similar to named methods in that arguments can be defined between parentheses, separated by commas.

The following contents in braces are the body of the function:

([[type] parameter [,...]]) {function body; };

The following code defines an anonymous method that takes only one parameter item and has no parameter type. This function is called for each element in the List, printing a string of element positions and values:

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

Click the Run button to execute the code.

If the function has only one return line, you can use the fat arrow abbreviation. Paste the following code into DartPad and click the Run button to verify that the two functions are the same.

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

Lexical scope

Dart is a lexical scoped language. Variables are scoped at code writing time, and variables defined in braces can only be accessed within braces, similar to Java.

Here is an example of a nested function with variables in multiple scopes:

bool topLevel = true; void main() { var insideMain = true; void myFunction() { var insideFunction = true; void nestedFunction() { var insideNestedFunction = true; assert(topLevel); assert(insideMain); assert(insideFunction); assert(insideNestedFunction); }}}Copy the code

Note that the nestedFunction() function can access all variables, including the top-level variable.

Lexical closures

A closure is a function object that can access variables in its lexical scope even if the function object is called outside its original scope.

A function can enclose variables defined in its scope. In the following example, the function makeAdder() captures the variable addBy. Whenever the function returns, it can use the captured addBy variable.

/// returns a function that adds [addBy] to the function argument. /// Returns a function that adds [addBy] to the /// function's argument. Function makeAdder(int addBy) { return (int i) => addBy + i; } void main() {// Generate a function plus 2. var add2 = makeAdder(2); // Generate a function that adds 4. var add4 = makeAdder(4); assert(add2(3) == 5); assert(add4(3) == 7); }Copy the code

Tests whether the functions are equal

Here is an example test of equality between top-level functions, static methods and sample methods:

Void foo() {// define the top level function (A top-level function) class A {static void bar() {} // define the static void baz() {} // define the instance method} void main() { var x; // Compare whether the top-level functions are equal. x = foo; assert(foo == x); // Compare static methods for equality. x = A.bar; assert(A.bar == x); // Compare instance methods for equality. var v = A(); Var w = A(); Var y = w; x = w.baz; // These two closures refer to the same instance object, so they are equal. assert(y.baz == x); // These two closures refer to different instance objects, so they are not equal. assert(v.baz ! = w.baz); }Copy the code

The return value

All functions return values. The last line of a function that does not display a return statement defaults to return NULL; .

foo() {}

assert(foo() == null);
Copy the code

The operator

Dart supports the following table operators. You can implement these operators as members of a class.

describe The operator
One yuan suffix Expression + + Expression -- (a) [] . ? .
One yuan prefix Expression - ! expression ~ expression + + expression Expression -
Method of, * / % ~ /
Addition and subtraction + -
An operation << >> >>>
The binary and &
Binary xOR ^
Binary or |
Relationship and type testing > = > < = < as is is!
Equal to the judge = = ! =
Logic and &&
Logic or ||
Empty judgment ??
Conditional expression Expression 1? Expression 2: Expression 3
cascade .
The assignment = * = / = + = - = & = ^ = And so on…

Please note:

The operator precedence above is modeled after the Dart parser’s behavior. For a more accurate description, see the syntax in the Dart language specification.

Once you use the operator, you create the expression. Here are some examples of operator expressions:

a++
a + b
a = b
a == b
c ? a : b
a is T
Copy the code

In the operator table, operators are ranked in order of precedence, that is, the first row has the highest priority, the last row has the lowest priority, and the left-most row has the highest priority and the right-most row has the lowest priority. For example, the % operator has precedence over ==, while == has precedence over &&. According to the priority rule, this means that the following two lines of code execute identically:

// Parentheses improve readability. // Parentheses improve readability. if ((n % i == 0) && (d % i == 0)) ... // Difficult to understand, but the same effect as the code above. if (n % i == 0 && d % i == 0) ...Copy the code

Please note:

For operators with two operands, the left-hand operand determines the function of the operator. For example, for aVector and aPoint, the expression aVector + aPoint uses the addition operator (+) defined in the Vector.

Arithmetic operator

Dart supports common arithmetic operators:

The operator describe
+ add
- Reduction of
Expression - Unary negative can also be used as an inversion (the sign of an inversion expression)
* take
/ In addition to
~ / In addition to integer
% modulus

Example:

assert(2 + 3 == 5); assert(2 - 3 == -1); assert(2 * 3 == 6); Assert (5/2 == 2.5); // The result is a float assert(5 ~/ 2 == 2); // The result is an integer assert(5%2 == 1); / / to take more than assert (5/2 = '${5 ~ / 2} r ${2} 5%' = = '5/2 = 2 r 1');Copy the code

Dart also supports increment and decrement operations.

Operator++var var = var + 1The value of the expression isvar + 1)
var++ var = var + 1The value of the expression isvar)
--var Var = var -1The value of the expression isVar - 1)
var-- Var = var -1The value of the expression isvar)

Example:

var a, b; a = 0; b = ++a; // Increment a by 1 before b is assigned. assert(a == b); // 1 == 1 a = 0; b = a++; // Increment a by 1 after b is assigned. assert(a ! = b); / / 1! = 0 a = 0; b = --a; // Decrement a by 1 before b is assigned. assert(a == b); // -1 == -1 a = 0; b = a--; // Subtract a by 1 after b is assigned. assert(a ! = b); / / - 1! = 0Copy the code

Relational operator

The following table lists relational operators and their meanings:

Operator= = equal
! = Ranging from
> Is greater than
< Less than
> = Greater than or equal to
< = Less than or equal to

To determine whether two objects x and y represent the same thing, use ==. (In rare cases, you may need to use the identical() function to determine whether two objects are identical.) . Here are some rules for the == operator:

  1. Let’s say I have variablesx 和 y, and at least one of x and y is null, x == y returns true if and only if both x and y are null, otherwise false if only one is null.
  2. x.==(y)Will return the value with or without y, that is, y is optional. That is to say,= =It’s actually a method in x, and it can be overridden. Please refer toRewriting operator.

The following code gives an example of each of these relational operators:

assert(2 == 2); assert(2 ! = 3); assert(3 > 2); assert(2 < 3); assert(3 >= 3); assert(2 <= 3);Copy the code

Type judgment operator

As, is, is! Operators are operators that determine the type of an object at run time.

Operator Meaning
as Type conversion (also used as a specificationClass prefix))
is Returns true if the object is of the specified type
is! Returns false if the object is of the specified type

Obj is T is true if and only if obj implements T’s interface. For example, obj is Object is always true, because all classes are subclasses of Object.

You can use the AS operator to convert an object to a specific type only if you are sure it is of that type. Such as:

(emp as Person).firstName = 'Bob';
Copy the code

If you are not sure if the object type is T, use is T to check the type before converting.

FirstName = 'Bob'; if (emp is Person) { }Copy the code

You can use the AS operator to abbreviate:

(emp as Person).firstName = 'Bob';
Copy the code

Memo:

There is a difference between the two methods: if the EMP is null or not of type Person, the first method will throw an exception and the second method will not.

The assignment operator

We can assign values using =, but we can also assign values using?? = to assign a value to a variable whose value is null.

// Assign value to a (Assign value to A) a = value; // assign b if and only if b is null. = value;Copy the code

Assignment operators like += combine arithmetic and assignment operators.

= - = / = % = > > = ^ =
+ = * = ~ / = < < = & = | =

The following table explains how the coincidence operators work:

scenario Compound operation Equivalent expression
Suppose you have an operatorop: a op= b a = a op b
Example: a += b a = a + b

The following example shows how to use assignment and the compound assignment operator:

var a = 2; // Assign a *= 3; Multiply: a = a * 3 assert(a == 6); multiply: a = a * 3 assert(a == 6);Copy the code

Logical operator

With logical operators you can reverse or combine Boolean expressions.

The operator describe
! expression Invert the result of the expression (true to false, false to true)
|| Logic or
&& Logic and

Here is an example of using a logical expression:

if (! done && (col == 0 || col == 3)) { // ... Do something... }Copy the code

Bitwise and shift operators

In Dart, the binary bit operator can operate on a bit of binary, but only on integers.

The operator describe
& Bitwise and
| Bitwise or
^ The bitwise exclusive or
~ expression Reverse by bit (that is, “0” becomes “1” and “1” becomes “0”)
<< A shift to the left
>> Who moves to the right

Here is an example using bitwise and shift operators:

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // 按位与 (AND)
assert((value & ~bitmask) == 0x20); // 取反后按位与 (AND NOT)
assert((value | bitmask) == 0x2f); // 按位或 (OR)
assert((value ^ bitmask) == 0x2d); // 按位异或 (XOR)
assert((value << 4) == 0x220); // 位左移 (Shift left)
assert((value >> 4) == 0x02); // 位右移 (Shift right)
Copy the code

Conditional expression

Dart has two special operators that can be used instead of if-else statements:

condition ? expr1 : expr2

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

Conditions? Expression 1: Expression 2: Executes expression 1 and returns the result if the condition is true, otherwise executes expression 2 and returns the result.

expr1 ?? expr2

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

Expression 1?? Expression 2: Returns the value of expression 1 if it is non-null, otherwise executes expression 2 and returns its value.

When determining assignments based on Boolean expressions, consider using? :.

var visibility = isPublic ? 'public' : 'private';
Copy the code

If the assignment is based on null, consider using?? .

String playerName(String name) => name ?? 'Guest';
Copy the code

The above example can also be written in at least two different forms, albeit less succinctly:

// Relative use? It's a bit long for the: operator. (Slightly longer version uses ? : operator). String playerName(String name) => name ! = null ? name : 'Guest'; // Longer if if-else is used. String playerName(String name) { if (name ! = null) { return name; } else { return 'Guest'; }}Copy the code

Cascade operators (..)

Cascade operators (..) Allows you to call variables or methods of multiple objects in succession on the same object.

For example:

QuerySelector ('#confirm') // Get an object.. Text = 'Confirm' // Use its members.. classes.add('important') .. onClick.listen((e) => window.alert('Confirmed! '));Copy the code

The first method, querySelector, returns a Selector object, and the subsequent cascade operators are members that call the Selector object and ignore the return value of each operation.

The code above is equivalent to:

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

Cascade operators can be nested, for example:

final addressBook = (AddressBookBuilder() .. name = 'jenny' .. email = '[email protected]' .. phone = (PhoneNumberBuilder() .. number = '415-555-0100' .. label = 'home') .build()) .build();Copy the code

Use cascade operators sparingly in functions that return objects. For example, the following code is incorrect:

var sb = StringBuffer(); sb.write('foo') .. write('bar'); // Error: void object has no method write (Error: method 'write' isn't defined for 'void').Copy the code

The sb.write() method in the above code returns void. Methods that return void cannot use cascade operators.

Memo:

Strictly speaking.. Cascading operations are not an operator but a special syntax of Dart.

Other operators

Most of the other operators have been used in other examples:

The operator The name describe
(a) Method of use Represents calling a method
[] Access to the List Access an element at a specific location in the List
. Access to members Member accessor
? . Conditional access member Similar to the above member accessors, except that the operation object on the left cannot be null, such as foo? Bar, which returns null if foo is null, bar otherwise

More about.? . And… For an introduction to operators, see class.

Flow control statement

You can use the following statements to control the flow of the Dart code:

  • if 和 else
  • forcycle
  • while 和 dowhilecycle
  • break 和 continue
  • switch 和 case
  • assert

The use of try-catch and throw can also affect the flow of control, as described in the exceptions section.

The If and the Else

Dart supports if-else statements, where else is optional, as in the following example. You can also refer to conditional expressions.

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}
Copy the code

Unlike JavaScript, conditions in Dart’s if statements must be Booleans and not other types. Check Boolean values for details.

The For loop

You can iterate using a standard for loop. Such as:

var message = StringBuffer('Dart is fun'); for (var i = 0; i < 5; i++) { message.write('! '); }Copy the code

In the Dart language, closures in the for loop automatically capture the index value of the loop to avoid some common pitfalls in JavaScript. Assume the following code:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
Copy the code

The code above prints 0 and 1, but if you execute the same code in JavaScript, it prints two 2’s.

ForEach () can be used if the object to iterate over implements the Iterable interface, or if indexes are not required, forEach is a good choice:

candidates.forEach((candidate) => candidate.interview());
Copy the code

Classes like List and Set that implement the Iterable interface also support for-in iteration:

var collection = [1, 2, 3]; for (var x in collection) { print(x); // 1 2 3}Copy the code

While and Do – the While

The while loop evaluates the condition before executing the loop:

while (! isDone()) { doSomething(); }Copy the code

A do-while loop executes the body of the loop before judging the condition:

do { printLine(); } while (! atEndOfPage());Copy the code

Break and Continue

Use break to break the loop:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}
Copy the code

Use continue to skip this loop and go straight to the next loop:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}
Copy the code

If you are using Iterable objects such as List or Set, you can rewrite the above example as follows:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());
Copy the code

The Switch and the Case

The Switch statement uses == in Dart to compare integers, strings, or compile-time constants. The two objects must be of the same type and cannot be subclasses and the == operator is not overridden. Enumerated types are ideal for use in Switch statements.

Memo:

The Switch statement in Dart is only suitable for limited situations, such as scenarios where an interpreter and scanner are used.

Each non-empty case clause must have a break statement. Non-empty case statements can also be terminated with a continue, throw, or return statement.

If no case statement is matched, the code in the default clause is executed:

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}
Copy the code

The following example ignores the break statement of the case clause and therefore generates an error:

var command = 'OPEN'; switch (command) { case 'OPEN': executeOpen(); // error: no break case 'CLOSED': executeClosed(); break; }Copy the code

However, Dart supports empty case statements, allowing them to be executed in fall-through form.

var command = 'CLOSED'; Switch (command) {case 'CLOSED': // Fall-through when the case statement is empty. Case 'NOW_CLOSED': // This statement is executed when the case condition is CLOSED and NOW_CLOSED. executeNowClosed(); break; }Copy the code

To implement fall-through in a non-empty case statement, use a continue statement with a label:

var command = 'CLOSED'; switch (command) { case 'CLOSED': executeClosed(); continue nowClosed; // Proceed with the case clause labeled nowClosed. NowClosed: case 'NOW_CLOSED': // This statement is executed when the case condition value is CLOSED and NOW_CLOSED. executeNowClosed(); break; }Copy the code

Each case clause can have local variables that are visible only within that case statement.

assertions

During development, you can use -assert (condition, optional information) when the condition expression is false; – statement to interrupt code execution. You can find plenty of examples of using Assert in this article. Here’s an example:

// Make sure the variable has a non-null value. Assert (text! = null); // Make sure the variable value is less than 100. assert(number < 100); // Make sure this is an HTTPS address. assert(urlString.startsWith('https'));Copy the code

The second argument to Assert adds a string message to it.

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');
Copy the code

The first argument to assert can be any expression with a Boolean value. If the value of the expression is true, the assertion is successful and execution continues. If the value of the expression is false, the assertion fails and an AssertionError is thrown.

How do I determine whether Assert works? Whether assert works depends on the development tool and the framework used:

In production code, assertions are ignored, and arguments passed in to assert are not judged.

abnormal

Dart code can throw and catch exceptions. Exceptions represent unknown error conditions that can be thrown if the exception is not caught causing the code that threw the exception to terminate execution.

Unlike Java, all Dart exceptions are non-mandatory, methods don’t have to declare which exceptions will be thrown, and you don’t have to catch any.

Dart provides two types of exceptions, Exception and Error, and a series of subclasses, and you can define your own. But Dart can throw any non-NULL object as an Exception, not just an Exception or Error type.

An exception is thrown

Here is an example of throwing or throwing an exception:

throw FormatException('Expected at least 1 section');
Copy the code

You can also throw arbitrary objects:

throw 'Out of llamas! ';Copy the code

Memo:

Good code often throws an Exception of type Error or Exception.

Because a thrown exception is an expression, it can be used in => statements, and it can be thrown anywhere else where expressions are used:

void distanceTo(Point other) => throw UnimplementedError();
Copy the code

Catch exceptions

Catching an exception prevents the exception from being passed on (except when it is rethrown). Catching an exception gives you the opportunity to handle it:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}
Copy the code

For code that can throw more than one exception type, you can also specify multiple catch statements, one for each exception type. If the catch statement does not specify an exception type, it means that any exception type can be caught:

try { breedMoreLlamas(); } on OutOfLlamasException {// specify exception buyMoreLlamas(); } on Exception catch (e) {print('Unknown Exception: $e'); } catch (e) {// // print('Something really unknown: $e'); }Copy the code

As shown above, you can use on or catch to catch exceptions, on to specify the exception type, catch to catch exception objects, and both.

You can specify two parameters for the catch method. The first parameter is the exception object thrown and the second parameter is the StackTrace object:

Try {// ··· ·} on Exception catch (e) {print('Exception details:\n $e'); } catch (e, s) { print('Exception details:\n $e'); print('Stack trace:\n $s'); }Copy the code

The keyword rethrow can throw the caught exception again:

void misbehave() { try { dynamic foo = true; print(foo++); } catch (e) {print('misbehave() unhandled ${e.runtimeType}.'); rethrow; // Allows the caller to view the exception. } } void main() { try { misbehave(); } catch (e) { print('main() finished handling ${e.runtimeType}.'); }}Copy the code

Finally

The finally statement is always executed whether or not an exception is thrown. If no catch statement is specified to catch the exception, the exception is thrown after the finally statement is executed:

try { breedMoreLlamas(); } finally {// Always clean up, even if an exception is thrown. cleanLlamaStalls(); }Copy the code

A finally statement is executed after any matching catch statement:

try { breedMoreLlamas(); } catch (e) { print('Error: $e'); // Handle exceptions first. } finally { cleanLlamaStalls(); // Then clean up. }Copy the code

You can read the Exceptions section of the Dart Core Library Overview for more information.

class

Dart is an object-oriented language that supports mixin-based inheritance. All objects are instances of a class, and all classes inherit from the Object class. Mixin-based inheritance means that although there is only one superclass for each class (except Object), the code of one class can be reused in multiple other class inheritance. The Extension method is a way to add functionality to a class without changing it or creating a subclass.

Use the members of the class

The members of an object consist of functions and data (that is, methods and instance variables). Method calls are made through the object, which has access to its functions and data.

Use (.) To access an instance variable or method of an object:

var p = Point(2, 2); // Get y assert(p.y == 2); // Call the distanceTo() method on p. double distance = p.distanceTo(Point(4, 4));Copy the code

Use? . Instead of. Avoids the problem caused by null on the left side of the expression:

// If p is non-null, set a variable equal to its y value. var a = p? .y;Copy the code

Using constructors

Constructors can be used to create an object. Constructors can be named by the class name or the class name. identifier. For example, the following code creates a Point object using the Point() and point.fromjson () constructors respectively:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
Copy the code

The following code has the same effect, but the new keyword before the constructor name is optional:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
Copy the code

Version Tips:

Starting with Dart 2, the new keyword is optional.

Some classes provide constant constructors. Use the constant constructor to create compile-time constants preceded by the const keyword:

var p = const ImmutablePoint(2, 2);
Copy the code

Two compile-time constants constructed with the same constructor and the same parameter values are the same object:

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

In constant context scenarios, you can omit the const keyword before a constructor or literal. For example, in the following example we create a constant Map:

// Lots of const keywords here. Const pointAndLine = const {'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)], };Copy the code

Depending on the context, you can keep only the first const keyword and omit all the rest:

// Only one const, which establishes the constant context. // Only one const keyword is required, the others are implicitly related to the context. const pointAndLine = { 'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], };Copy the code

However, if it is not possible to omit const from the context, the const keyword should not be omitted. Otherwise, a nonconstant object will be created. For example:

var a = const ImmutablePoint(1, 1); // Creates a constant (Creates a constant) var b = ImmutablePoint(1, 1); // Does NOT create a constant assert(! identical(a, b)); // These two variables are NOT the same instance!Copy the code

Version Tips:

Only starting with Dart 2 can the const keyword be omitted based on context judgment.

Gets the type of the object

The Type of an Object, which is an instance of Type, can be obtained at runtime using the runtimeType property of an Object.

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

So far, we’ve solved how to use classes. The rest of this section shows you how to implement a class.

The instance variables

Here is an example of declaring an instance variable:

class Point { double x; // Declare the double variable x and initialize it to null. double y; // declare double y and initialize to null double z = 0; // declare the double variable z and initialize to 0. }Copy the code

All uninitialized instance variables have a value of NULL.

All instance variables implicitly declare a Getter method, and non-final instance variables implicitly declare a Setter method. You can look at getters and setters for more information.

class Point { double x; double y; } void main() { var point = Point(); point.x = 4; // Use Setter methods for x. assert(point.x == 4); // Use the Getter method for x. assert(point.y == null); // Default value is null. }Copy the code

If you initialize an instance variable when you declare it (rather than in a constructor or other method), the value of the instance variable is set when the object instance is created, before the constructor and its list of initializers are executed.

The constructor

A constructor can be declared by declaring a function that has the same name as the class (you can also add additional identifiers to the named constructor). Most constructors take the form of generative constructors, which create an instance of a class:

class Point { double x, y; Point(double x, double y) {// There will be a better way to implement this logic. this.x = x; this.y = y; }}Copy the code

Use the this keyword to reference the current instance.

Memo:

Using this keyword makes sense if and only if there is a naming conflict, otherwise Dart ignores this keyword.

Assigning a value to an instance variable in a constructor is similar for most programming languages, but Dart provides a special syntactic sugar to simplify this step:

class Point { double x, y; // The syntax sugar used to set x and y before the constructor body is executed. Point(this.x, this.y); }Copy the code

Default constructor

If you don’t declare a constructor, Dart automatically generates a no-argument constructor that calls its parent’s no-argument constructor.

Constructors are not inherited

A subclass does not inherit the constructor of its parent class, and if the subclass does not declare a constructor, there is only one constructor with no arguments by default.

The named constructor

You can declare multiple named constructors for a class to express more explicit intent:

class Point { double x, y; Point(this.x, this.y); // name constructor point.origin () : x = 0, y = 0; }Copy the code

Remember that constructors cannot be inherited, which means that subclasses cannot inherit the parent’s named constructor. If you want to provide a named constructor in a subclass with the same name as the parent’s named constructor, you need to declare it explicitly in the subclass.

Call the parent class non-default constructor

By default, the subclass constructor invokes the anonymous no-arg constructor of the parent, and the call will be in front of the subclass constructor function body code execution, if there is a list of initialization subclass constructor, then the initialization list before invoking the constructor of the parent is carried out, in general, the three call sequence is as follows:

  1. Initialization list
  2. A parameterless constructor for the parent class
  3. Constructor of the current class

If the parent class does not have an anonymous, parameterless constructor, then the subclass must call one of the constructors of the parent class. Specifying a parent constructor for the subclass’s constructor needs only to be specified with (:) in front of the constructor body.

In the following example, the constructor of the Employee class calls the named constructor of the parent class Person. Click the Run button to execute the sample code.

Since the argument is passed to the parent constructor before the subclass constructor is executed, the argument can also be an expression, such as a function:

class Employee extends Person { Employee() : super.fromJson(defaultData); / /...}Copy the code

Please note:

Arguments passed to the parent constructor cannot use the this keyword, because at this point in the argument passing, the subclass constructor has not been executed and the instance object of the subclass has not been initialized, so all instance members cannot be accessed, but class members can.

Initialization list

In addition to calling the superclass constructor, instance variables can be initialized before the constructor body is executed. Each instance variable is separated by a comma.

// Initializer list sets instance variables before // The constructor body runs. // Use the initialization list to set instance variables before executing the constructor body. Point.fromJson(Map<String, double> json) : x = json['x'], y = json['y'] { print('In Point.fromJson(): ($x, $y)'); }Copy the code

Please note:

The statement to the right of initialize list expression = cannot use this keyword.

In development mode, you can use assert in the initialization list to validate input data:

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

It is very convenient to set final fields using the initializer list, which is used in the following example to set the values of three final variables. Click the Run button to execute the sample code.

Redirect constructor

Sometimes a constructor in a class can only be used to call other constructors in the class. This constructor does not have a function body, so you can use (:) after the function signature to specify the other constructors to be redirected to:

class Point { double x, y; // The primary constructor of the class. Point(this.x, this.y); // Delegate implementation to the main constructor. Point.alongXAxis(double x) : this(x, 0); }Copy the code

Constant constructor

If the objects generated by a class are immutable, you can make them compile-time constants when they are generated. You can do this by prefixing the class constructor with the const keyword and making sure that all instance variables are final.

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

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}
Copy the code

Instances created by constant constructors are not always constants, as described in the section Using constructors.

Factory constructor

Identifying the constructor of a class with the factory keyword makes the constructor a factory constructor, which means that using the constructor to construct instances of the class does not always return a new instance object. For example, a factory constructor might return an instance from the cache, or an instance of a subtype.

In the following example, the Logger factory constructor returns an object from the cache, and the Logger.fromjson factory constructor initializes a final variable from the JSON object.

class Logger { final String name; bool mute = false; // the _cache variable is library private because it is preceded by an underscore. 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); }}Copy the code

Memo:

This is not accessible in the factory constructor.

The factory constructor is called like any other constructor:

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

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
Copy the code

methods

Methods are functions that provide behavior for an object.

Instance methods

Object instance methods have access to instance variables and this. The distanceTo() method below is an example of an instance method:

import 'dart:math'; class Point { double x, y; Point(this.x, this.y); double distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); }}Copy the code

The operator

Operators are instance methods with special names. Dart allows you to define operators with the following names:

< + | []
> / ^ [] =
< = ~ / & ~
> = * << = =
- % >>  

Memo:

You may notice that some operators are not in the list, for example! =. Because they’re just grammatical sugar. The expression E1! = e2 is just! (e1 == e2) a syntactic sugar.

To represent the rewrite operator, we mark it with an operator identifier. Here is an example of overriding the + and – operators:

class Vector { final int x, y; Vector(this.x, this.y); Vector operator +(Vector v) => Vector(x + v.x, y + v.y); Vector operator -(Vector v) => Vector(x - v.x, y - v.y); Void main() {final v = Vector(2, 3); // Operator == and hashCode not shown. final w = Vector(2, 2); assert(v + w == Vector(4, 5)); assert(v - w == Vector(0, 1)); }Copy the code

Getter and Setter

Getters and setters are a pair of special methods for reading and writing properties of objects. As mentioned above, there is an implicit Getter for each property of an instance object, and a Setter for non-final properties. You can add getters and setters for additional attributes using the get and set keywords:

class Rectangle { double left, top, width, height; Rectangle(this.left, this.top, this.width, this.height); // Define two attributes generated by the calculation: right and bottom. double get right => left + width; set right(double value) => left = value - width; double get bottom => top + height; set bottom(double value) => top = value - height; } void main() { var rect = Rectangle(3, 4, 20, 15); assert(rect.left == 3); rect.right = 12; assert(rect.left == -8); }Copy the code

The advantage of using getters and setters is that you can use your instance variables and wrap them in methods over time without changing any code, meaning that you define them and then change them without affecting the original logic.

Memo:

Operators such as increment (++) execute correctly regardless of whether a Getter is defined. To avoid unnecessary exceptions, the operator calls the Getter once and stores its value in a temporary variable.

Abstract methods

Instance methods, getters, and setters can all be abstract. Defining an interface method without implementing it lets the class that implements it implement it. Abstract methods can only exist in abstract classes.

Use the semicolon (;) directly An alternative method body can declare an abstract method:

Abstract class Doer {// Define instance variables and methods... void doSomething(); // Define an abstract method. } class EffectiveDoer extends Doer {void doSomething() {// This method is not abstract anymore... }}Copy the code

An abstract class

Using the abstract keyword to identify a class makes it an abstract class that cannot be instantiated. Abstract classes are often used to declare interface methods and sometimes have concrete method implementations. If you want an abstract class to be instantiated at the same time, you can define a factory constructor for it.

Abstract classes often contain abstract methods. Here is an example of an abstract class that declares an abstract method:

// This class is declared abstract and thus // can't be instantiated. Abstract class AbstractContainer {// Define constructors, fields, methods, etc... void updateChildren(); // Abstract methods. }Copy the code

Implicit interface

Each class implicitly defines and implements an interface that contains all instance members of the class and other interfaces that the class implements. If you want to create A class A API that supports calling class B without inheriting from class B, you can implement A class B interface.

A class implements one or more interfaces and implements the API defined by each interface using the keyword implements:

// A person. The implicit interface contains The greet() method. The class Person {// _name variable is also included in the interface, but it is only visible in the library. final _name; // The constructor is not in the interface. Person(this._name); // Greet () on the interface. String greet(String who) => 'hello, $who. I am a $_name. '; } // An implementation of the Person interface. class Impostor implements Person { get _name => ''; String greet(String who) => 'hello $who. Do you know who I am? '; } String greetBob(Person Person) => person.greet(' Person '); Void main() {print(greetBob(Person(' small '))); print(greetBob(Impostor())); }Copy the code

If you need to implement multiple interface classes, you can separate each interface class with a comma:

class Point implements Comparable, Location {... }Copy the code

Extending a class

Create a subclass using the extends keyword and reference a parent class using the super keyword:

class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } class SmartTelevision extends Television {void turnOn() {super.turnon (); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } / /...}Copy the code

Overriding class member

Subclasses can override instance methods (including operators), getters, and setters of their parent classes. You can use the @override annotation to indicate that you have overridden a member:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}
Copy the code

You can use the covariant keyword to narrow down the type of method arguments or instance variables in your code that are type-safe.

Please note:

If you override the == operator, you must also override the Getter method for the object hashCode. You can refer to implementing mapping keys for more examples of rewriting == and hashCode.

NoSuchMethod method

If calling a method or instance variable that does not exist on the object will trigger the noSuchMethod method, you can override the noSuchMethod method to track and record this behavior:

Class A {// Unless you override noSuchMethod, calling A nonexistent member will cause NoSuchMethodError. @Override void noSuchMethod(Invocation) {print(' You tried using a non-existent member: '+ '${Invocation. MemberName}'); }}Copy the code

You can only call an unimplemented method if one of the following conditions is true:

  • The receiver is staticdynamicType.
  • The receiver has static typing, defines unimplemented methods (abstractions are also available), and the receiver’s dynamic typing is implementednoSuchMethodMethod and concrete implementation withObjectThe difference between.

You can refer to the noSuchMethod forwarding specification for more information.

Extension methods

The extension method is a way to add functionality to an existing library. You may already be using it without knowing that it is an extension method. For example, when you use code completion in your IDE, it recommends using the extended method along with the regular method.

Here is an example of using the extension method in String, which we call parseInt() and which is defined in string_apis. Dart:

import 'string_apis.dart'; . print('42'.padLeft(5)); // Use a String method. print('42'.parseInt()); // Use an extension method.Copy the code

For more information about using and implementing extension methods, see the Extension Methods page.

Enumerated type

Enumeration types are special types, also called Enumerations or enums, that define a fixed number of constant values.

Using enums

Use enumerated

Use the keyword enum to define enumeration types:

enum Color { red, green, blue }
Copy the code

You can use trailing commas when declaring enumerated types.

Each enumerated value has a Getter method called the index member variable, which returns the positional value of the base index at 0. For example, the index of the first enumeration value is 0 and the index of the second enumeration value is 1. And so on.

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

To get all enumeration values, use the values method of the enumeration class to get the list containing them:

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

You can use enumerations in Switch statements, but note that each case of an enumerated value must be handled. That is, each enumerated value must be a case clause, otherwise a warning will appear:

var aColor = Color.blue; Switch (aColor) {case color. red: print(' red! '); break; Case color. green: print(' Green as grass! '); break; Default: // Warning will appear without this statement. print(aColor); // 'Color.blue' }Copy the code

Enumerated types have two limitations:

  • Enumerations cannot be subclasses, they cannot be mixed in, and you cannot implement an enumeration.
  • An enumerated class cannot be explicitly instantiated.

You can consult the Dart programming language specification for more information.

Use mixins to add functionality to classes

Mixin is a method pattern for reusing code from a class in multiple inheritance.

Use the Mixin pattern using the with keyword followed by the name of the Mixin class:

Class Musician extends Person with Musical {// ···} Class Maestro extends Person with Musical, Aggressive, Demented { Maestro(String maestroName) { name = maestroName; canConduct = true; }}Copy the code

To implement a Mixin, create a class that inherits from Object and does not declare a constructor. Use the keyword mixin instead of class unless you want this class to be used as a normal class. Such as:

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

You can use the keyword on to specify which classes can use the Mixin class. For example, if there is A Mixin class A, but A can only be used by class B, you can define A like this:

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}
Copy the code

In the preceding code, only classes that extend or implement the Musician class can use the mixin MusicalPerformer. Because SingerDancer extends MusicianSingerDancer can mix in MusicalPerformer.

Version Tips:

The mixin keyword is only referenced in Dart 2.1. Code in earlier versions usually uses abstract class instead. You can consult the Dart SDK change log and the 2.1 Mixin specification for more information about mixins changing in 2.1.

Class variables and methods

The static keyword is used to declare class variables or class methods.

A static variable

Static variables (class variables) are often used to declare state variables and constants that belong within the scope of a class:

class Queue { static const initialCapacity = 16; // ···} void main() {assert(queue.initialCapacity == 16); }Copy the code

Static variables are initialized the first time they are used.

Memo:

The code follows the naming rules in the style recommendation guide and uses camel case to name constants.

A static method

Static methods (that is, class methods) cannot operate on instances, so you cannot use this. But they can access static variables. You can call static methods directly on a class, as shown in the following example:

import 'dart:math'; class Point { double x, y; Point(this.x, this.y); static double 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 = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); Assert (2.8 < distance && distance < 2.9); print(distance); }Copy the code

Memo:

For some generic or common static methods, you should define them as top-level functions rather than static methods.

Static methods can be used as compile-time constants. For example, you can pass a static method as an argument to a constant constructor.

The generic

If you look at the API documentation for arrays, you’ll see that the array List is actually of type List

. <… The > symbol indicates that an array is a generic (or parameterized) type, usually using a single letter to represent type parameters such as E, T, S, K, and V.

Why generics?

Generics are often used when type safety is required, but they can also be beneficial to code execution:

  • Specifying generics properly can help code generation better.
  • Using generics reduces code duplication.

For example, if you want to declare an array that can only contain strings, you can declare the array as List

(pronounced “List of strings”). Then you can easily avoid the problems of putting non-string variables in the array. Meanwhile, the compiler and anyone else reading the code can easily find and locate problems:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
Copy the code

Another reason to use generics is to reduce code duplication. Generics allow you to share the same interface declaration between multiple implementations of different types. For example, in the following example, a class declares an interface for caching objects:

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

Before long, you might want to do a special cache for String objects, so there’s a new class for String caching:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}
Copy the code

If, at some point, you want to create a class for numeric types, there’s a lot of code like this…

Consider using generics to declare a class that can be implemented differently by different types of cache implementations:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}
Copy the code

In the code above, T is an alternative type. It acts as a type placeholder that specifies the type when the developer calls the interface.

Use collection literals

List, Set, and Map literals can also be parameterized. To define a parameterized List, simply add

before the brackets; To define a parameterized Map, add

before the braces:
,>

Var names = <String>[' xiao Yun ', 'Xiao Fang ',' Xiao Min ']; Var uniqueNames = <String>{' uniqueNames ', 'uniqueNames ',' uniqueNames '}; Var pages = <String, String>{'index.html': 'homepage ', 'robots.txt': 'humans. TXT ':' we are human, not machine '};Copy the code

Use type-parameterized constructors

You can also use generics when calling constructors by enclosing Angle brackets after the class name (<… >) wrap one or more types:

var nameSet = Set<String>.from(names);
Copy the code

Create a Map object with a key of type Int and a value of type View:

var views = Map<int, View>();
Copy the code

A collection of generic types and the types they contain

Dart’s generic types are solidified, which means that the type information is retained even at runtime:

var names = List<String>(); Names.addall ([' xiao Yun ', 'Xiao Fang ',' Xiao Min ']); print(names is List<String>); // trueCopy the code

Memo:

Unlike Java, generics in Java are type erased, which means that generic types are removed at run time. In Java you can tell if an object is a List but you can’t tell if an object is a List

.

Restrict parameterized types

When using generics, you might want to limit the type range of a generic. Use the extends keyword:

Class Foo<T extends SomeBaseClass> { String toString() => "instance of 'Foo<$T>' "; } class Extender extends SomeBaseClass {... }Copy the code

Use SomeBaseClass or a subclass of SomeBaseClass as a generic parameter:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Copy the code

Foo

= SomeBaseClass

var foo = Foo(); print(foo); // Instance of 'Foo<SomeBaseClass>'Copy the code

Using a non-someBaseclass type as a generic parameter causes a compilation error:

var foo = Foo<Object>();
Copy the code

Use generic methods

Dart originally only supported specifying generics for class declarations, but now it is also possible to use generics for methods, called generic methods:

T first<T>(List<T> ts) {// Handle some initialization work or error detection...... T tmp = ts[0]; // Handle some extra checks... return tmp; }Copy the code

The generic T of the method first

can be used as follows:

  • Function return value type (T).
  • Parameter type (List<T>).
  • Type of local variable (T tmp).

You can learn more about generics by looking at using generic functions.

Libraries and visibility

Import and library keywords can help you create a modular and shareable code base. The code base not only provides the API but also acts as a wrapper: members beginning with an underscore (_) are visible only in the code base. Each Dart program is a library, even if it is not specified using the keyword library.

Dart’s libraries can be published and deployed using package tools.

Use the library

Use import to specify the namespace so that other libraries can access it.

For example, you can import the dart: HTML library to use the DART Web API:

import 'dart:html';
Copy the code

The only argument to import is the URI used to specify the code base, in the form Dart: XXXXXX for libraries built into Dart. For other libraries, you can use a file system path or package: XXXXXX. The library specified by package: XXXXXX is provided through a package manager (such as the pub utility) :

import 'package:test/test.dart';
Copy the code

Memo:

A URI stands for uniform Resource Identifier (URI).

A URL (Uniform resource Locator) is a common URI.

Specifying the library prefix

If two code bases you imported have conflicting identifiers, you can specify a prefix for one of them. For example, if library1 and library2 both have Element classes, we could do this:

import 'package:lib1/lib1.dart'; import 'package:lib2/lib2.dart' as lib2; // Use the Element class lib1. Element element1 = Element(); // Use the Element class lib2. lib2.Element element2 = lib2.Element();Copy the code

Import part of the library

If you only want to use part of the code base, you can selectively import the code base. Such as:

// Import only foo in lib1. (Import only foo). import 'package:lib1/lib1.dart' show foo; // Import all of lib2 except foo. import 'package:lib2/lib2.dart' hide foo;Copy the code

Lazy-loaded library

Lazy loading (also known as lazy loading) allows an application to load its code base as needed. Here are some scenarios where lazy loading might be used:

  • To reduce application initialization time.
  • Handle A/B testing, such as testing different implementations of various algorithms.
  • Load features that are rarely used, such as optional screens and dialog boxes.

 

Lazy-loading is currently supported only by Dart 2JS, Dart VM and DartDevc. Check out Issue #33118 and Issue #27776 for more information.

Use the deferred as keyword to identify codebase that needs to be loaded late:

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

Call the loadLibrary function to load the library when you actually need to use the library API:

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

In the previous code, use the await keyword to suspend code execution until the library loads. See Async support for more information on async and await.

It doesn’t matter that the loadLibrary function can be called multiple times; the code base is only loaded once.

Here are some things to keep in mind when using lazy loading:

  • Constants in a lazy-loaded code base need to be imported when the code base is loaded, not when it is not loaded.
  • Lazy load library types cannot be used when importing files. If you need to use types, consider moving the interface type into another library and having both libraries import it separately.
  • Dart implicitly willloadLibraryMethod imported into useDeferred AS namespaceIn the class.loadLibraryThe function returns aFuture.

To realize library

Refer to Creating dependent library packages for suggestions on how to implement library packages, including:

  • How to organize the source files of the library.
  • How to useexportCommand.
  • When to usepartCommand.
  • When to uselibraryCommand.
  • How to implement multi-platform library support using dump and export commands.

Asynchronous support

The Dart code base has a large number of functions that return Future or Stream objects. These functions are asynchronous, and they return directly before time-consuming operations (such as I/O) complete rather than waiting for time-consuming operations to complete.

The async and await keywords are used to implement asynchronous programming and make your code look synchronous.

To deal with the Future

You can obtain the result of a Future execution in two ways:

  • useasync 和 await;
  • Using the Future API, detail the reference library overview.

Code that uses async and await is asynchronous, but looks a bit like synchronous code. For example, the following code uses await to wait for the execution result of an asynchronous function.

await lookUpVersion();
Copy the code

We must use await in asynchronous functions with the async keyword:

Future checkVersion() async { var version = await lookUpVersion(); // continue processing logic with version}Copy the code

Memo:

Although an asynchronous function can handle time-consuming operations, it does not wait for these time-consuming operations to complete. An asynchronous function executes and returns a Future object when it encounters the first await expression (line of code), and then waits for the await expression to complete.

Use try, catch, and finally to handle exceptions caused by using await:

try { version = await lookUpVersion(); } catch (e) {// The version cannot be found.Copy the code

You can use the await keyword multiple times in asynchronous functions. For example, the following code waits for the result of a function three times:

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

The return value of an await expression is usually a Future object; If not, it will automatically wrap it in a Future object. The Future object represents a "promise" and await expression blocks until the desired object returns.

If using await results in a compile error, make sure await is used in an asynchronous function. For example, if you want to use await in the main() function, the main() function must be identified with the async keyword.

Future main() async { checkVersion(); Print (' execute in Main: version is ${await lookUpVersion()}'); }Copy the code

Declaring asynchronous functions

An asynchronous function is a function whose body is marked by the async keyword.

Add the keyword async to the function and have it return a Future object. Suppose there are methods that return String:

String lookUpVersion () = > '1.0.0';Copy the code

Change it to an asynchronous function that returns Future:

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

Note that the function body does not need to use the Future API. Dart creates Future objects if necessary.

If the function does not return a valid value, set its return type to Future

.

For an introduction to the use of Future, async, and await, see this codelab: Asynchronous Programming codelab.

To deal with the Stream

If you want to get a value from a Stream, you have two options:

  • useasyncKeywords and oneAsynchronous loop(using theawait forKeyword identification).
  • Use the Stream API. Refer to the library overview for details.

Memo:

Before using the await for keyword, make sure it makes the code logic clearer and that you really need to wait for all results to complete. For example, you should not normally use the await for keyword on UI event listeners because the stream of events emitted by the UI framework is infinite.

Defining an asynchronous loop with await for looks like this:

Await for (varOrType identifier in expression) {// Execute whenever Stream emits a value}Copy the code

The expression must be of type Stream. The execution process is as follows:

  1. Wait until the Stream returns a data.
  2. Execute the body of the loop with the data returned from Stream 1.
  3. Repeat procedures 1 and 2 until the Stream data is returned.

Use the break and return statements to stop receiving Stream data, thus breaking out of the loop and unregistering to listen on the Stream.

If you encounter a compile-time error while implementing an asynchronous for loop, check to ensure that await for is in an asynchronous function. For example, to use an asynchronous for loop in an application’s main() function, the body of the main() function must be marked async:

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

You can consult the Dart: Async section of the library overview for more information on asynchronous programming.

The generator

Consider using generator functions when you need to generate a string of values lazily. Dart has built-in support for two forms of generator methods:

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

Implement a synchro generator function that uses yield statements to pass values by adding the sync* keyword to the function and setting the return value type to Iterable:

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

Implementing an asynchronous generator function is similar to synchronizing, except that the keyword async* and the return value Stream are:

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

If the generator is called recursively, use yield* statements to improve execution performance:

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

Can be called class

By implementing the call() method of the class, you allow instances of the class to be used in a manner similar to function calls.

In the following example, the WannabeFunction class defines a call() function that takes three string arguments, and its body concatenates three strings separated by Spaces, with an exclamation mark appended at the end. Click the Run button to execute the code.

isolation

Multicore cpus are used in most computers, even on mobile platforms. To take advantage of multi-core capabilities, developers typically use shared memory to allow threads to run concurrently. However, sharing data with multiple threads often causes many potential problems and causes code to run incorrectly.

Dart uses the ISOLATE instead of threads to solve the concurrency problem caused by multiple threads, and all Dart code runs in one ISOLATE. Each ISOLATE has its own heap memory to ensure that its status is not accessed by other isolates.

You can consult the documentation below for more information:

The type definition

In the Dart language, functions are objects like strings and numbers, and you can use a type definition (or method type alias) to name the type of the function. When a function of the function type is assigned to a variable using function naming, the type definition preserves the associated type information.

For example, the following code does not use a type definition:

class SortedCollection { Function compare; SortedCollection(int f(Object a, Object b)) { compare = f; }} // Simple incomplete implementation. int sort(Object a, Object b) => 0; void main() { SortedCollection coll = SortedCollection(sort); // We know that compare is a function, but we don't know what function it is. assert(coll.compare is Function); }Copy the code

In this code, the type of the Function is lost when f is assigned to compare. The type of the Function is (Object, Object) → int (→ return), which is a subclass of Function. But when f is assigned to compare, the type of f (Object, Object) → int is lost. We can use typedefs to explicitly retain type information:

typedef Compare = int Function(Object a, Object b); class SortedCollection { Compare compare; SortedCollection(this.compare); } // Simple incomplete implementation. int sort(Object a, Object b) => 0; void main() { SortedCollection coll = SortedCollection(sort); assert(coll.compare is Function); assert(coll.compare is Compare); }Copy the code

Memo:

Currently, type definitions can only be used for function types, but that may change in the future.

Because a type definition is just an alias, we can use it to determine the method of any function type:

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!
}
Copy the code

metadata

Using metadata adds some additional information to your code. Metadata annotations start with @, followed by a compile-time constant (such as deprecated) or call a constant constructor.

Dart has two annotations that all code can use: @deprecated and @Override. You can see extending a class to get an example of the use of @Override. Here is an example using @deprecated:

Class Television {/// _ deprecated: use [turnOn] instead of _ @ Deprecated void activate() {turnOn(); } // turn on the power of the TV. void turnOn() {... }}Copy the code

You can customize metadata annotations. The following example defines an @todo annotation with two parameters:

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}
Copy the code

Examples of @todo annotations:

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}
Copy the code

Metadata can be used before library, class, typedef, type Parameter, constructor, factory, function, field, parameter, or variable declarations. It can also be used before import or export. You can use reflection to retrieve metadata information at run time.

annotation

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

Single-line comments

Single-line comments start with //. Anything between // and the end of the line is ignored by the compiler.

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

Multiline comment

Multi-line comments start with /* and end with */. Anything between /* and */ is ignored by the compiler (document comments are not ignored), and multi-line comments can be nested.

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}
Copy the code

Documentation comments

A document comment can be a multi-line or single-line comment, starting with /// or /**. Using // on consecutive lines has the same effect as multi-line document comments.

In a document comment, the Dart compiler ignores all text unless you enclose it in brackets. Use brackets to refer to classes, methods, fields, top-level variables, functions, and parameters. The symbols in parentheses are resolved in the lexical field of the documented program element.

Here is a document comment that references other classes and members:

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

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

Parse the Dart code and generate HTML documents. For examples of generating documents, refer to Dart API Documentation for recommendations on document structure, refer to the documentation: Guidelines for Dart Doc Comments.

conclusion

This page provides an overview of the features commonly used in the Dart language. There are more features to be implemented, but we hope they won’t break existing code. For more information, refer to the Dart language specification and the Efficient Dart Language Guide.

To learn more about the Dart core library, refer to the Dart Core Library Overview