温馨提示:本文只罗列 Dart 中比较重要、奇特的语法,所以不合适没有其他语言基础的人学习!!
main(List<String> args) { print("Hello World");}
// 1. 明确的声明String name = "lqr";// 2. 类型推导(var/final/const)// 类型推导的方式虽然没有明确的指定变量的类型,但是变量是有自己明确的类型的// 2.1 var声明变量var age = 18;// age = "abc"; // IDE报错:A value of type 'String' can't be assigned to a variable of type 'int'.// 2.2 final声明常量final height = 1.88;// height = 2.00; // IDE报错:The final variable 'height' can only be set once.// 2.3 const声明常量const address = "广州市";// address = "北京市"; // IDE报错:Constant variables can't be assigned a value.// 2.4 final和const的区别// const必须赋值 常量值(编译期间需要有一个确定的值)// final可以通过计算/函数获取一个值(运行期间来确定一个值)// const date1 = DateTime.now(); // 写法错误final date2 = DateTime.now();// 2.5 Object和dynamic的区别// 分别使用Object 和 dynamic,让父类引用指向子类对象// Object调用方法时,编译时会报错Object obj = "lqr";print(obj.substring(1)); // IDE报错:The method 'substring' isn't defined for the type 'Object'.// dynamic调用方法时,编译时不报错,但是运行时会存在安全隐患// dynamic是明确声明(var是类型推导)dynamic obj = "lqr";print(obj.substring(1)); // qr
js 中存在非零即真、非空即真的特性,但在 Dart 中没有!!Dart 要求 if() 语句必须传入一个 bool 类型:
// Dart中没有非零即真,也没有非空即真var flag = "true";if (flag) { // IDE报错:Conditions must have a static type of 'bool'. print("执行代码");}
注意:Dart 不支持非空即真或者非 0 即真,必须有明确的 bool 类型
Dart 有三种定义字符串的方式:
// 1. 定义字符串var str1 = 'abc';var str2 = "abc";var str3 = """abccbanba""";// 小技巧:如果字符串中存在双引号,又不想转义的话,可以使用单引号来定义该字符串,反之使用双引号。print('a"b"c'); // a"b"cprint("a'b'c"); // a'b'c
Dart 支持字符串插值:
// 2. 字符串和表达式拼接var name = "lqr";var age = 19;var height = 1.88;// 强调:${变量}可以省略{},${表达式}不能省略{}// var message1 = "my name is ${name}, age is ${age}, height is ${height}";var message1 = "my name is $name, age is $age, height is $height"; // my name is lqr, age is 19, height is 1.88var message2 = "name is $name, type is ${name.runtimeType}"; // name is lqr, type is Stringprint(message1);print(message2);
Dart 集合类型有:
// 1. 列表List:[];var names = ["abc", "cba", "nba", "cba"];names.add("mba");// names.remove(value);// names.removeAt(index);// 2. 集合Set:{};var movies = {"星际穿越", "大话西游", "盗梦空间"};names = Set<String>.from(names).toList(); // 可以用Set来去重print(names);// 3. 映射Mapvar info = { "name": "lqr", "age": 18,};
Dart 的函数定义:
返回值 函数的名称(参数列表) { 函数体 return 返回值}
例子:
main(List<String> args) { print(sum(20, 30));}int sum(int num1, int num2) { return num1 + num2;}// 返回值的类型可以省略(开发中不推荐)// sum(int num1, int num2) {// return num1 + num2;// }
Dart 的函数参数有两类:
main(List<String> args) { sayHello1("lqr");}// 必选参数:必须传void sayHello1(String name) { print(name);}
main(List<String> args) { sayHello2("lqr"); sayHello2("lqr", 18, 1.88); sayHello3("lqr", age: 18, height: 1.88); sayHello3("lqr", height: 1.88, age: 18); sayHello3("lqr", height: 1.88);}// 位置可选参数:[int age, double height]// 实参和形参在进行匹配时,是根据位置来匹配void sayHello2(String name, [int age, double height]) {}// 命名可选参数:{int age, double height}// 实参和形参在进行匹配时,是根据变量名来匹配void sayHello3(String name, {int age, double height}) {}
Dart 中没有函数重载!!!Dart 中没有函数重载!!!Dart 中没有函数重载!!!但是,Dart 函数支持为可选参数设置默认值:
void sayHello1(String name) { print(name);}// Dart中没有函数的重载// void sayHello1(){ // IDE报错:The name 'sayHello1' is already defined.// ...// }// 注意:只有可选参数才可以有默认值,必传参数没有默认值void sayHello4(String name, [int age = 20, double height = 1.7]) {}void sayHello5(String name, {int age = 20, double height = 1.7}) {}
注意:只有可选参数才能设置默认值。
函数是一等公民 意味着可以将函数赋值给一个变量,也可以将函数作为另外一个函数的参数或返回值来使用。
main(List<String> args) { // 1. 直接找到另外一个定义的函数传进去 test(bar); // bar函数被调用 // 2. 匿名函数 (参数列表){函数体}; test(() { print("匿名函数被调用"); // 匿名函数被调用 return 10; }); // 3. 箭头函数:条件,函数体只有一行代码 // 注意:Dart中的箭头函数与js中的箭头函数是两回事 test(() => print("箭头函数被调用")); // 箭头函数被调用}// 函数可以作为另外一个函数的参数void test(Function foo) { var result = foo();}void bar() { print("bar函数被调用");}
上述代码中,当将函数作为另一个函数的参数时,使用 Function 来声明这个参数类型,很明显有个问题,那就是无法对传入的函数做限制:
main(List<String> args) { test((name) { print(name); // lqr });}// 封装test函数,要求:传入一个函数// 缺点:Function无法对传入函数做限制(比如:函数的参数列表、返回值)void test(Function foo) { foo("lqr");}
这时,可以使用 函数签名 来代替 Function:
main(List<String> args) { test((num1, num2) { return num1 + num2; });}// 可以限制 作为参数的函数的类型// 缺点:相当于在形参位置上写了一个函数声明(函数签名),整体看起来可读性差void test(int foo(int num1, int num2)) { foo(20, 30);}
为了有更好的可读性,可以使用 typedef 来代替 函数签名:
main(List<String> args) { test((num1, num2) { return num1 + num2; }); var demo1 = demo(); print(demo1(20, 30)); // 600}// 可以使用typedef定义一个函数类型typedef Calculate = int Function(int num1, int num2);void test(Calculate calc) { calc(20, 30);}// 函数作为返回值Calculate demo() { return (num1, num2) { return num1 * num2; };}
// 1. ??=// 当原来的变量有值时,那么 ??= 不执行// 原来的变量为null时,那么将值赋值给这个变量var name = null;name ??= "lilei";print(name); // lilei// 2. ??// ??前面的数据有值,那么就使用??前面的数据// ??前面的数据为null,那么就使用后面的值var name = null;var temp = name ?? "lilei";print(temp); // lilei
Dart 可以使用级联语法 (..) 将任意对象的 变量赋值 或 方法调用 操作都变成 链式调用:
main(List<String> args) { // var p = Person(); // p.name = "lqr"; // p.run(); // p.eat(); // 级联运算符 var p = Person() ..name = "lqr" ..eat() ..run();}
Dart 支持 普通 for 循环,以及 for-in 循环:
// 1. 基础for循环for (var i = 0; i < 10; i++) { print(i);}// 2. 遍历数组var names = ["why", "cba", "cba"];for (var i = 0; i < names.length; i++) { print(names[i]);}for (var name in names) { print(name);}
while 和 do-while 和其他语言一致,break 和 continue 用法也是一致
Dart 中使用 class 关键字来定义类:
main(List<String> args) { var p1 = new Person("lqr", 18); // 从Dart2开始,new关键字可以省略 var p2 = Person("lqr", 18);}class Person { String name; int age; // Person(String name, int age) { // this.name = name; // this.age = age; // } // 等同于上面的代码 Person(this.name, this.age);}
Dart 不支持函数重载,所以,构造函数也一样不能重载。不过,可以使用 命名构造函数 来解决:
main(List<String> args) { var p = Person("lqr", 18); var p1 = new Person.withNameAgeHeight("lqr", 18, 1.88); var p2 = Person.fromMap({ "name": "lqr", "age": 18, "height": 1.88, });}class Person { String name; int age; double height; Person(this.name, this.age); // Dart中没有函数重载 // Person(this.name, this.age, this.height); // IDE报错:The default constructor is already defined. // 命名构造函数 Person.withNameAgeHeight(this.name, this.age, this.height); Person.fromMap(Map<String, dynamic> map) { this.name = map['name']; this.age = map['age']; this.height = map['height']; } @override String toString() { return "$name $age $height"; }}
如果类中的属性使用 final 修饰,那么在必须在构造函数中对 final 属性进行赋值,但要注意的是,不能在构造函数的函数体内对 final 属性赋值:
class Person { final String name; final int age; // Person(this.name, this.age) {} // 必传参数 Person(this.name, {this.age = 10}) {} // (带默认值的)可选参数 // Person(this.name) { // IDE报错:All final variables must be initialized, but 'age' isn't. // this.age = 10; // IDE报错:'age' can't be used as a setter because it's final. // }}
虽然 Dart 不支持在构造函数函数体中对 final 属性赋值,但是可以用 初始化列表 对其赋值:
初始化列表一般与命名可选参数配合使用,当外界调用构造函数时,可以对形参对应的属性进行校验,以及设置初始值。
class Person { final String name; final int age; // function ():xxxxxx {} ,其中xxxxxx部分就是初始化列表 Person(this.name) : this.age = 10 {} // 初始化列表:可能使用 表达式 来为属性赋值 // Person(this.name, {this.age = 10}) {} // (带默认值的)可选参数:只能使用 赋值语句 来为属性赋值 // Person(this.name, {int age}) : this.age = age ?? 10 {} // 初始化列表:可以对形参进行校验,以及设置初始化值 // 初始化列表有多个时,用逗号隔开 // Person(this.name, {int age, double height}) : this.age = age ?? 10, this.height = height ?? 1.88 {}}
重定向构造函数,其实就是在一个构造函数中,去调用另一个构造函数:
class Person { String name; int age; // 构造函数的重定向 Person(String name) : this._internal(name, 0); Person._internal(this.name, this.age);}
在某些情况下,我们希望 传入相同的值 能 返回同一个对象,这时,可以使用常量构造函数:
main(List<String> args) { // 用 const常量 去接收一个常量构造函数的结果,可以省略 const // const p1 = const Person("lqr", 18); const p1 = Person("lqr", 18); const p2 = Person("lqr", 18); print(identical(p1, p2)); // true // 如果用 var变量 去接收一个常量构造函数的结果,则这时省略的不是 const,而是 new!! var p11 = Person("lqr", 18); // ==> var p11 = new Person("lqr"); var p22 = Person("lqr", 18); print(identical(p11, p22)); // false // 必须所有的属性值相同,对象才是同一个 const p111 = Person("lqr", 18); const p222 = Person("lqr", 20); print(identical(p111, p222)); // false}class Person { // 拥有常量构造方法的类中,所有的成员变量必须是final修饰 final String name; final int age; // 一个类中只能有一个常量构造方法,命名构造方法也不行 // const Person(this.name); const Person(this.name, this.age);}
为了满足 传入相同的值,得到相同的对象 这个需求,除了可以使用常量构造函数外,还可以使用工厂构造函数,而且工厂构造函数更加灵活。
main(List<String> args) { final p1 = Person.withName("lqr"); final p2 = Person.withName("lqr"); print(identical(p1, p2));}class Person { String name; String color; // static 修饰的属性是类属性,可以通过类名直接调用 static final Map<String, Person> _nameCache = {}; Person(this.name, this.color); // 普通的构造函数:会自动返回创建出来的对象,不能手动返回 // 工厂构造函数最大的特点:可以手动的返回一个对象 factory Person.withName(String name) { if (_nameCache.containsKey(name)) { return _nameCache[name]; } else { final p = Person(name, "default"); _nameCache[name] = p; return p; } }}
很多时候,我们并不希望外部可以直接访问对象字段,而是经过 setter 和 getter 中转,这样的好处是,可以在 setter 和 getter 中做一些额外的工作,Dart 支持为字段增加 setter 和 getter:
main(List<String> args) { final p1 = Person(); // p1.setName("lqr"); // IDE报错:The method 'setName' isn't defined for the type 'Person'. // setter 和 getter 像字段一样使用 p1.setName = "lqr"; print(p1.getName); // lqr}class Person { String name; // setter // set setName(String name) { // this.name = name; // } set setName(String name) => this.name = name; // getter:注意getter没有() // String get getName { // return this.name; // } String get getName => this.name;}
严格意义上来说,Dart 中的 setter 和 getter 已经不能算是方法了吧。
Dart 中的继承使用 extends 关键字,子类中使用 super 来访问父类。
注意:Dart 只支持单继承。
main(List<String> args) {}class Animal { int age; Animal(this.age); void run() { print("向前跑~"); }}class Person extends Animal { String name; // 必须在初始化列表中,调用父类的构造函数 Person(this.name, int age) : super(age); @override void run() { // super.run(); print("向前走~"); }}// Dart只支持单继承// class SuperMan extends Runner, Flyer{ // IDE报错:Each class definition can hava at most one extends clause.// }
Dart 可以使用 abstract 关键字来定义抽象类:
main(List<String> args) { final s = Shape(); // IDE报错:Abstract classes can't be instantiated.}// 注意2:抽象类不能实例化abstract class Shape { // 抽象方法,没有方法体,也不用 abstract 关键字修饰 int getArea(); String getInfo() { return "形状"; }}// 注意1:继承自抽象类后,必须实现抽象类的抽象方法class Rectangle extends Shape { @override int getArea() { return 100; }}
注意:Dart 中抽象类不能实例化,但也有特殊情况,有工厂构造函数(factory)的抽象类可以实例化,比如:Map。
Dart 使用 implements 关键字来实现多个接口,但奇葩的是,Dart 中没有用来定义接口的关键字,默认情况下所有的类都是隐式接口,即可以 implements 任意类:
注意:要重写被 implements 的类中的所有方法!
main(List<String> args) {}// Dart中没有哪一个关键字是来定义接口的// 没有这些关键字interface/protocol// 默认情况下所有的类都是隐式接口class Runner { void running() { print("跑吧"); }}class Flyer { void flying() { print("飞吧"); }}// 当将一个类当做接口使用时,那么实现这个接口的类,必须实现这个接口中所有方法(不可以在这些方法里使用super)class SuperMan implements Runner, Flyer { @override void flying() { // super.flying(); // IDE报错:The method 'flying' is always abstract in the supertype. } @override void running() {}}
建议:用 abstract class 来声明接口,反正被 implements 的类的所有方法都要重写。
当遇到要复用 2 个类中方法的时候,要怎么办?
Dart 提供了另一个方案:Mixin 混入。可以定义 2 个 mixin 类,通过 with 关键字把这 2 个 mixin 类混入到一个类 A 中,这时类 A 就拥有了它们的方法,从而达到复用 2 个类中方法的目的:
main(List<String> args) { final superMan = SuperMan(); superMan.eating(); // 动作吃东西 superMan.running(); // running(调用的是混入类Runner中的running()) superMan.flying(); // flying}class Animal { void eating() { print("动作吃东西"); } void running() { print("动物running"); }}mixin Runner { void running() { print("running"); }}mixin Flyer { void flying() { print("flying"); }}// Runner中的running()与Animal中的running()冲突,这时会发生覆盖,即SuperMan中的running()调用的是Runner的running()class SuperMan extends Animal with Runner, Flyer {}
类中使用 static 关键字修饰的属性(或方法)就是类属性(或类方法),类属性和类方法可以通过类名直接调用:
main(List<String> args) { Person.courseTime = "8:00"; print(Person.courseTime); Person.gotoCourse();}class Person { // 成员变量 String name; // 静态属性(类属性) static String courseTime; // 对象方法 void eating() { print("eating"); } // 静态方法(类方法) static void gotoCourse() { print("去上课"); }}
枚举使用 enum 关键字来定义,枚举有 2 个常见的属性:
// 枚举:enummain(List<String> args) { final color = Colors.red; switch (color) { case Colors.red: print("红色"); break; case Colors.blue: print("蓝色"); break; case Colors.green: print("绿色"); break; } print(Colors.values); // [Colors.red, Colors.blue, Colors.green] print(Colors.red.index); // 0}enum Colors { red, blue, green,}
集合类型使用泛型:
// List使用时的泛型写法:var names1 = ["abc", "cba", "nba", 111]; // List<Object>var names2 = ["abc", "cba", "nba"]; // List<String>// var names3 = <String>["abc", "cba", "nba", 111]; // IDE报错:The element type 'int' can't be assigned to the list type 'String'.var names3 = <String>["abc", "cba", "nba"]; // List<String>List<String> names4 = ["abc", "cba", "nba"]; // List<String>// Map使用时的泛型写法:var info1 = {"name": "lqr", "age": 18}; // _InternalLinkedHashMap<String, Object>var info2 = <String, String>{'name': 'lqr', 'age': '18'}; // _InternalLinkedHashMap<String, String>Map<String, String> info3 = {'name': 'lqr', 'age': '18'}; // _InternalLinkedHashMap<String, String>
类定义使用泛型:
main(List<String> args) { var point = Point<double>(1.23333, 1.9527); // Point<double> final pointX = point.setAndGetX(5.55); print(pointX); // 5.55}// class Point<T> // 泛型T是Object的子类class Point<T extends num> { // 泛型T是数字类型 T x; T y; Point(this.x, this.y); T setAndGetX(T x) { this.x = x; return this.x; }}
T extends num 用于限制泛型的类型必须是数字,不限制的话则默认是 Object。
库导入的语法:
import '库所在的 uri';
系统库的 uri 一般以 dart: 开头,常见的系统库有:dart:io、dart:async、dart:math 等:
// import 'dart:io';// import 'dart:isolate';// import 'dart:async';// import 'dart:math';// 系统的库:import 'dart:库的名字';import 'dart:math';main(List<String> args) { final num1 = 20; final num2 = 30; print(min(num1, num2));}
注意:dart:core 也是很常用的系统库,不过可以省略不写。
自定义库就是自己项目中定义的其他 dart 文件,比如在 utils 目录下创建一个 math_utils.dart 文件,内容如下:
// utils 目录下的 math_utils.dart 文件内容:int sum(int num1, int num2) { return num1 + num2;}int mul(int num1, int num2) { return num1 * num2;}
这里的 math_utils.dart 文件就是一个自定义库,你可以在其他 dart 文件中导入:
// 导入自定义库可以是相对路径或绝对路径。import 'utils/math_utils.dart';main(List<String> args) { print(sum(20, 30));
那些存放在远程仓库上的库文件就是第三方库,如果项目中需要依赖第三方库,就需要在项目目录下创建一个 pubspec.yaml 文件,其中 dependencies 部分填写要依赖的第三方库的描述(库名: 版本):
name: myProjectNamedescription: my project namedependencies: http: ^0.12.0+4environment: sdk: ">=2.10.0 <3.0.0"
可以在 pub.dev 上查找第三方库的使用说明。
填写好依赖后,需要执行 dart pub get 或 flutter pub get 命令,让终端从服务器上拉取第三方对应的版本文件,之后就可以在代码中使用这个第三方库了:
import 'package:http/http.dart' as http;main(List<String> args) async { var url = 'https://example.com/whatsit/create'; var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'}); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); print(await http.read('https://example.com/foobar.txt'));}
比如:math_utils.dart 库中有 int sum(int num1, int num2) 方法,当前 dart 文件中又定义了 void sum(int num1, int num2) 方法,这 2 个方法名相同,但返回值不同:
import 'utils/math_utils.dart';main(List<String> args) { print(sum(20, 30)); // IDE报错:This experssion has a type of 'void' so its value can't be used.}// void sum(num1, num2) // 参数类型可以不写,默认是 dynamicvoid sum(int num1, int num2) { print(num1 + num2);}
这时,当前 dart 文件识别到的是自己的 sum()方法,而实际上我们想要使用的是 math_utils.dart 库中 sum()方法,这就发生了库方法名冲突,可以使用 as 关键字给库起别名来解决:
import 'utils/math_utils.dart' as mUtils;main(List<String> args) { print(mUtils.sum(20, 30));}void sum(sum1, sum2) { print(sum1 + sum2);}
默认 import 进来的库中所有内容都是可见的,但是很多时候我们希望只导入库中的某些内容,或者希望隐藏库中的某些内容,这时可以使用 show 和 hide 关键字:
// import "utils/math_utils.dart" show sum, mul;import "utils/math_utils.dart" show sum;main(List<String> args) { print(sum(20, 30)); print(mul(20, 30)); // IDE报错:The function 'mul' isn't defined.}
// import "utils/math_utils.dart" hide sum, mul;import "utils/math_utils.dart" hide sum;main(List<String> args) { print(sum(20, 30)); // IDE报错:The function 'sum' isn't defined. print(mul(20, 30));}
有些时候,可能需要在一个 dart 文件中,导入 n 个库,这还是能接受的:
import 'utils/math_utils.dart';import 'utils/date_utils.dart';import ...main(List<String> args) { print(sum(20, 30)); print(dateFormat());}
但如果是 n 个 dart 文件都要导入相同的 m 个库呢?很明显,需要有个统一批量导入库文件的方案:可以创建一个新的 dart 文件,使用 export 关键字统一导库。
// utils 目录下的 utils.dart 文件内容:export 'math_utils.dart';export 'date_utils.dart';export ...
这时,其他 dart 文件中,只需要导入这个 utils.dart 就好了:
import 'utils/utils.dart';main(List<String> args) { print(sum(20, 30)); print(dateFormat()); // math_utils.dart中的_min()无法被外界调用 // print(_min(20, 30)); // IDE报错:The function '_min' isn't defined.}
注意:Dart 语言中没有可见性修饰符,即没有 public、protected、private。如果某个成员属性或方法不想被外界(其他 dart 文件)调用,可以在名字前加个下划线 _。