preface

Because the Dart official document is not very detailed and has too many language features, I often get stuck on a certain point when reading the official document. Therefore, I complete this introduction to Dart grammar by consulting various materials.

All the information in this article comes from the OFFICIAL Dart documentation, the Dart blog, etc. The purpose of this article is to help students who want to learn about Flutter master Dart language features or solve some questions about Dart.

If you know Typescript or Java, you can quickly read the Dart features. You just need to learn the syntax differences.

If you only know JavaScript, this article is for you, and you’ll get an in-depth look at all of Dart’s features. I sometimes compare Dart to JavaScript.

If you already know Dart but sometimes forget the grammar, you can quickly find out what you want to know.

I hope this article has been useful to you who use or will soon use Dart development.

No more words, let’s go!

A simple Dart program

In DART, we need to run the program inside main, and all code needs to be written inside main to be called.

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

// The position where the application starts execution must be written
void main() {
  var number = 42; // Declare and initialize a value
  printInteger(number); // Execute a function
}
Copy the code

The above code contains the following:

/ / : a comment

Viod: the function returns no value

Var: Lets Dart automatically determine how a type is declared

Print () : Prints the content

Int: indicates the integer number type


v a r i a b l e N a m e or * variableName * or
{expression} : string interpolation, string variables containing variables or expressions

Main () : Special and required top-level function from which Dart will start execution

Important concepts

  1. All variables refer to objects, and each object is an instance of a class. Numbers, functions, and Nulls are all objects. With the exception of NULL (if null security is enabled), all classes inherit from the Object class.
  2. Although Dart is strongly typed, it is possible to declare variables without specifying a type, and Dart has the same type inference as Typescript.
  3. If null security is enabled, variables cannot be null if they are not declared as nullable types. You can use the type suffix (?) Declare the type as nullable. For example, int? Variables can be integer numbers or NULL.
  4. If Dart thinks an expression may be null, but you know it can’t be null, you can use an assertion (!) To mean not empty. For example: int x=nullableButNotNullInt!
  5. If we want to explicitly allow any type, we can use Object? (with null security enabled), Object, or the special type Dynamic
  6. Dart supports generics, such as arraysList<int>Represents a list or list of int objectsList<Object>Represents a list of objects of any type
  7. Dart supports top-level variables as well as variables (static and instance variables) that define a class or object. Instance variables can be called attributes.
  8. Dart does not have public, protected, or private member access qualifiers. To represent private, use_Precede the variable declaration as an identifier.
  9. Variable declarations, like in other languages, can start with a letter or underscore followed by a combination of characters and numbers.
  10. Datr expressions have values, statements have no values. Like conditional expressionsexpression condition ? expr1 : expr2Contains the valueexpr1 expr2.if-elseA branch statement has no value.
  11. The Dart tool displays both warning and error types of problems. Warnings indicate a problem with the code but do not prevent it from running; Errors include compile errors and run errors. Compile errors cause code to fail to run, and run errors cause exceptions at run time.

Variable declarations

Variable declarations can be made as follows:

var str='hello dart';
var num=123;
// A string of characters
String str='hello dart';
// Number type
Int num=123;
Copy the code

Using var, you can infer the type automatically, or you can write the type String manually, as Java does.

Automatic inference is preferred, as recommended by the efficient guidelines from the OFFICIAL Dart language

If a reference to an Object can be of any type, as with ANY for TS, then Object or Dynamic can be specified

  Object a = 'blob';
  a = 123;
  a = [];
  dynamic b = 'blob';
  b = 123;
  b = [];
Copy the code

Difference between Object and Dynamic

Object is the base class of all classes and is equivalent to a supertype that is compatible with all types. Dynamic is a dynamic class, similar to TypeScript’s any.

In the following code, an Object declaration would fail compilation.

  Object a = 'String';
  a.subString(1); //❌The method 'subString' isn't defined for the type 'Object'
Copy the code

Changing to Dynamic indicates that this is a dynamic type, bypassing compilation checks.

  dynamic a = 'String';
  a.substring(1);
Copy the code

Final vs. const

Constants can be declared with final and const modifiers, which can either replace var or precede the type.

final name='some name';
const age=20;
const int age = 123;
final List list = [];
Copy the code

Final contains the function of const. The difference is that:

  • Final can be assigned initially and only once if it is assigned. Const needs to be assigned to begin with
  • Const must be given an explicit compile constant value (that is, a value determined at compile time)
  • Final can obtain a value (that is, a value determined at run time) by evaluating/functions
  • Final not only has the property of being a const compile-time constant, but it is lazy-initialized, that is, not initialized until it is first used at run time

For example

  // The constant 'a' must be initialized is initialized.
	const a; 
  // Error initialized Const variables must be initialized with a constant value
  const a = new DateTime.now();

  final b;
  b = new DateTime.now(); // No error will be reported
Copy the code

Const variables are compile-time constants. If you use const to modify a variable in a class, you must add the static keyword, that is, static const.

const

A const variable can be assigned directly when declared, or it can be assigned using another const variable.

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

The const keyword is used not only to define constants, but also to create constant values that can be assigned to any variable. A constructor of this type can also be declared to be const; the object created by this type of constructor is immutable.

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

Assigning a constant using an initialization expression omits the const keyword. For example, assigning the constant bar omits const.

It is also possible to create a singleton-like object using const variables, such as the following syntax, which creates the same object

class Person {
  final String name;
  const Person(this.name);
}

void main(List<String> args) {
  const p1 = const Person('myname');
  const p2 = Person('myname'); // const can be omitted
  print(identical(p1, p2));// These two objects are equal
}
Copy the code

The default value

The default value for uninitialized and nullable variables is NULL.

int? lineCount;
assert(lineCount == null);
var a;
assert(a == null);
Copy the code

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.

If null security is enabled, a variable must be initialized before you can use it

  int count;
  // The non-nullable local variable 'count' must be assigned before it can be used. Local variables that are not empty must be assigned before they are used
  print(count);
Copy the code

We don’t have to initialize the variable at first declaration, but we need to assign it before we use it. For example, the following code is valid because Dart detects that count is an assigned variable before it is passed to print for use

  int count;
  count = 0;
  print(count);
Copy the code

Top-level and class variables are lazily initialized: the initialization code runs when the variable is first used.

The data type

  • Numbers: int and double

  • Strings Type: String

  • Booleans type: bool

  • Lists Type: List

  • Maps type: Map type

  • Sets: Sets

  • Symbols type: Symbol

  • Null: Null

  • Runes: Commonly used for character substitution in the Characters API

Each variable reference in Dart points to an object, and a constructor can also be used to initialize variables. Some built-in types have their own constructors, such as using Map() to create a Map object.

There are some types of special roles in Dart:

  1. Object: is a superclass of all types except Null
  2. Future and Stream: Used asynchronously
  3. Iterable: Generator constructor for for-in loops and synchronization
  4. Never: Indicates that the expression can Never be reached. Usually used when a function throws an exception.
  5. Dynamic: This type can be used if you want to disable static checking. Usually you can use Object or Object? Instead.
  6. Void: Indicates that no value is returned

Numbers

Int is an integer and double is a floating-point number.

Numeric types are subclasses of num. Num defines some basic operators and methods. You can also look at the APIS in the DART: Math library.

Double is compatible with integers, meaning that a double can be either an integer or a floating-point number.

If you want to declare that a type is a number, either an integer or a floating-point number, you can use the num type

  num x = 1;
  x += 2.5;
  print(x); / / 3.5
Copy the code

If you declare a double but the value is an integer, it is automatically converted to a float.

  double a = 1;
  print(a); / / 1.0
Copy the code

Number to string:toString()

  int a = 123;
  double b = 123.23;
  String _a = a.toString();
  assert(_a == '123');
  String _b = b.toStringAsFixed(1);
  assert(_a == '123.2');
Copy the code

String to number:parse

  String a = '123';
  String b = '123.23';
  int _a = int.parse(a);
  assert(_a == 123);
  double _b = double.parse(b);
  assert(_b == 123.23);
Copy the code

It is recommended to use double. Parse to convert to numbers

Strings

Strings can be quoted in single/double quotes, single quotes inside double quotes can be escaped, and vice versa.

var s1 = 'Creates string literals 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 use a slash to escape strings that conflict with single quotes: \'. ';
var s4 = "In double quotes, you don't need to escape strings that conflict with single quotes: '";
Copy the code

String concatenation with $or ${} or +, the specific use of ${expression}, if a single variable, can be omitted {}. If the expression results in an object, Dart automatically calls the object’s toString method to get a string.

  var s = 'this is string';
  var map = {"name": "qiuyanxi"};
  print(The value of 'is:${s.toUpperCase()}The value of the map is$map');
  THIS IS STRING map {name: qiuyanxi}
Copy the code

Using three single quotes or three double quotes creates a multi-line string.

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

var s2 = """This is also a
multi-line string.""";
Copy the code

If you want nothing to be done to the contents of the string (such as escaping), you can create a RAW string by prefixing the string with r.

var s = R 'In raw strings, the escaped string \n will print "\n" directly instead of escaping to a newline. ';
Copy the code

Compile-time constants (null, number, string, Boolean) can be interpolated as string literals only if the string is a compile-time constant declared by const.

// These are available in string constants declared by const
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These cannot be used in const strings, only var declarations are useful
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';
var invalidConstString = '$aNum $aBool $aString $aConstList';
Copy the code

booleans

Bool Indicates a Boolean type

Dart’s conditional judgment is different from JavaScript, which can use Falsy or Truthy values for conditional judgment

0, ' ',false,undefined,null, -0,NaN // Falsy value of javascript

/ / such as:
let a;
if(! a) {console.log(` a is${a}`); / / a is undefined
}
Copy the code

Dart requires a bool or a return bool to make a conditional judgment.

Bool type

  String? a = null;
  // Conditions must have a static type of 'bool'.Try changing the condition
  // ❌ The condition must be bool
/* if (a) { print(a); } else { print(a); } * /
  /* The following code is correct */
  bool b;
  bool getResult() {
    return true;
  }

  b = getResult();
  if (b) {
    print(b);
  } else {
    print(b);
  }
Copy the code

Returns a value of type bool

// The dart version determines whether it is an empty string, NaN, null, or 0
// Check for an empty string.
var fullName = ' ';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints == 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Copy the code

Lists

Like arrays in JavaScript, Dart arrays are wrapped Object special classes, not arrays in the traditional sense.

List declaration

  var arr = <String> ['0'.'1'.'2'.'3']; // Define the array type
  var arr1 = [0.1.2.3.4]; // Automatic inference
	List arr5 = <String> ['0'.'1'.'2'.'3'];// Define the list as a type
Copy the code
  • Use the const keyword to create compile-time variables that cannot be modified or added

      var arr2 = const [1.2.3.4]; // Create a compile-time constant that cannot be modified or added
      arr2.add(5); // Cannot add to an unmodifiable list
    Copy the code
  • Create a fixed length collection

    	var arr3 = List.filled(2.' ');// Create a fixed length collection
    	var arr4 = List.filled<int> (2.0);// Create a fixed length typed collection
    Copy the code
  • Extend operator operations on arrays

      var list = [1.2.3];
      var list2 = [0. list];// Insert list into list2
      assert(list2.length == 4);
    Copy the code
  • The null-aware operator operates on an array to avoid exceptions if null

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

      var arr = <String> ['0'.'1'.'2'.'3']; 
    	arr.length
    Copy the code
  • Check whether it is empty

      var arr = <String> ['0'.'1'.'2'.'3']; 
      arr.isEmpty
      arr.isNotEmpty
    Copy the code
  • Flip the array

      var arr = ['1'.'2'];
      var newArr = arr.reversed.toList();
      print(newArr);
    Copy the code
  • You can use if or for in a List

      var nav = ['Home'.'Furniture'.'Plants'.if (true) 'Outlet'];
      var listOfInts = [1.2.3];
      var listOfStrings = ['# 0'.for (var i in listOfInts) '#$i'];
      print(listOfStrings); // [#0, #1, #2, #3]
    Copy the code

For other apis, please refer to the official documentation.

Sets

Dart’s SET declaration

  var halogens = {'fluorine'.'chlorine'.'bromine'.'iodine'.'astatine'};
  Set s = <String> {'fluorine'.'chlorine'};
Copy the code

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

  var s = <String> {};Set _s = <String> {};Set<String> names = {};
  var _names = {}; // This is a map, not a set
Copy the code
  • useaddMethods oraddAllMethod to add items
  var sets = <Object> {}; sets.add('1');
  sets.addAll([1.2.3]);
  print(sets);
Copy the code
  • Use.length to get the number of elements in a Set

      final sets = {'fluorine'.'chlorine'};
      print(sets.length);
    Copy the code
  • Add the const keyword to create a Set compile-time variable

    final constantSet = const {
      'fluorine'.'chlorine'.'bromine'.'iodine'.'astatine'};// constantSet.add('helium'); // This line will cause an error.
    Copy the code
  • Sets can use both extension operators and null-aware operators

      final sets = {'fluorine'.'chlorine'};
      var maybeNull;
      final a = <String> {'hello'. sets};final b = <String> {'hello'. ? maybeNull};print(a);
      print(b);
    Copy the code

Maps type

The Maps type in Dart is similar to the Map data structure in JavaScript, except that quotes are enforced around keys. The Maps type is used as an object in Dart.

Declare a map, using var to make the map automatically infer, or manually write the map type

  const a = [1.2.3];
  var map = {a: '123'}; // map is used for js map, and key is not used for [key].
  var map1 = <String.String> {'a': '123'}; // map is used as the js object, and the key should be quoted
  var map2 = Map(a);// Create a map of free type
  var map3 = Map<int.String> ();// Define the type when creating the map
  map3[1] = '1'; // Assign a value to map
  print(map);
  print(map1);
  print(map.containsKey(a)); // js's map.has method determines whether the key exists
Copy the code

In JavaScript, you can use new Map() to turn normal functions into constructors. Dart can omit new, and the above code creates a Map object using the Map() constructor.

  • Add a single attribute and multiple attributes

      var map = {};
      map['age'] = 20;
      map.addAll({"name": 'qiuyanxi'.1: 2});
      print(map);
    Copy the code
  • Returns null if the key is not in the map

      var map = {};
      assert(map['name'] = =null);
    Copy the code
  • Length gets the number of key-value pairs

      var map = {};
      assert(map.length == 0);
    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
  • Map uses extension operators and null-aware operators

      var map = {'name': "qiuyanxi"};
      Map? maybeNull;
      varnewMap = {... map};varnewMap2 = {... ? maybeNull};Copy the code

Access to type

Use runtimeType to get the type of the object

  var n = null;
  var s = 'String';
  print(n.runtimeType); // Null
  print(s.runtimeType); // String
Copy the code

function

Define functions. It is recommended to define return types

  String getName() {
    return 'qiuyanxi';
  }
Copy the code

Functions with only one expression can be simplified using the arrow function

  String getName() => 'qiuyanxi';
Copy the code

Necessary parameters

  String getName(String name, int age) => '$name$age';
  getName('qiuyanxi'.10);
Copy the code

Optional position parameter

Use [] to indicate optional positional arguments

  void printThings([String? str, String str2 = 'default value']) {
    assert(str == null);
    assert(str2 == 'default value');
  }
  printThings();
Copy the code

Named parameters

Named parameters are optional by default. If it is a required parameter, you need required

When defining a function{parameter 1, parameter 2}To specify named parameters

  String getName2({required String name, int? age = 10}) = >'$name$age';
Copy the code

Is used when calling a functionParameter Name: Parameter valueSpecify named parameters

  getName2(name: 'qiuyanxi');
Copy the code

The default parameters

If a parameter is optional but cannot be null, a default value needs to be provided. The argument is null if there is no default value

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

The default value

Only optional arguments have default values, which must be compile-time constants. For example, the following arguments are the default List and Map. To become compile-time constants, the const keyword is required

  void getList([List<int> list = const [1.2.3]]) {}
  void getMap([Map<String.String> map = const {"name": "qiuyanxi"}]) {}
Copy the code

The main function

The main function, the mandatory top-level function of every Dart program, is the entry point to the program. The main function returns void and has an optional argument of type List

.

You can pass arguments to main from the command line

hello-world.dart

void main(List<String> args) {
  Dart hello-world.dart 1 test
  print(args); //['1', 'test']
  assert(args.length == 2);
  assert(int.parse(args[0= =])1);
  assert(args[1] = ='test');
}
Copy the code

Anonymous functions

Anonymous functions are used as arguments

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

Use the anonymous arrow function as an argument

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

Lexical scope

Dart’s scope is lexical and, like JavaScript, is determined at code writing time.

closure

Closures, like JavaScript, won’t be explained much.

The return value

All functions return a value, even if the return value is void. If no return statement is explicitly written, return NULL is executed by default

// This is a function that explicitly returns void
  void returnVoid() {
    print('hello');
  }

  var a = returnVoid();
  // void variables cannot be used
  // print(a);

// This is a function without a return statement
  returnNull() {}
  var b = returnNull();
  assert(returnNull() == null); // true
Copy the code

The operator

The assignment operator

  var a = 1;
  int?b; b ?? =2; // Assign 2 to b if b is empty
  a += 0; // a=a+0
Copy the code

Arithmetic operator

  print(a + b);
  print(a - b);
  print(a * b);
  print(a / b);
  print(a % b); / / to take over
  print(a ~/ b); / / integer
	a ++ // calculate first and then increment
  a -- // calculate and then subtract
  -- a // subtract first
  ++ a // increment first and then calculate
Copy the code

Relational operator

  print(a == b);
  print(a >= b);
  print(a <= b);
  print(a ! = b); identical(DateTime.now(), DateTime.now()); // Determine whether two objects are equal
Copy the code

Type judgment operator

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

Logical operator

The operator describe
! * Expression * Invert the result of the expression (true to false, false to true)
` `
&& Logic and
  var c = false;
  var d = true;
  / * * /
  if(! c) {print(c);
  }
  /* && and */
  if (c && d) {}
  / * | | or * /
  if (c || d) {}
Copy the code

expression

The expression 1 ?? The expression of 2

If expression 1 is null, expression 2 is returned

  / *?? The operator * /
  var i;
  var j = i ?? 10; // if I is null, 10 is assigned to j, merging the operator with js null
  print(j);
Copy the code

conditions ? The expression 1 : The expression of 2

  /* The ternary operator */
  var flag;
  flag = true;
  var f = flag ? 'true' : 'false';
Copy the code

Cascade operator

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

The following code

varpaint = Paint() .. color = Colors.black .. strokeCap = StrokeCap.round .. strokeWidth =5.0;

/* is the same as */
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

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

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

Other operators

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
? [] Nullify access List When the left caller is not empty, accesses the 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

Sentenced to empty

  • Checks if the string is empty

      var str = ' ';
      if (str.isEmpty) {
        print('Judged to be empty string');
      }
    Copy the code
  • Check whether the value is null

      var _null = null;
      if (_null= =null) {
        print('Judged as null');
      }
    Copy the code
  • Determine if it is NaN

      var _nan = 0 / 0;
      if (_nan.isNaN) {
        print('is NaN');
      }
    Copy the code

Air safety

Dart currently supports controlled security, which means that their values cannot be null unless we declare them nullable, which has the benefit of improving the robustness of the code and providing errors at compile time.

Air safety requires a DART of 2.12 or above

To declare nullability, place a question mark in front of the type:

  int? count;
  count = null;
Copy the code

If there is no question mark, then the value cannot be empty

  int count;
  // ❌ A value of type 'Null' can't be assigned to a variable of type 'int'.
  count = null;
Copy the code

If we know that a value cannot be null, but Dart judgment may be null, then use! Represents a non-empty assertion

  String? getData(String? data) {
    if (data is String) {
      return 'this is string data';
    }
    return null;
  }

- String a = getData('12131');
+  String a = getData('12131')!;
Copy the code

Flow control statement

  • The for loop

      for (var i = 0; i < 10; i++) {
        print(i);
      }
    Copy the code

    JavaScript var has only one scope in the for loop. Dart var does not have this problem, so the above code will type I normally.

  • for… In circulation

    Use for.. In iterates over iterable objects, such as Lists and sets

      var list = [1.2.3];
      var sets = <int> {1.2.3};
      for (var value in list) {
        print(value);
      }
      for (var value in sets) {
        print(value);
      }
    Copy the code

    Iterable objects can also loop through using the forEach method

    var collection = [1.2.3];
    collection.forEach(print); / / 1 2 3
    Copy the code
  • The while loop

      var i = 10;
      while (i > 0) {
        print(i);
        i--;
      }
    Copy the code
  • The do while loop

      do {
        print(i);
        i--;
      } while (i > 0);
    Copy the code

    The difference between do while and while is that the do while loop does once even if the condition is not met; The while loop doesn’t

      var i = 0;
      do {
        print(i); // This code executes
        i--;
      } while (i > 0);
    
      while (i > 0) {
        print(i);// Never execute
        i--;
      }
    Copy the code
  • Break, continue statement

    Break skips the loop, and continue skips the loop

  • The switch and the case

  • Assertions, assert

      assert(1 < 2);
      assert(1 > 2.'1>2 is wrong');
    Copy the code

Exception handling

Dart provides two types of exceptions, Exception and Error, as well as subclasses. We can define our own exception type, or we can throw any non-null object as an exception.

  • An exception is thrown
  throw new Exception('This is an exception.');
  throw 'This is an exception.';
Copy the code
  • Catch exceptions

      try {
        // throw Error();
        throw Exception('this is exception error');
      } on Exception catch (e) {
        print('this is Unknown exception $e');
      } catch (e,s) {
        print('No specified type, handles all error $e');
        print('Stack trace:\n $s');
      }
    Copy the code

    The above code uses ON and catch to catch exceptions, with ON specifying the type of exception and catch catching objects. When an error is thrown that is not of the exception type specified by ON, the last catch pocket is taken.

    The catch method takes two arguments. The first argument is the exception object thrown and the second argument is the stack information.

  • Rethrow Throws an exception again

    When we catch an exception, we can throw it again.

    The following example throws an exception caught by an inner function into an outer scope for the code in main to catch.

    void misbehave() {
      try {
        dynamic foo = true;
        print(foo++); // Runtime error
      } catch (e) {
        print('misbehave() partially handled ${e.runtimeType}. ');
        rethrow; // Allow callers to see the exception.}}void main() {
      try {
        misbehave();
      } catch (e) {
        print('main() finished handling ${e.runtimeType}. '); }}Copy the code

    The code above prints the following:

    misbehave() partially handled NoSuchMethodError.
    main() finished handling NoSuchMethodError.
    Copy the code
  • Finally

    A finally statement is executed whether or not an exception is thrown.

      try {
        throw Error();
      } catch (e) {
        print('i will catch this error');
      } finally {
        print('finally print this message');
      }
    Copy the code

class

Dart is a language that supports mixin-based inheritance (JavaScript is prototype-based), where all objects are instances of a class and all classes except null inherit from the Object class.

Define the attributes and methods of the class

// Class definitions cannot be written in main
class Person {
  String? name; // Declare instance variable name, initially null.
  int age = 0; // Declare y, initially 0.
  void getInfo() {
    print('The ${this.name} ------ The ${this.age}');
  }
  void setName(String name) {
    this.name=name; }}void main(List<String> args) {
  var p = new Person();// You can omit new
  p.getInfo();
  p.setName('my name');
  p.getInfo();
}
Copy the code

Instance attributes default to NULL if not initialized.

All instance variables implicitly declare Getter methods. Instance variables that can be modified and late final declarations but variables that are not initialized also implicitly declare a Setter method that we can use to read or set instance objects through getters and setters.

class Person {
  String? name;
  int age = 0;
  late final int height;
}

void main(List<String> args) {
  var p = new Person();
  p.name = 'my name';// setter
  p.height = 180;// setter
  print(p.name);// getter
  print(p.height);// getter
  p.height = 190; // Field 'height' has already been initialized.
}
Copy the code

Instance variables can be final, in which case they can only be set once.

The constructor

Constructors can be created using a function with the same name as the class, and there is also a named constructor.

class Point {
  double x = 0;
  double y = 0;
  // Point(double x, double y) {
  // this.x = x;
  // this.y = y;
  // }
  // The syntax of the constructor above can be written like this
  Point(this.x, this.y);
  // The named constructor uses the initializer list
  Point.origin(double xOrigin, double yOrigin):x=xOrigin,y=yOrigin
  // The named constructor can also be written like this
  // Point.origin(double this.x, double this.y);
}
// Use it in main
void main(List<String> args) {
  // Use the named constructor
  var p1 = new Point.origin(10.20);
  var p2 = Point(10.20);
}
Copy the code

Use the this keyword to reference the current instance.

If no constructor is declared, Dart automatically generates a no-argument constructor that calls the no-argument constructor of its parent class.

Constructors are not inherited, which means that subclasses cannot inherit constructors from their parent class. The named constructor also cannot be inherited.

There can be more than one named constructor that can be called directly when instantiated as needed.

Initialization list

Before the constructor runs, there is a concept of an initializer list. Instance variables can be initialized

class Rect {
  int height;
  int width;
  Rect()
      : width = 10,
        height = 10 {
    print("The ${this.width}---The ${this.height}");
  }
  Rect.create(int width, int height)
      : width = width,
        height = height {
    print("The ${this.width}---The ${this.height}"); }}void main(List<String> args) {
  var p1 = Rect(); / / 10-10

  var p2 = Rect.create(100.200); / / 100-200
}
Copy the code

When constructed using Rect, the initializer list initializes width and height to 10.

When the rect.create construct is used, the initializer list is initialized with the value passed in.

Initializer lists can solve the problem of a class variable that is both final and optional and whose optional parameter defaults need to be determined dynamically.

class Person {
  static final String name = 'myname';
  final int age;
  Person() : age = Person.name == 'myname' ? 10 : 20;
}
Copy the code

The above code age is optional and the default value of age needs to be determined dynamically, so there is no way to use optional parameters, as in the following example:

class Person {
  static final String name = 'myname';
  final int age;
  // ❌ The default value of an optional parameter must be constant.
  Person([this.age = Person.name == 'myname' ? 10 : 20]);
}
Copy the code

Redirect constructor

When a constructor is called, giving that constructor the ability to call another constructor is called a redirection constructor.

In the following code, when you call Person.init(), you are redirected to the Person constructor, essentially initializing the list.

class Person {
  String name;
  int age;
  Person(this.name, this.age);
  Person.init(String name, int age) : this(name, age);
}
Copy the code

Constant constructor

When a class variable is final, the constructor needs to be const, otherwise it will get an error when instantiated.

The following example creates two equal objects, similar to the singleton pattern.

class Person {
  final String name;
  const Person(this.name);
}

void main(List<String> args) {
  const p1 = const Person('myname');
  const p2 = Person('myname'); // const can be omitted
  print(identical(p1, p2));// These two objects are equal
}
Copy the code

Factory constructor

Whereas a normal constructor returns an object automatically, a factory constructor’s best feature is that it can return an object manually.

This is not accessible in the factory constructor

The following uses the factory constructor to actively return a singleton

class Person {
  String name;

  static final Map<String, Person> cache = {};

  Person(this.name);

  factory Person.getSingle(String name) {
    if (cache.containsKey(name)) {
      return cache[name] as Person;
    } else {
      cache[name] = new Person(name);
      return cache[name] asPerson; }}}void main(List<String> args) {
  var p1 = Person.getSingle('qyx');
  var p2 = Person.getSingle('qyx');
  print(identical(p1, p2));// true
}
Copy the code

Private properties/methods of the instance

Separating a class into a file and prefixing an attribute or method with an _ defines the private variables of the instance object.

lib/Person.dart

class Person {
  String? name; // Declare instance variable name, initially null.
  int _age = 0; // Declare y, initially 0.
  void getInfo() {
    print('The ${this.name} ------ The ${this._age}'); }}Copy the code

main.dart

import 'lib/Person.dart';

void main(List<String> args) {
  var p = Person();
  // print(p._age); invalid
  p.getInfo();
}
Copy the code

Getter and Setter

The constructor automatically sets getters and setters for instance variables, or we can specify them manually, which has the advantage of listening for properties.

class Rect {
  int height;
  int width;
  Rect(this.width, this.height);
  // Manually specify getters
  get area {
    return this.height * this.width;
  }
  // Manually specify how to write the setter
  set h(int value) {
    print("When you call xx.h, it prints this message to indicate that you are being listened to.");
    this.height = value;
  }

  set w(int value) {
    this.width = value; }}void main(List<String> args) {
  var p = Rect(10.20);
  print(p.area);// getter
  p.h = 100;// setter
  p.w = 100;
  print(p.area);
}
Copy the code

Static members

As with TS, static is used to declare static members.

class Rect {
  static int height = 10;
  static int width = 10;
  static getArea() {
    print(height * width); }}void main(List<String> args) {
  Rect.getArea();
}
Copy the code

There are two points to note:

  • Static members do not have access to instance variables

    class Rect {
      int height = 10;
      static int width = 10;
      static getArea() {
        print(this.height * width); // Failed to access the height attribute}}Copy the code
  • Instance methods can access static members

    class Rect {
      int height;
      static int width = 10;
      Rect(this.height);
      getArea() {
        print(this.height * width);// If accessing instance attributes, this is recommended.}}void main(List<String> args) {
      new Rect(10).getArea();
    }
    Copy the code

inheritance

Constructors cannot be inherited; the extends and super keywords are used to inherit properties and methods of the parent class.

Pure inherited parent class

class Animal {
  String name;
  void sound(voice) {
    print(voice);
  }

  Animal(this.name);
}

class Dog extends Animal {
  Dog([String name = 'dog') :super(name);
}

void main(List<String> args) {
  var dog = new Dog();
  print(dog.name); // dog
  dog.sound('wang wang'); / / wang wang
}
Copy the code

Where Dog([String name = ‘Dog ‘]) : super(name); Some explanation is needed:

  • : super(name)This syntax is set with an initializer that calls the constructor of its parent class when Dog is constructedname
  • Dog([String name = 'dog'])This syntax is callednew Dog()whennameThis parameter is optional. The default value isdog

Extend the attributes and methods of subclasses

class Animal {
  String name;
  void sound(voice) {
    print(voice);
  }

  Animal.create(this.name);
}

class Dog extends Animal {
  String sex;
  Dog(this.sex, [String name = 'dog') :super.create(name);
  void run() {
    print('The ${this.name} runrun'); }}Copy the code

Overrides properties and methods of the parent class

class Animal {
  String name;
  void sound(voice) {
    print(voice);
  }

  Animal.create(this.name);
}

class Dog extends Animal {
  String sex;
  Dog(this.sex, [String name = 'dog') :super.create(name);
  void run() {
    print('The ${this.name} runrun');
  }

  @override
  void sound(voice) {
    print('The ${this.name} $voice'); }}void main(List<String> args) {
  var dog = new Dog('male');
  print(dog.name); // dog
  dog.sound('wang wang'); / / dog barked
}
Copy the code

It is recommended to override the attributes and methods of the parent class using @Override

A method that calls the parent class in a subclass

The method of the parent class is called by super

class Dog extends Animal {
  String sex;
  Dog(this.sex, [String name = 'dog') :super.create(name);
  void run() {
    super.sound('wang wang');
    print('The ${this.name} runrun'); }}Copy the code

An abstract class

  • Abstract classes are primarily used to define standards

  • Abstract classes cannot be instantiated; only subclasses that inherit from them can be instantiated

  • Abstract classes that want to be instantiated can use factory constructors

Use the abstract keyword to indicate that this is an abstract class.

For example, the following defines an abstract class for Animal, which contains the criteria for all animals.

abstract class Animal {
  sound(); // Abstract methods
  print() {} // Ordinary methods may not be implemented by subclasses
}

// Subclasses must implement the same abstract methods
class Dog extends Animal {
  @override
  sound() {}
}
Copy the code

polymorphism

Polymorphism is the same operation on different objects, can produce different interpretations and different effects.

In JavaScript, polymorphism is implemented in the form of prototype chains, such as the toString method on Object and Array prototypes, Prototype essentially writes a toString on array. prototype to override the toString on Object. Prototype

Polymorphism in Dart is a way of overriding the parent class definition by subclasses so that each subclass behaves differently.

With an abstract class, you just define the methods of the parent class and don’t implement them, and let the subclasses that inherit them implement them, and each subclass is polymorphic.

abstract class Animal {
  sound(); // Abstract methods
}

class Dog extends Animal {
  @override
  sound() {
    print('wang wang');
  }

  run() {}
}

class Cat extends Animal {
  @override
  sound() {
    print('meow meow');
  }

  run() {}
}

void main(List<String> args) {
  var dog = new Dog();
  var cat = new Cat();
  print(dog.sound());
  print(cat.run());
  // The run method cannot be called
  Animal _dog = new Dog();
  Animal _cat = new Cat();
}
Copy the code

interface

Dart doesn’t have interfaces, so we use abstract classes to define interfaces and implements to make classes match interfaces.

Class matches a single interface

For example, the following uses abstract classes to encapsulate uniform add, delete, change, and query functions

abstract class Db {
  String uri;
  add();
  remove();
  save();
  select();
}
Copy the code

Use implements to match the interface

class MySql implements Db {
  @override
  add() {}

  @override
  remove() {}

  @override
  save() {}

  @override
  select() {}
}
Copy the code

The above code can also be inherited and overridden using the extends keyword. In general we use it like this:

  • If we need a common method to reuse, we use extends

  • If you need a canonical constraint, use implements

Class matches multiple interfaces

abstract class A {
  late String name;
  getA();
}

abstract class B {
  getB();
}

class C implements A.B {
  @override
  getA() {}

  @override
  getB() {}

  @override
  late String name;
}
Copy the code

Mixins with

Similar functionality to multiple inheritance can be achieved with mixins, which use the keywords with and mixin

mixin A {
  void getA() {}
}

mixin B {
  void getB() {}
}

class C with A.B {}

void main(List<String> args) {
  var c = new C();
  c.getA();
  c.getB();
}
Copy the code

The above code mixes the instance methods of multiple Mixins classes.

  • Mixins can only inherit from Object classes, not from other classes.

    class A {
      void getA() {}
    }
    
    class B extends A { 
      void getB() {}
    }
    
    class C with A.B {} // ❌ error: B is a mixins class and cannot be inherited
    Copy the code

    To make mixins classes more intuitive, it is recommended to define mixin classes using the mixin keyword

    Mixin A {void getA() {}} mixin B extends A {// ❌Copy the code
  • Classes that are mixins cannot have constructors

    mixin A {
      void getA() {}
    }
    
    mixin B {
      B(); // ❌ error B is a mixins class and cannot have a constructor
      void getB() {}
    }
    
    class C with A.B {} 
    Copy the code
  • A class can mixins multiple mixins classes

  • A class can inherit from a class and mixins some mixins classes

    class A {
      void getA() {}
    }
    
    class B {
      void getB() {}
    }
    
    class C extends A with B {}
    Copy the code
  • Mixins are neither inheritance nor interface. When mixins are used, they create a superclass that is compatible with all classes

    class A {
      void getA() {}
    }
    
    mixin B {
      void getB() {}
    }
    
    class C extends A with B {}
    
    void main(List<String> args) {
      var c = new C(); 
      print(c is A);// true
      print(c is B);// true
      print(c is C);// true
    }
    Copy the code
  • Use the ON keyword to specify which classes can use the Mixin class

    class A {
      void getA() {}
    }
    
    mixin B on A {
      void getB() {}
    }
    
    // class C with B {} ❌
    class C extends A with B {}
    Copy the code

The generic

Like TS, Dart supports generics, which are generic types, non-specific types that are assigned to the user.

The following functions, for example, specify the type that is passed in.

  T getData<T>(T data) {
    return data;
  }

// The caller can specify the type
  getData<String> ('123');
  getData<num> (123);
	getData<List> ([1.2.3]);
Copy the code

A generic class

When instantiating a class, generics specify the type of the instance object.

The following is the type of the List object property value specified after the List is instantiated.

  List l1 = new List<int>.filled(2.1);
  List l2 = new List<String>.filled(2.' ');
Copy the code
  • Define generic classes

    class A<T> {
      T age;
      A(T this.age);
      T getAge() {
        return this.age; }}Copy the code
  • Use generic classes

    void main(List<String> args) {
      // Use generic classes
      var a = new A<int> (12);
      var b = A<String> ('12');
    }
    Copy the code

A generic interface

A generic interface is defined as a collection of interfaces and generic classes

// Generic interface
abstract class Cache<T> {
  void setKey(String key, T value);
}
// The class matches this interface
class FileCache<T> implements Cache<T> {
  @override
  void setKey(String key, T value) {}
}

class MemoryCache<T> implements Cache<T> {
  @override
  void setKey(String key, T value) {}
}
Copy the code

Specifies the specific type of the generic when used

  var f = new FileCache<String> ();/ / the specified String
  f.setKey('key'.'string');
  var m = new MemoryCache<int> ();/ / specified int
  m.setKey('key'.123);
Copy the code

Limit the generic

Like Typescript, generic constraints use the extends keyword.

abstract class Cache<T> {
  void setKey(String key, T value);
}
// MemoryCache must be an int
class MemoryCache<T extends int> implements Cache<T> {
  @override
  void setKey(String key, T value) {}
}
void main(List<String> args) {
  // var m = new MemoryCache
      
       (); It can't be a String here
      
  var m = new MemoryCache<int> (); m.setKey('key'.123);
}
Copy the code

enum

The rules for enumerations are very different from Typescript and are ultimately used to define constant values

  • Define the enumeration

    enum Colors { RED, GREEN, BLUE }
    Copy the code
  • Access the subscript of the enumeration

    assert(Color.red.index == 0);
    Copy the code
  • Gets all enumeration values

    Colors.values // [Colors.RED, Colors.GREEN, Colors.BLUE]
    Copy the code
  • Accessing enumerated values

    Colors.RED // Colors.RED
    Copy the code
  • Access enumerated values with subscripts

      assert(Colors.values[0] == Colors.RED);
    Copy the code
  • The type of an enumeration value

    Colors.RED.runtimeType // Colors
    Copy the code

Newest modifier

Dart2.12 adds the late modifier, which has two uses:

  • Used to declare a variable that is initialized after the declaration and cannot be null
  • Lazy initialization of variables

Dart’s control flow analysis can detect when non-NULL variables are set to non-NULL values before being used, but sometimes the analysis fails. Two common cases are top-level variables and instance variables: Dart usually can’t determine if they’re set, so it doesn’t try.

If you are sure that the variable was set before it was used, but Dart is inconsistent, you can use late to eliminate the error

// The non-nullable variable 'a' must be initialized.
String a;
void main(List<String> args) {
  a = '123';
  print(a);
}
Copy the code

In the code above, a is the global variable. Dart has no way of analyzing whether the global variable is set, so the code above will report an error. At this point, the late statement can be used to eliminate errors.

- String a;
+ late String a;
void main(List<String> args) {
  a = '123';
  print(a);
}
Copy the code

If you mark a variable as late but initialize it when it is declared, the initializer runs when the variable is first used. This lazy initialization is handy in several cases:

  • Variables are not necessarily used, so this initialization is very memory saving

    // This is the program's only call to _readThermometer().
    late String temperature = _readThermometer(); // Lazily initialized.
    Copy the code

    In the above code, the _readThermometer function will not be called if temperature is never used

  • Late is added when the instance variable is not initialized but needs to be accessed

    class Cache {
      late String name;
      void setName(String name) {
        this.name = name; }}Copy the code

    In the above code, since there is no constructor, the name attribute will not be initialized when the instance is instantiated, and accessing it will result in an error

      var m = new Cache();
      // ❌LateInitializationError: Field 'name' has not been initialized.
      print(m.name);
    Copy the code

library

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

The only argument to import is the URI used to specify the code base.

For the built-in Dart library, use the Dart: XXXXXX form.

For other libraries, you can use a file system path or the package: XXXXXX format. Package: The library specified by XXXXXX is provided through a package manager such as the pub utility.

import 'dart:math'; // Introduce the built-in Math library
import 'package:test/test.dart'; // Import the library in the package manager
import 'lib/test.dart'; // Introduce a self-written library
Copy the code

Package bag

Yaml file is manually created in the root directory, similar to package.json of NPM, which is used to manage package versions and dependencies.

Write the following as prompted:

name: my_app
environment:
  sdk: '> = 2.12.0 < 3.0.0'
Copy the code

Then you can go to the website to find out what packages are available.

Take the HTTP module as an example:

Download package

Only DART uses this command

$ dart pub add http
Copy the code

Use this command when there is a flutter

$ flutter pub add http
Copy the code

The above command adds version dependencies in pubspec.yaml and implicitly runs dart pub get or flutter pub get

dependencies: 
  http: ^ 0.13.4
Copy the code

Use the package

import 'package:http/http.dart' as http;
Copy the code

Library conflict

If two imported code bases have the same name, you can use the specified prefix as. 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;

// Uses Element from lib1.
Element element1 = Element(a);// Uses Element from lib2.
lib2.Element element2 = lib2.Element(a);Copy the code

Part of the import

If you want to use only part of the code base, you can use partial imports.

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

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

export

Use the export keyword to export

export 'package:lib1/lib1.dart';
export 'src/middleware.dart' show Middleware, createMiddleware;
Copy the code

Lazy loading

Lazy loading (also known as lazy loading) is when it is needed.

Lazy-loading is currently supported only by Dart 2JS, Dart VM and DartDevc

Use the Deferred As keyword to identify code libraries that need 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<void> greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
Copy the code

Use the await keyword to suspend code execution until the library is loaded. 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 willloadLibrary()Import into useDeferred as * Namespace *In the class.loadLibrary()The function returns aFuture.

Package Management Suggestions

If we implement our own code base, to improve performance, we should put the code in the /lib/src directory, and then export the SRC API in the /lib directory to expose the API in the lib/ SRC directory.

asynchronous

Future

Futures, like JavaScript promises, use async and await to make code asynchronous. We must use await in asynchronous functions with the async keyword:

Future<void> checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
Copy the code

The code above waits until lookUpVersion processing is complete before taking the next step.

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.

Async keyword

Just like JavaScript rules, using async alone can only generate Future objects and does not make code asynchronous. An 🌰

  Future<void> checkVersion() async {
    print(123);
  }
	checkVersion();
  print(456);
	/ / 123
	/ / 456
Copy the code

The above code does not make checkVersion asynchronous because 123 is printed before 456.

Block code with await to make it truly asynchronous code.

  Future<int> getVersion() async= >123;

  Future<void> checkVersion() async {
    print(0); // Here is the synchronization code again
    var res = await getVersion(); // This becomes asynchronous
    print(res);
  }

  checkVersion();
  print(456);
Copy the code

The result of the above is

0, 456, 123Copy the code

Exception handling

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

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

Typedefs

Typedefs is a type alias, a handy way to refer to a type, often used to encapsulate a type, using the typedef keyword.

For example, if the project has a List of type numeric, we can wrap it into a type alias and use it directly

typedef IntList = List<int>;

IntList a = [1.2.3];
Copy the code

When the pass parameter is a function and an explicit type definition is required, using a type alias simplifies code

  void PrintString(String getS(String str)) {
    print(getString('name'));
  }
Copy the code

The PrintString function above requires passing in a function that returns both a String value and an argument. Using a typedef simplifies the code:

typedef GetString = String Function(String str);
 
 void PrintString(GetString getS) {
    print(getString('name'));
  }
Copy the code

The last

Promote my long-maintained Github blog

1. From learning to summary, record important knowledge points of the front-end, including in-depth Javascript, HTTP protocol, data structure and algorithm, browser principles, ES6, front-end skills, etc.

2. There is a directory in the README to find relevant knowledge points.

If it is helpful to you, welcome star and follow.

🌹✿ ° °) Blue ✿