开始前请至少准备:
0.一定的面向对象编程基础
Flutter应用使用Dart语言进行开发
那么我们来快速熟悉一下Dart语言,以免之后在学习Flutter时被语言关卡住
如果你熟悉java和python语法,那么学习Dart语言几乎没有什么难度
这篇笔记的所有内容总结自Dart官网,如果有更多细节方面的问题,可以移步https://www.dartlang.org/guides/language/language-tour阅读官方说明
注释语法:
// 单行注释
/*
多行注释
*/
/// 文档注释
关键字:
保留关键字:
assert break case catch class const continue
default do else enum extends false final
finally for if in is new null rethrow return super
switch this throw true try var void while with
上下文关键字(contextual keywords):
仅在特定位置被理解为关键字
async hide on show sync
内建标识符关键字(built-in identifiers):
不可作为class名或type名或import前缀
abstract as covariant deferred
dynamic export external factory
Function get implements import
interface library mixin operator
part set static typedef
异步支持关键字:
await yield
变量:
声明方式:
类型/var/dynamic 变量名 = 变量值;
变量存储引用,所有的变量值都是object
变量的默认值都是null
var声明的变量在被赋值时,编译器会自动推断类型,一旦类型推断完毕,变量的类型将不能发生变化
dynamic声明的变量类型是不固定的
相同类型多变量的声明:
int x,y = 1;
一起声明的变量将被赋予相同的值
常量:
final或const关键字修饰的变量是常量
常量最多被赋值一次
赋值后无法改变
const常量的特殊之处:
const常量要求在编译期就能够完全确定它的值
相同的const常量对象可以多次声明,但只会被创建一次
const修饰的集合和构造类实例中的所有值也必须都是const常量或在编译器可以确定的值
Dart内建类型:
int和double:
int: 64字节整型-2^63 - 2^63 - 1
double: 64字节双精度浮点型
int和double支持+,-,*,/,~/运算符
int支持位运算符,支持10进制和16进制(数值加0x前缀)表示法
double支持科学记数法,如1.1e2表示1.1*10^2
零除不会出现异常,而是得到NaN
字符串(String)类型:
使用双引号或者单引号包含字符串文本
在字符串中使用占位符传递变量或表达式
String str='Dart Language';
print('字符串 $str 的长度是: ${str.length}');//打印结果:字符串 Dart Language 的长度是: 13
const字符串中占位符表示的变量也必须是const的
字符串比较使用==
String str1='Dart';
String str2='Dart';
print(str1==str2);//打印结果:true
字符串可使用+进行拼接
使用'''或"""创建多行字符串
String multiLine='''
Dart
Flutter
''';
使用r前缀的字符串避免转义
String str=r'Dart\nFlutter';
print(str);//打印结果: Dart\nFlutter
字符串与数值互相转换:
int str2int=int.parse('1');//String转int
double str2double=double.parse('3.14');//String转double
String int2str=1.toString();//int转字符串
String double2str=3.1415926.toString();//double转字符串
String double2strFixed=3.1415926.toStringAsFixed(2);//double转字符串并保留小数点后2位
布尔(bool)类型:
true/false
数组(List)类型:
List list=[1,2,3];
Map类型:
Map map={
'key1':'value1',
'key2':'value2'
};
map['key3']='value3';//加入新的键值
Runes类型:
用于表示unicode码
Symbol类型:
没看懂,也不常用,以后用到了再回来补
函数:
函数也是对象,类型是Function
声明方式:
返回值类型 函数名(参数表){
函数体
}
返回值类型可省略,类型将被推断
任何函数没有return语句,可以编译通过,但是运行期返回值是null
Dart函数仅能返回一个结果
=>语法用于声明仅有一行函数体的函数,函数体不需要return语句
int add(int i1,int i2) => i1+i2;
函数可选参数:
可以是位置的或者命名的,但两种可选择参数在函数中不能同时存在
可选参数必须放在必填参数之后
可选参数可以给定默认值
否则默认值都是null
void positionalOptParamExample(String param1,[int param2,bool param3=false]){
//位置可选参数声明方式
//param3被给定默认值false
}
void namedOptParamExample(String param1,{int param2=0,bool param3}){
//命名可选参数声明方式
//param2被给定默认值0
}
含有多个位置可选参数的函数在调用时,只能按顺序省略处于可选参数表后面的参数
比如一个函数有三个位置可选参数,你可以只传递第一个,省略后两个
如果你要传递第三个参数,那么前两个参数也必须要传递
含有命名可选参数的函数在调用时需要以名值对(key:value)的形式传递命名参数
namedOptParamExample('param1',param2:1);
对于命名参数的传递顺序没有要求
Flutter中Widget构造函数的传参方式就是可选命名关键字
main函数:
程序入口,有一个可选的List
函数作为对象
将函数(或者lambda表达式)赋值给变量
通过该变量进行函数调用
main(){
var hello=func;
var append=(String s1,String s2)=>print(s1+' '+s2);
hello();//打印结果: hello world
append('hello','dart');//打印结果: hello dart
}
void func(){
print('hello world');
}
Dart支持闭包
closure(int i){
return (double d)=>d*i;//使用lambda表达式产生一个匿名函数,并作为外层函数的结果返回
}
运算符:
算术运算符:
+,-,*,%,++,--不细说
除:/得到的结果是double类型
地板除:~/得到的结果是int类型
比较运算符:
==,!=,>,<,>=,<=
类型测试运算符:
is: obj is Type,判断对象是否是某个类型的实例
is!: obj is! Type,判断对象是否不是某个类型的实例
as: 类型转换,obj as Type,将对象转换为指定类型
赋值运算符:
=
复合赋值运算符"
+=,-=,*=,/=,~/=,%=.??=...
这里有个值得一说的??=运算符
a ??=value;// a如果为null,则赋值为value,否则,a的值不变
逻辑运算符:
&&, ||, !
位运算符:
& | ^(异或) ~(取反) << >>
条件运算符:
? :(三目运算符),??
a??b的含义是若a为null,返回b,否则返回a
其他值得一提的操作符
?.
在进行对象的方法或者属性调用时,如果对象为null,直接返回null,若对象非空,返回方法结果或者属性值
流程控制:
分支:
if(exp){
}
if(exp){
}else{
}
if(exp){
}else if(exp){
}else{
}
switch(obj){
case case0:
break;
case case1:
break;
......
default:
....
}
循环:
for(var i=0;i<3;i++){
}
for(var x in collection){
}
while(exp){
}
do{
}while(exp)
支持break/continue控制流程,支持标签语法
断言
用于调式,debug模式下如果断言失败,抛出异常
生产环境下断言失效
assert(booleanExpr);
assert(booleanExpr,"断言异常时附加的自定义信息")
异常:
dart的所有异常都是运行期异常
在函数签名中,无法像java那样声明可能产生的异常
dart可以抛出任意非空对象作为异常,这些对象无需是Exception的实例,但是不建议你在生产中这样做
throw FormatException("自定义异常信息");//异常抛出示例
throw "任意非空对象作为异常示例";//抛出任意非空对象
try-catch-finally语法:
try{
}on AException catch (e){
}on BException catch (e){
}finally{
}
如果希望catch所有类型的异常,可以省略on XxException
如果不需要在catch语句块中调用异常对象,则可以省略catch(e)
catch语句可以指定两个参数catch (e,s),第二个参数表示异常栈对象
可以使用多个catch并列来分别捕获不同的异常
注意并列异常之间的继承关系,子类异常应处于父类异常之前
如果希望在捕获到异常后再次抛出,可以在catch语句块中使用rethrow;
可以在try语句块或catch语句块之后追加finally语句块
类:
类的声明:
class Person {
String name;
bool gender;
}
成员变量和方法:
定义在类中的变量称为成员变量
定义在类中的函数称为方法
类成员的访问控制:
Dart没有public,private等访问控制关键词
变量名或方法名如果以下划线_开头,则认为是私有的
getter/setter:
类的成员变量都有一个隐式的getter方法
用于读取该变量的值
非final成员变量有一个隐式setter方法
用于写入该变量的值
可以通过实现getter/setter方法来为类附加额外的成员变量(这种成员变量通常是通过对其他成员进行运算得到),使用关键字get和set
class Person{
String get name=>this.name;
set name(String name)=>this.name=name;
}
类的构造函数:
类通过构造函数实例化对象
在Dart2中,调用构造函数时,可以省略new关键字
一般构造函数的声明:
class Person {
String name;
bool gender;
//构造函数的声明
Person(String name, bool gender) {
this.name = name;
}
}
Dart还提供一种语法糖来简化构造函数的声明
class Person {
String name;
bool gender;
Person(this.name, this.gender) ;
}
若不声明构造函数
类会具有一个隐式的未命名的无参构造
命名构造函数:
上面说过Dart不允许方法重载,如果希望在一个类中声明多个构造函数,可以使用命名构造函数
class Person {
String name;
bool gender;
Person(this.name, this.gender) ;
//命名构造函数
Person.init(String name){
this.name=name;
}
}
调用父类构造函数:
构造函数不会被继承
默认情况下,子类的构造函数会隐式调用父类的未命名无参构造,如果父类没有未命名无参构造,就必须显式地调用一个父类的构造函数
//这里假设Person继承了一个父类,这个父类有一个id属性,且没有未命名无参构造
Person(int id,String name, bool gender) : super(id){
this.name=name;
this.gender=gender;
}
构造函数的初始化列表:
在Dart中还可以为构造函数声明初始化列表,在对象实例化之前完成一些校验或者赋值操作
初始化列表通常用于类的final属性的赋值
class Student{
final int id;
final String name;
//使用初始化列表的构造函数
Student(int id,String name):this.id=id,this.name=name;
}
总结一下一个具有初始化列表的构造函数在被调用时发生了什么:
1.运行初始化列表
2.调用父类构造函数
3.执行当前构造函数中的逻辑
构造函数的重定向:
通常一个类声明多个构造函数时,只是简单地调用已经声明的构造函数中的赋值逻辑,这时,可以将要声明构造函数重定向至其他构造函数,避免相同的逻辑被多次编写
注意:
1.仅能重定向至未命名的构造函数
2.重定向构造函数的方法体必须是空的
class Student{
final int id;
final String name;
Student(int id,String name):this.id=id,this.name=name;
//init构造函数被重定向至上面的构造函数
Student.init(String name):this(0,name);
}
const构造函数:
如果希望一个类产生的实例是不变的,可以声明const构造函数
const构造函数要求类的所有成员都是final的
调用const构造器时需要const关键字来修饰,否则实例化的对象将不是const的
main(){
const School school1=const School('北京大学','地址...');//const对象
School school2=School('清华大学', '地址...');//非const对象
}
class School{
final Stringname;
final Stringaddress;
const School(this.name,this.address);//const构造函数
}
工厂构造函数:
工厂模式构造函数使用factory关键字修饰
通常,这种构造函数不会在每次调用时返回一个新的实例,而是可能从缓存中获取已经被创建的实例
class Logger {
final String name;
bool mute =false;
static final Map_cache = {};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
}else {
final logger =Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
}
抽象方法和抽象类
没有方法体的类成员方法是抽象方法,使用abstract关键字修饰
包含抽象方法的类也需要由abstract关键字修饰
抽象方法可以由非抽象的子类进行实现
但是如果希望抽象类也可以被实例化,则需要声明一个factory构造函数
隐式接口:
你可以用一个类实现另一个类(不一定是抽象类),因为每个类都有一个隐式的接口,这样,一个api就有了多种实现
接口实现使用implements关键字
class A {
String myFunc(String str) {
return "...$str...";
}
}
class B implements A {
String myFunc(String str) {
return "!!!$str!!!";
}
}
继承:
Dart支持单继承,使用extends关键字
子类可以重写父类中的方法
可以使用@override注解对方法重写进行检查
操作符重写:
在Dart中,操作符也可以被理解为对对象进行方法调用,因此操作符也是可以重写的
< > <= >= - + / ~/ * % | ^ & << >> [] []= ~ ==等操作符支持重写
class Apple {
ApplePenoperator +(Pen pen) =>ApplePen();
}
class Pen {}
class ApplePen {}
main(){
Apple apple=Apple();
Pen pen=Pen();
ApplePen applePen=apple+pen;
}
==操作符的重写类似于java中的equals()方法重写,需要同时重写hashCode的getter方法
枚举:
声明方式:
enum Color {red, green, blue }
使用.index获取枚举的声明顺序,从0开始
使用Color.value获取包含所有枚举项的List
枚举可以用于switch
mixin:
上面说过Dart是单继承的,如果一个类已经继承了另一个类,但是还希望复用第三个,第四个类中的属性和方法,就可以使用mixin(mix in两个英文单词的连写,以为混入,不要读成迷信)
mixin类的声明使用mixin关键字,在类中混入其他类使用with关键字
//声明父类
class SuperClass {
void funcA() {
print("funcA called");
}
}
//声明第一个mixin类
mixin MixinClassA {
void funcB() {
print("funcB in MixinClassA called");
}
}
//声明第二个Mixin类
mixin MixinClassB {
void funcB() {
print("funcB in MixinClassB called");
}
void funcC() {
print("funcC called");
}
}
//这个类继承了 SuperClass类,同时混入了MixinClassA和 MixinClassB两个类,它的实例可以调用这三个类的方法,注意 MixinClassA和 MixinClassB中都实现了 funcB方法, SubClass实例将调用 MixinClassB对于 funcB的实现,即后混入的方法实现会覆盖先混入的方法实现
class SubClass extends SuperClass with MixinClassA, MixinClassB {}
类变量和类方法:
在类中使用static关键字修饰的变量和方法可以直接使用类名.的形式调用,无需实例化对象
static变量在真正调用前不会初始化
在java中通常会用static来修饰工具类方法
Dart建议用顶级函数来实现工具方法,而不是写在类中
顶级变量,顶级函数:
Dart中不是所有变量和函数都要定义在类中
直接定义在dart文件中的变量,函数被称为顶级变量,顶级函数
泛型:
泛型集合:
泛型集合使编译器在编译阶段可以确定集合元素的类型
List list=[1,2,3];
Map map={1:'value1',2:'value2'};
泛型类和泛型方法:
class GenericClass
{ void func(T param) {
...
}
}
库:
库的导入:
使用import关键字
Dart内建库的导入
import "dart:xxx";
导入其他第三方库"
import "package:xxx/xxx.dart";
库别名:
别名用于解决导入的两个库中存在冲突的标识符的问题,使用as关键字
import "package:lib1/lib1.dart";
import "package:lib2/lib2.dart as lib2";
Element e1=Element();
lib2.Element e2=lib2.Element();
库的部分导入:
import "package:lib1/lib1.dart" show foo,bar;//只导入库的部分内容
import "package:lib1/lib1.dart" hide foo,bar;//不导入库的部分内容
库的懒加载:
使用deferred关键字
在需要时加载库,而不是运行应用时加载库,可以提高应用的启动效率
对于很少使用的库,懒加载可以避免资源的浪费
import 'package:lib1/lib1.dart' deferred as lib1
当需要调用库时,调用lib1.loadLibrary()方法完成加载
可以多次调用loadLibrary方法,但库只会加载一次
异步:
Dart中并不支持严格意义上的异步,而是将逻辑延迟执行,这些被延迟执行的逻辑被放入事件队列,并在最终被遍历执行.Dart实际上是使用单线程模型来实现所谓的异步的.(这一部分我理解的还不是很透彻,以后用到了再回来补充.)
使用关键字await和async
异步方法返回一个Future
Future
version() async { return "1.0.0";
}
在上面的async方法体中,返回值是String类型,但是会被封装为Futrue
如果异步方法什么也不打算返回,则返回值类型为Future
异步方法调用时,可附加await关键字,如果在一个方法的方法体中使用了await关键字,则这个方法需要声明为async方法
生成器:
Dart支持两种生成器
1.同步生成器,返回一个Iterable对象,使用sync*关键字
2.异步生成器,返回一个Stream对象,使用async*关键字
同步生成器:一个自然数生成器的实现
Iterable
naturalsGenerator(int limit) sync* { int k =0;
while (k < limit) {
//使用yield而不是return返回值
yield k++;
}
}
Stream
naturailsGeneratorAsync(int limit) async* { int k =0;
while (k < limit) {
yield k++;
}
}
使用await-for语句对Stream进行处理
var stream = naturailsGeneratorAsync(10);
await for (var i in stream) {
...
}
Callable类:
在类中实现call方法,该类对象可以视为方法进行调用
Isolates:
上面说过Dart是一种单线程模型的语言,每个Isolates分配独立的内存,不同的Isolates之间不能互相访问对方内存中的资源
Typedef:
Dart中,方法也是对象,这个对象的类型是Function
但是Function类不会记录具体方法对象的传参方式和返回值类型
使用typedef声明一个方法类型,所有与该方法类型传参方式,返回值类型一致的方法,都是该方法类型的对象
typedef int tdFunc(int i, String str);
任何参数为int i,String str,且返回值为int的方法,都是类型为tdFunc的对象
元数据注解:
使用@符号附加的类,变量,标注与类型/方法/参数/变量前,与反射搭配使用,常用的注解有@override(检查重写),@deprecated(过时的类/方法/变量),@required(必传参数)
举例实现一个元数据注解
class Todo {
final Stringwho;
final Stringwhat;
const Todo(this.who, this.what);
}
@Todo("我", "实现这个方法")
void anUnimplMethod(){
}
级联标记:
用于连续对同一个对象进行多次属性设置或方法调用
可以简化语法
使用方法为:
obj
..属性/方法调用
..属性/方法调用
.......;
比如我们声明一个有很多成员的类:
class Student{
int id;
String name;
bool gender;
String address;
double height;
double weight;
}
在java中,你在构造这个对象后,需要调用一大堆setter方法来设置成员的值
在Dart中,我们使用级联语法来完成这个任务
main(){
Student s=Student();
s
..id=1
..name='小明'
..address='家庭住址'
..gender=true
..height=170.5
..weight=99.9;
}