阅读 284

Flutter系列笔记-2:Dart语法基础知识点

最好的Dart语言学习教程是官网

英文原版官网

也可以看中文版的

以下知识点总结基于 Dart VM version: 2.5.0

  • 1.Dart是一个函数式编程语言

      在函数式编程中,你可以做到:
    
      1.将函数当做参数进行传递
      2.将函数直接赋值给变量
      3,对函数进行解构,只传递给函数一部分参数来调用它,
         让它返回一个函数去处理剩下的参数(也被称为柯里化)
      4.创建一个可以被当作为常量的匿名函数
      (也被称为 lambda 表达式,在 Java 的 JDK 8 release 中支持了 lambda 表达式)
    复制代码
  • 重要概念(来自官网的翻译) 当你在学习 Dart 语言时, 应该牢记以下几点:

    1.所有变量引用的都是 对象,每个对象都是一个 类 的实例。数字、函数以及 null 都是对象。所有的类都继承于 Object 类。

    2.尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。 在上述代码中,变量 number 的类型被推断为 int 类型。 如果想显式地声明一个不确定的类型,可以使用特殊类型 dynamic。

    3.Dart 支持泛型,比如 List(表示一组由 int 对象组成的列表)或 List(表示一组由任何类型对象组成的列表)。

    4.Dart 支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态 和 实例方法) 。你还可以在函数中定义函数(嵌套 或 局部函数)。

    5.Dart 支持顶级 变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。

    6.Dart 没有类似于 Java 那样的 public、protected 和 private 成员访问限定符。 如果一个标识符以下划线 (_) 开头则表示该标识符在库内是私有的。可以查阅 库和可见性 获取更多相关信息。

    7.标识符 可以以字母或者下划线 (_) 开头,其后可跟字符和数字的组合。

    8.Dart 中 表达式 和 语句 是有区别的,表达式有值而语句没有。 比如条件表达式 expression condition ? expr1 : expr2 中含有值 expr1 或 expr2。 与 if-else 分支语句相比,if-else 分支语句则没有值。 一个语句通常包含一个或多个表达式,但是一个表达式不能只包含一个语句。

    9.Dart 工具可以显示 警告 和 错误 两种类型的问题。警告表明代码可能有问题但不会阻止其运行。 错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致异常。

1.程序运行的入口函数 void main()

void main() {
  print('Hello, World!');
}
复制代码

2.关于变量定义和类型

dart的基本变量类型有
numbers 
strings 
booleans 
lists (also known as arrays) 
sets 
maps
runes (for expressing Unicode characters in a string)
symbols

//dynamic 定义一个类型可变的变量
dynamic value = "";
//获取变量类型
print(value.runtimeType);//String
value = 1;
print(value.runtimeType);//int
value = [];
print(value.runtimeType);//List<dynamic>
value = <int>[];
print(value.runtimeType);//List<int>

//使用 var 定义变量

//bool变量
var isDebug = true;

//定义字符串变量
//双引号
 var name = "hello world dart";

//单引号
 var name2 = 'hello world dart';

//单双混用
 var name3 = 'hello world "dart"';
 var name4 = "hello world 'dart'";

//三个双引号(按书写格式输出)
 var name5 = """
  hello
  world
  dart
  """;

//三个单引号(按书写格式输出)
 var name6 = '''
  hello
  world
  dart
  ''';

//创建一个原样的字符串,不会转义,回车换行也不会生效,原样输出
var s = r'In a raw string,not even \n gets special treatment';
var s1 = 'test test \ntest2';
print(s);//不会换行
print(s1);//会换行

print(r"^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+");//打印结果 ^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+
print("^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+");//打印结果  ^((https|http|ftp|rtsp|mms)?://)[^s]+


//dart里只有两个数值类型 int double 都是num的子类
//定义int变量
var year = 1977;

//定义double变量
var antennaDiameter = 3.7;


//定义List变量
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var emptyList = <String>[];//List<String>

//定义Set变量
var setObjects = {'Jupiter', 'Saturn', 'Uranus', 'Neptune'};
var emptySet = <String>{};//Set<String>

//定义map变量
var image = {
  'tags': ['saturn'],
  'url': '//path/to/saturn.jpg'
};
var emptyMap = {};//Map<String, Object>

//除了用var定义变量外 
//可以直接声明类型
double z = 1;
复制代码

3.流程控制语句

if (year >= 2001) {
  print('21st century');
} else if (year >= 1901) {
  print('20th century');
}


for (var object in flybyObjects) {
  print(object);
}


for (int month = 1; month <= 12; month++) {
  print(month);
}


while (year < 2016) {
  year += 1;
}


dart支持switch,并且可以使用 continue和label
If you really want fall-through, you can use a continue statement and a label:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // 继续执行标签为 nowClosed 的 case 子句。

  nowClosed:
  case 'NOW_CLOSED':
    // case 条件值为 CLOSED 和 NOW_CLOSED 时均会执行该语句。
    executeNowClosed();
    break;
}
复制代码

4.定义方法

//最好都指定返回值
int fibonacci(int n) {
  if (n == 0 || n == 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

var result = fibonacci(20);

//如果方法里只有一行,可以简写(可以省略{}和return)
int add(int a,int b) {
  return a+b;
}
//可以简写成
int add(int a,int b) => a+b;

//定义一个无参匿名方法并立即运行
(){
  print("hello world");
}();

//定义一个有参匿名方法并立即运行
(int i,int j){
  print('i+j=${i+j}');
}(7,8);

//定义一个函数,赋值给一个变量
var fun = () {
  print("hello world");
};
print("fun is function:${fun is Function}");//true
fun();
复制代码

5.注释

//单行注释
var intValue = 1;

///多行注释,
///可用于dartdoc
var doubleValue = 1.0;

/*支持单行和多行注释*/
var comments = "这是注释"; 
复制代码

6.导包

// 导入dart核心库
import 'dart:math';

// Importing libraries from external packages
import 'package:test/test.dart';

// Importing files
import 'path/to/my_other_file.dart';

//show hide

// 只导入 lib1 中的 foo。(Import only foo).
import 'package:lib1/lib1.dart' show foo;

// 导入 lib2 中除了 foo 外的所有。
import 'package:lib2/lib2.dart' hide foo;

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

// 使用 lib1 的 Element 类。
Element element1 = Element();
// 使用 lib2 的 Element 类。
lib2.Element element2 = lib2.Element();

//延迟加载库
import 'package:greetings/hello.dart' deferred as hello;
Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
复制代码

7.类 class

class Spacecraft {
  // 私有变量,当前.dart文件内可以访问,其他dart文件类里面不能访问这一个变量
  int _speed = 0;
  String name;
  DateTime launchData;

  //带有语法糖的构造函数,使用创建对象同时并给成员变量赋值
  Spacecraft(this.name,this.launchData) {
	// 初始化代码
  }

  //命名构造函数
  //重定向构造方法
  //有时一个构造方法仅仅用来重定向到该类的另一个构造方法。
  //重定向方法没有主体,它在冒号(:)之后调用另一个构造方法。
  Spacecraft.unlaunched(String name) : this(name,null);

  //构造函数的成员变量初始化列表,初始化列表赋值比构造函数函数体还要早运行的
   Spacecraft.fromJson(Map<String, dynamic> json)
  : name = json["name"],
    launchData = DateTime.fromMicrosecondsSinceEpoch(json["launchData"]) {
     print("fromJson $launchData");
   }      

  //构造函数上也可以加断言,调试模式下,如果断言条件不成立,会抛异常,及时发现错误
  Spacecraft.fromJson2(Map<String, dynamic> json)
  : assert(json.containsKey("name")),
    assert(json.containsKey("launchData")) {
    print("fromJson $launchData");
  }

  //Dart 支持工厂构造方法。它能够返回其子类甚至 null 对象。要创建一个工厂构造方法,请使用 factory 关键字。
  //工厂构造方法不支持this.参数,不支持初始化列表,不支持断言
  factory Spacecraft.fromJson3() {
    return null;
  }
	
  // 当作常量使用的匿名函数
  int get launchYear => launchData?.year;

  //无参无返回值方法
  void describe(){
	print("Spacecraft:$name");
  }

  //私有的函数,以_开头的函数,只能在类内部使用,不能在外部使
  void _privateFunction() {
  }
}

//使用时
var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5));
voyager.describe();

var voyager3 = Spacecraft.unlaunched('Voyager III');
voyager3.describe();

//Const 构造方法
//如果你的类生成的对象永远都不会更改,则可以让这些对象成为编译时常量,会放到常量池
//为此,请定义 const 构造方法并确保所有实例变量都是 final 的。
class ImmutablePoint {
  const ImmutablePoint(this.x, this.y);

  final int x;
  final int y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
}
var a = ImmutablePoint.origin;//常量
var a1 = const ImmutablePoint(0, 0);//常量
var a2 = ImmutablePoint(0, 0);
print(a1 == a);//true
print(a == a2);//false 一个是常量,一个不是常量
print(a1 == a2);//false 一个是常量,一个不是常量
复制代码

8.继承

dart是单继承机制
//Spacecraft定义见 7.类 class
class Orbiter extends Spacecraft {
  num altitude;
  Orbiter(String name, DateTime launchDate, this.altitude)
      : super(name, launchDate);
}
var orbiter = Orbiter("google",DateTime.now(),100);
print(orbiter.name);
print(orbiter.launchData);
print(orbiter.altitude);
orbiter.describe();
复制代码

9. Mixins

Mixins 跨多个类层次结构重用代码
Mixins are a way of reusing code in multiple class hierarchies

class Piloted {
  int astronauts = 1;
  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

//Spacecraft定义见 7.类 class
class PilotedCraft extends Spacecraft with Piloted {
  // ···
}

//PilotedCraft同时有 Spacecraft 和 Piloted的成员变量和方法
var p = PilotedCraft("test", DateTime.now());
print(p is Spacecraft);//true
print(p is Piloted);//true

//不使用 class 关键字,只使用 mixin定义,代表这一个类不能被实例化,没有构造函数,只能被with使用
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose =false;
  bool canConduct = false;

  void entertainMe() {
    if(canPlayPiano) {
      print("Playing piano");
    } else if(canConduct) {
      print("Waviing hands");
    }else {
      print("Humming to self");
    }
  }
}
复制代码

10.接口与抽象类

Dart 语言并没有提供 interface 关键字,但是每一个类都隐式地定义了一个接口。所有类都可以当成接口被实现

class MockSpaceship implements Spacecraft {
  // ···
}

抽象类
abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}

class MyDescribable extends Describable {

  @override
  void describe() {
    // TODO: implement describe
  }
}
复制代码

11.async/await 避开回调地狱

网上另外一个人写的关于dart异步和消息机制的文章

【Dart学习】-- Dart之消息循环机制[翻译]

【dart学习】-- Dart之异步编程

使用async/await代替回调,可以不使用回调就可获取到异步操作后的结果,避免回调地狱,async/await可以理解成代替回调的语法糖

以创建文件,写文件,读取文件内容 为例
//使用回调方式
void main() async {
	var writeToFile = (File file) {
    file.writeAsStringSync("${DateTime.now()}\n", mode: FileMode.append);
    file.readAsString().then((content) {
      print("$content");
     }, onError: (e) {
      print("e:$e");
     });
    };

    var file = File("D://test.txt");
    file.exists().then((bool exists) {
    if (exists) {
      writeToFile(file);
     } else {
      file.createSync();
      writeToFile(file);
     }
    });
}


//使用async/await去掉回调 优化代码可读性
void main() async {
  var file = File("D://test.txt");
  //Future<bool> exists() 下面如果没有await返回的是Future<bool>,如果有await返回的就是bool,异步的执行结果
  if(!await file.exists()) {
   await file.create();
  }
  await file.writeAsString("${DateTime.now()}\n", mode: FileMode.append);
  print("${await file.readAsString()}");
}

//  async*和 yield
Stream<String> report(Iterable<String> objects) async* {
  for (var object in objects) {
  await Future.delayed(oneSecond);
  yield 'flies by $object';
}

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

12.异常 Exceptions throw rethrow

try catch final on 可以组合使用,常见有以下使用方式
//try catch(e) catch也可以把错误堆栈打印了来,用法catch(e,stack)
try {
    //todo
} catch(e,stack) {
	print("catch: $e $stack");
}

//try cache(e) finally
try {
    //todo
} catch(e) {
	print("catch: $e");
} finally {
	//todo
}

try {
 //io操作
} on IOException catch (e) {
  print('Could not describe object: $e');
} finally {
  //todo
}


try {
    //todo
} on IOException catch(e) {
	print("Could not describe object: $e");
} catch(e) {
	print("catch: $e");
} finally {
	//todo
}

try {
  //todo
} on IOException catch (e) {
  //todo
} on Exception {
  //todo
} finally {
  //todo
}


rethrow throw 抛出异常
与java不同,throw可以抛出任何值类型的异常
如 
抛出字符串异常 throw "this exception"
抛出数字异常 throw 10101
抛出 Exception子类对象 throw FormatException();
这样也是可以的 throw FormatException

void misbehave() {
  try {
    throw "exception";
  } catch (e) {
    if (!canHandle(e)) rethrow;//把不处理的异常原样抛出
    handle(e);
  }
}

异常与异步 
try catch可以处理 await语句的异步异常
如果没有await处理,try catch就不可以处理异步异常
Future<String> getExceptionFuture() {
  var str =  Future.delayed(Duration(seconds: 4),() => throw FormatException);
  print("str type :${str.runtimeType}");
  return str;
}

main() async {
  try {
    var result =  await getExceptionFuture();
    print("$result");
  }catch(e) {
    print("has exception runtimeType:${e.runtimeType} info:$e");
  }
}
复制代码

其他小知识点


使用可选参数(而不是使用重载)

class Person {
  String name;
  int age;
  int height;
  int weight;
  Person({this.name, this.age, this.height,this.weight});

  @override
  String toString() {
    return 'Person{name: $name, age: $age, height: $height, weight: $weight}';
  }
}

void main() {
  print(Person(name: "小明"));
  print(Person(name: "小明",height: 170));
  print(Person(name: "小明",height: 170,weight: 100));
}
复制代码

字符串模板#

字符串模板使用
1.使用$并且没有{} 例 "$x"
2.使用$并且有{}  例 "${person.name}"

//stringify(2, 3) should return '2 3'.
String stringify(int x, int y) {
  return "$x $y";
}
复制代码

操作符

x ??= y

如果x为null,则给x赋值y,如果x不为null,则不赋值,不运行y表达式(如y是一个函数)

int a; // The initial value of a is null.
a ??= 3;
print(a); // <-- Prints 3.

a ??= 5;
print(a); // <-- Still prints 3.
复制代码

value = x ?? y

如果x为null,则赋值y给value,否则赋值x给value

var nullValue;
print(nullValue ?? "this is y");//this is y
print("this is x" ?? "this is y");//this is x
复制代码

条件属性访问 (避免空指针) ?.

条件属性访问,用于避开空指针异常
myObject?.someProperty
相当于
(myObject != null) ? myObject.someProperty : null

?.可以在同一个表达式中使用多次
myObject?.someProperty?.someMethod()
复制代码

箭头操作符 =>

箭头操作符,用于直接执行可边的表达式,并返回右边表达式的操作结果,用于替代函数的{}
例
bool hasEmpty = aListOfStrings.any((s) {
  return s.isEmpty;
});
可以用 => 简化成
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
复制代码

级联操作符 ..

级联操作符
myObject..someProperty..someMethod()
只有void修饰的方法可以使用级联,非void的都不可以使用级联

class BigObject {
  int anInt = 0;
  String aString = '';
  List<double> aList = [];
  bool _done = false;

 void allDone() {
   _done = true;
 }
 //只有void修饰的方法可以使用级联,非void的都不可以使用级联
 bool isDone() {
   return _done;
 }
}

BigObject fillBigObject(BigObject obj) {
	if(obj is Null) {
		return null;
	}
	return obj
	..anInt = 1
	..aString = 'String!'
	..aList.add(3)
	..allDone();
}
复制代码

其他比较特殊的运算符操作符

算术运算符 
assert(5 ~/ 2 == 2);// ~/	除并取整
assert(5 % 2 == 1);// %	取模

类型判断运算符
as、is、is! 运算符是在运行时判断对象类型的运算符

// 类型检查,如果类型成立,会智能转换
if (emp is Person) {
  emp.firstName = 'Bob';//不需要显式转换就可以使用Person的属性和方法
}

//如果 emp 为 null 或者不为 Person 类型,则会抛出异常。
(emp as Person).firstName = 'Bob';
复制代码

get 和 set

1.可以使用get set为私有成员变量提供对外访问的方法,如果get set不需要特殊处理,成员变量定义成公开比较好
class MyClass {
  int _aProperty = 0;

  int get aProperty => _aProperty;

  set aProperty(int value) {
    if (value >= 0) {
      _aProperty = value;
   }
 }
}

2.也给私有成员变量只使用 get,使变量对外只读
class MyClass {
  int _aProperty = 0;
  int get aProperty => _aProperty;
}

3.只使用 get 可以使一个函数对外像一个属性一样访问
class MyClass {
  List<int> _values = [];

  //函数对外像一个属性一样访问,省去() var len = MyClass().count;
  int get count {
    return _values.length;
  }
}
复制代码

可选位置参数

//普通方法的参数列表
int sumUp(int a, int b, int c) {
  return a + b + c;
}
//调用时三个参数都需要传值,缺一不可
int total = sumUp(1, 2, 3);

//可选位置参数
//参数列表可以全部都是可选位置参数,可以使用在构造函数上
//可选位置参数需要用[]括起来,只能放在参数列表后面,可以有一个或多个,
//方法调用时可选位置参数是可选的,不一定需要全部传,
//可选位置参数可以有默认值,如果定义时没有赋默认值,则为null
//可选位置参数不能与可选命名参数共存,有可选位置参数,不能有可选命名参数
int sumUpToFive(int a, [int b, int c=1, int d, int e]) {
  int sum = a;
  if (b != null) sum += b;
  if (c != null) sum += c;
  if (d != null) sum += d;
  if (e != null) sum += e;
  return sum;
}

int total = sumUptoFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
复制代码

可选命名参数

//参数列表可以全部都是可选命名参数,可以使用在构造函数上
//可选命名参数需要放到参数列表后面,同可选位置参数一样可以有默认值,也可能没有,
//方法调用时可选命名参数是可选的,不一定需要全部传,
//与可选位置参数不同的是,可选命名参数需要使用时,要带上参数名称
//可选命名参数不能与可选位置参数共存,有可选命名参数,不能有可选位置参数
void printName(String firstName,String lastName,{String prefix,String suffix=''}) {
  print("$prefix $firstName $lastName ${suffix ?? ""}");
}
printName("Poshmeister", "Moneybuckets",suffix:"IV");
printName("Poshmeister", "Moneybuckets",prefix:"prefix",suffix:"IV");
复制代码

扩展方法

dart在2.7版本支持了扩展方法(kotlin也有相同的概念)
示例:
void main() {
  print('10086'.parseInt());
  print('10086'.parseInt().runtimeType);
  print(Person(name: 'alibaba',age: 18,weight: 100,height: 200,country: 'zh').isChinesePeople());
}

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
// ···
}

extension PersonMethods on Person {
  bool isChinesePeople () {
    return 'zh' == country;
  }
}
class Person {
 String name;
 int age;
 int weight;
 int height;
 String country;

 Person({this.name, this.age, this.weight, this.height,this.country});
}
复制代码

dart代码规范