Dart基础学习
概述
应用场景
版本
数据类型
变量
var
dynamic和Object
常量final和const
数值型
数值型num
字符串(String)
布尔型bool
列表List(数组)
Map
dynamic动态类型
运算符
算术运算符
关系运算符
逻辑运算符
赋值运算符
条件表达式
控制语句
if语句
for语句
while语句
break和continue
switch...case语句
函数/方法
main方法定义
方法定义Function
可选参数
默认参数
方法对象
匿名方法
闭包
面向对象编程
类与对象
计算属性
构造方法
命名构造方法
常量构造方法
工厂构造方法
初始化列表:
静态成员static
对象操作符
条件成员访问?.
类型转换as
是否指定类型is/is!
级联(链式)操作..
对象call方法
继承
继承中的构造方法
构造方法执行顺序
抽象类abstract
接口
Mixins
操作符覆写(重载运算符)
可覆写的操作符
枚举&类型
枚举
泛型<>
异步
Future
.then/.catchError
.whenComplete
.wait
Async/await
回调地狱(Callback Hell)
使用Future消除Callback Hell
使用async/await消除callback hell
Stream
Dart和Java及JavaScript对比
Dart vs Java
Dart vs JavaScript
Dart基础学习
概述
- Dart是Google发布的一门开源编程语言
- Dart初期目标是成为下一代的web开发语言
- Dart目前已可用于全平台开发
- Dart是一门面向对象的编程语言
应用场景
- Web开发
- 跨平台移动应用开发( Fltter )
- 脚本或服务端开发
版本
Dart1.x为稳定版本
Dart2.x目前为开发版本
案例:
hello_world.dart
//Main程序入口
void main(){
//控制台打印
print("Hello World!");
}
- main方法是固定写法,它是程序入口
- print方法可以在控制台输出内容
- 通过文件选择run ,可以运行文件中的main方法
数据类型
变量
- 使用var声明变量,可赋予不同类型的值
var a=12;
- 未初始化时,默认值为null
- 使用final声明一个只能赋值一次的变量
final c=30
var
类似于JavaScript中的var
,它可以接收任何类型的变量,但最大的不同是Dart中var变量一旦赋值,类型便会确定,则不能再改变其类型
var t;
t = "hi world";
// 下面代码在dart中会报错,因为变量t的类型已经确定为String,
// 类型一旦确定后则不能再更改其类型。
t = 1000;
dynamic和Object
Object
是Dart所有对象的根基类,也就是说所有类型都是Object
的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object
声明的对象.dynamic
与var
一样都是关键词,声明的变量可以赋值任意对象。dynamic
与Object
,声明的变量可以在后期改变赋值类型。dynamic
声明的对象编译器会提供所有可能的组合, 而Object
声明的对象只能使用Object的属性与方法, 否则编译器会报错。
dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代码没有问题
t = 1000;
x = 1000;
dynamic a;
Object b;
main() {
a = "";
b = "";
printLengths();
}
printLengths() {
// no warning
print(a.length);
// warning:
// The getter 'length' is not defined for the class 'Object'
print(b.length);//只能使用Object类型默认的属性和方法,子类的不能使用了
}
//变量a不会报错, 变量b编译器会报错
常量final和const
- 一个
final
变量只能被设置一次. const
变量是一个编译时常量(编译的时候就初始化了--定义的时候就要初始化).final
变量在第一次使用时被初始化。- 被
final
或者const
修饰的变量,变量类型可以省略
//可以省略String这个类型声明
final str = "hi world";
//final String str = "hi world";
const str1 = "hi world";//定义的时候就要初始化
//const String str1 = "hi world";
数值型
- 数值型-Number
- 字符串-String
- 布尔型-Boolean
- 列表-List
- 键值对-Map
- Runes/Symbols(基本不用)
数值型num
类型:
- 整形Int
- 浮点型 double
数值型操作
- 运算符:+、-、*、/、~/(取整)、%
- 常用属性: isNaN(是否是非数字),isEven(是否是偶数),isOdd(是否是奇数)等
- 常用方法:
- abs(),取绝对值。
- round(), 四舍五入。
- floor()、向下取整。
- ceil(),向上取整。
- toInt()、
- toDouble()
void main(){
//num是数值类型,因此可以设置整数和浮点数。
num a = 10;
a = 12.5;
//是整数,仅仅只能复制为整数,不能设置古典色。
int b = 20;
// b = 20.5;
//浮点数不能设置整数。
double c = 10.5;
// c = 30;
print(b + c);//30.5
print(b - c);//9.5
print(b * c);//210.0
print(b / c);//1.9047619047619047
print(b ~/ c);//1
print(b % c);//9.5
print(0.0 / 0.0);//NaN 0.0/0.0非数字,其他都是数字
print(b.isEven);//true
print(b.isOdd);//false
int d = 11;
print(d.isEven);//false
print(d.isOdd);//true
int e = -100;
print(e.abs());//100
double f = 10.3;
print(f.round());//10
print(f.floor());//10
print(f.ceil());//11
print(f.toInt());//10,取整
print(d.toDouble());//11.0,向上取整,获取为小数
}
字符串(String)
创建
- 使用单引号,双引号创建字符串
- 使用三个引号或双引号创建多行字符串
- 使用r创建始raw字符串
操作:
- 运算符:+、*、==、[]
- 插值表达式:
$(lexpression}
(字符串中表达式) - 常用属性: length, isEmpty, isNotEmpty
- 常用方法:
- contains()
- subString()
- startsWith(),
- endsWith()
- indexOf(),
- lastIndexOf()
- toLowerCase(),
- toUpperCase()
trim(), - trimLeft(),
- trimRight()
split(), - replacexXX()
void main(){
String str1 = 'Hello';//""
//```设置多行
String str2 = '''Hello
Dart''';//"""
print(str2);
//Hello
// Dart
String stra = 'Hello \n Dart';//单引号\n会换行
print(stra);
//Hello
// Dart
String str3 = r'Hello \n Dart';//r开头,\n等会是字符创
print(str3);
//Hello \n Dart
String str4 = "This is my favorite language";
print(str4 + "New");//This is my favorite languageNew
print(str4 * 2);//复制2遍 This is my favorite languageThis is my favorite language
print(str3 == str4);//false
print(str4[1]);//h
int a = 1;
int b = 2;
print("a + b = ${a + b}");//a + b = 3
print("a = $a");//只有一个字符,可以省略{} a = 1
print(str4.length);//28
print(str4.isEmpty);//false
print(str4.contains("This"));//true
print(str4.substring(0,3));//Thi
print(str4.startsWith("a"));//false
print(str4.endsWith("ge"));//true
var list = str4.split(" ");//[This, is, my, favorite, language]
print(list);
print(str4.replaceAll("This", "That"));//That is my favorite language
}
布尔型bool
布尔值只有true和false
bool isTrue = true;
bool isFalse = false;
print("Hello".isEmpty);//false
列表List(数组)
创建
- 创建List :
var list = [1, 2, 3];
- 创建不可变的List :
var list = const [1, 2, 3];
- 构造创建:
var list = new List();
也可以定义固定类型的var list = new List<String>();
常用操作:
- [],length
- indexOf(),lastIndexOf()
- add(),insert()
- sort()排序,sublist()
- remove(),clear()
- shuffle()打乱,asMap()转map,forEach()
- 倒序
- 修改值
fillRange(s,e,value)
join("字符串")
,以字符串分割,转为字符串split("字符串")
void main(){
var list1 = [1,2,3,"Dart",true];//[1, 2, 3, Dart, true]
print(list1);
print(list1[2]);//3
list1[1] = "Hello";//[1, Hello, 3, Dart, true]
print(list1);
var list2 = const [1,2,3];
// list2[0] = 5;
var list3 = new List();
var list = ["hello","dart"];
print(list.length);//2
list.add("New");//[hello, dart, New]
print(list);
list.insert(1, "Java");//[hello, Java, dart, New]
print(list);
list.remove("Java");//[hello, dart, New]
print(list);
// list.clear(); //[]
// print(list);
print(list.indexOf("dart1"));//-1
list.sort();
print(list);//[New, dart, hello],按照字母牌,大写字母比小写字母小
print(list.sublist(1));//[dart, hello]
list.forEach(print);
//New
//dart
//hello
list=list.reversed.toList();//只调用reversed方法会是(1,2,3),不是list
list.fillRange(1,3,"aa");
//将list的下表为[1,3)的值都改为"aa"
}
Map
创建
var language = {first': 'Dart' 'second': Java' };
- 创建不可变Map:
var language = const { 'first': 'Dart','second":'Java'};
- 构造创建:
var language = new Map();
void main(){
//创建法1
var map1 = {"first":"Dart",1:true,true:"2"};
print(map1);//{first: Dart, 1: true, true: 2}
print(map1["first"]);//Dart
print(map1[true]);//2
map1[1] = false;
print(map1);//{first: Dart, 1: false, true: 2}
var map2 = const {1:"Dart",2:"Java"};
// map2[1] = "Python";//不能改变值
var map3 = new Map();
var map = {"first":"Dart","second":"Java","third":"Python"};
print(map.length);//3
// map.isEmpty;
print(map.keys);//(first, second, third)(是list)
print(map.values);//(Dart, Java, Python)
print(map.containsKey("first"));//true
print(map.containsValue("C"));//false
map.remove("third");
print(map);//{first: Dart, second: Java}
map.forEach(f);//forEach遍历每一个字段,f调用f方法,传入key,value;
//key=first,value=Dart
//key=second,value=Java
var list = ["1","2","3"];
print(list.asMap());//{0: 1, 1: 2, 2: 3} //转map是 下标:value
}
void f(key,value){
print("key=$key,value=$value");//${} 简写
}
dynamic动态类型
使用var
定义一个字段,字段类型可以随便变换,其实,var定义的就是一个dynamic
类型
void main(){
var a;
a = 10;
a = "Dart";
dynamic b = 20;//定义,动态类型
b = "JavaScript";
var list = new List<dynamic>();
list.add(1);
list.add("hello");
list.add(true);
print(list);
}
运算符
算术运算符
- 加减乘除,取整取余:
+,-,*, /,~/,%
- 递增递减:
++var, var++,--var, var--
void main(){
int a = 10;
int b = 2;
print(a + b);//12
print(a - b);//8
print(a * b);//20
print(a / b);//5.0
print(a ~/ b);//5
print(a % b);//0
print(a++);//10
print(++a);//12
print(a--);//12
print(--a);//10
}
关系运算符
- 运算符:
==,!=, >, <,>=,<=
- 判断内容是否相同使用
==
判断对象不适合
void main(){
int a = 5;
int b = 3;
print(a == b);//false
print(a !=b );//true
print(a > b);//true
print(a < b);//false
print(a >= b);//true
print(a <= b);//false
String strA = "123";
String strB = "123";
print(strA == strB);//true
strB = "123 ";
print(strA == strB);//false
}
逻辑运算符
- 运算符:
!取反
,&&且
,||或
void main(){
bool isTrue = true;
print(!isTrue);//false
bool isFalse = false;
print(isTrue && isFalse);//false
print(isTrue || isFalse);//true
String str = "";
print(str.isEmpty);//true isEmpty空字符串
str=null;
print(str.isEmpty);//会异常,null不能判断
}
赋值运算符
- 基础运算符:
=, ??=左侧有值不变,左侧没值,赋值
- 复合运算符:
+=,-=,*=,/=,%=, ~/=
void main(){
int a = 10;
int b = 5;
b ??= 10;//b有值,不设置,没有值,设置为右侧的值
print(b);//5
a += 2;
print(a);//12
a -= b;
print(a);//7
a *= b;
print(a);//35
// a /= b;(/需要double)
a ~/= b;
print(a);//7
a %= b;
print(a);//2
}
条件表达式
- 三目运算符:
condition ? expr1 : expr2
- ??运算符:
expr1 ?? expr2
第一个表达式值不为空,使用第一个,为空使用第二个
int gender = 1;
String str = gender == 0 ? "Male=$gender" :"Female=$gender";
print(str);//Female=1
String a = "Dart";
String b = "Java";
String c = a ?? b;
print(c);//Dart
控制语句
if语句
void main(){
int score = 100;
if(score >= 90){
if(score == 100){
print("完美");
}else {
print("优秀");
}
}else if(score > 60){
print("良好");
}else if(score == 60){
print("及格");
}else {
print("不及格");
}
//完美
}
for语句
- for
- for..in
void main(){
var list = [1,2,3,4,5];
for(var index = 0;index < list.length;index++){
print(list[index]);
}
print("---------");
for(var item in list){
print(item);
}
}
1
2
3
4
5
---------
1
2
3
4
5
while语句
- while
- do...while
int count = 0;
while(count < 3){
print(count++);
}
print("---$count----");
do{
print(count--);
}while(count > 0 && count < 3);
0
1
2
---3----
3
2
1
break和continue
- 终止循环:break
- 跳出当前循环:continue
var list = [1,2,3];
for(var item in list){
if(item == 2) {
// break;
continue;
}
print(item); //1 3 输出
}
print("-----");
var list2 = [4,5,6];
for(var item1 in list){
if(item1 == 2){
break;
}
for(var item2 in list2){
if(item2 == 5){//第二个到5,跳出
break;
}
print(item2);
}
}
1
3
-----
4
switch...case语句
- 比较类型:
num , String,编译期常量,对象,枚举
- 非空case必须有一个break
- default处理默认情况
continue跳转标签
void main(){
String language = "Java";
switch(language){
case "Dart":
print("Dart is my favorite");
break;
case "Java":
print("Java is my favorite");
break;
case "Python":
print("Python is my favorite");
break;
default:
print("None");
}
//Java is my favorite
switch(language){
Test:
case "Dart":
print("Dart is my favorite");
break;
case "Java":
print("Java is my favorite");
continue Test;
// break;
case "Python":
print("Python is my favorite");
break;
default:
print("None");
}
//Java is my favorite 第一步从switch跳转到java的case输出,然后跳转到Test,输出dart的内容,break
//Dart is my favorite
}
函数/方法
Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。
main方法定义
- main方法可以设置参数
void main(){}
void main(...){}
案例
void main(){print(123);}
void main(List args){print(args);}
运行main方法
- 使用工具直接右键运行
- 命令行运行
进入dart文件目录
dart 文件 参数
例如 上面第二个main: dart test2.dart 1 2 3 //1 2 3会作为list的3个字段传入
输出为 [1, 2, 3]
方法定义Function
返回类型方法名(参数1,参数2.,.){
方法体..
return返回值
}
- 方法也是对象,并且有具体类型Function
- 返回值类型、参数类型都可省略
- 返回值类型没有定义,默认当做
dynamic
类型
typedef bool CALLBACK();//校验类型
//不指定返回类型,此时默认为dynamic,不是bool
isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
void test(CALLBACK cb){
print(cb());
}
//报错,isNoble不是bool类型,是dynamic
test(isNoble);
- 返回值类型没有定义,默认当做
- 箭头语法:
=> expr
是{ return expr;}
缩写。只适用于一个表达式 - 方法都有返回值。如果没有指定,默认
return null
void main(List args){
print(args);
print(getPerson("张三", 18));
//getPerson:name=张三,age=18
print(getPerson2("张三", 18));
//getPerson2:name=张三,age=18
print(printPerson("李四", 20));
//name=李四,age=20
//null
}
int gender = 1;
String getPerson(String name,int age){
return gender == 1 ? "getPerson:name=$name,age=$age":"Test";
}
//返回类型可以省略,参数类型可以省略
//只有一句return,可以使用=>表达式
getPerson2(name,age) => gender == 1 ? "getPerson2:name=$name,age=$age":"Test";
printPerson(name,age){
//没有设置返回值,默认返回了null
print("name=$name,age=$age");
}
- 对于只包含一个表达式的函数,可以使用简写语法
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
-----------
bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ;
- 函数作为变量
var say = (str){
print(str);
};
say("hi world");
- 函数作为参数传递
void execute(var callback) {
callback();
}
execute(() => print("xxx"))
可选参数
- 可选命名参数: ( param1,param2,...}:需要设置key
- 可选位置参数: [ paraml,param2....]:包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:
注意,不能同时使用可选的位置参数和可选的命名参数
void main(){
//没有传的值是null
printPerson("李四");//name=李四,age=null,gender=null
//可选参数传入的时候需要标明key
printPerson("李四",age: 20);//name=李四,age=20,gender=null
printPerson("李四",age: 20,gender: "Male");//name=李四,age=20,gender=Male
printPerson("李四",gender: "Male");//name=李四,age=null,gender=Male
//没有传的值是null
printPerson2("张三");//name=张三,age=null,gender=null
//传入参数需要顺序传入,不能乱序
printPerson2("张三",18);//name=张三,age=18,gender=null
printPerson2("张三",18,"Female");//name=张三,age=18,gender=Female
}
//{}中是可选参数
printPerson(String name,{int age,String gender}){
print("name=$name,age=$age,gender=$gender");
}
//[]中是可选参数
printPerson2(String name,[int age,String gender]){
print("name=$name,age=$age,gender=$gender");
}
默认参数
- 使用=在可选参数指定默认值
- 默认值只能是编译时常量
void main(){
printPerson("李四");//name=李四,age=30,gender=Female
printPerson("李四",age: 20);//name=李四,age=20,gender=Female
printPerson("李四",age: null);//name=李四,age=null,gender=Female
printPerson("李四",gender: "Male");//name=李四,age=30,gender=Male
}
//设置默认值
printPerson(String name,{int age = 30,String gender = "Female"}){
print("name=$name,age=$age,gender=$gender");
}
方法对象
- 方法可作为对象赋值给其它变量
- 方法可作为参数传递给其它方法
void main(){
//定义方法变量
Function func = printHello;
//运行方法
func();//Hello
var list = [1,2,3,4];
list.forEach(print);
//1
//2
//3
//4
var list2 = ["h","e","l","l","o"];
//listTimes方法传入两个字段
print(listTimes(list2, times));
//[hhh, eee, lll, lll, ooo]
print(listTimes2(list2, times));
//[hhhhhhhhh, eeeeeeeee, lllllllll, lllllllll, ooooooooo]
}
void printHello(){
print("Hello");
}
//方法定义的参数可以随便定义,第二个参数只是字段的定义,不代表是下方的times方法,设置为f都行
List listTimes(List list ,String times(str)){
for(var index = 0;index < list.length;index++){
list[index] = times(list[index]);
}
return list;
}
List listTimes2(List list ,String f(str)){
for(var index = 0;index < list.length;index++){
list[index] = f(list[index]);
}
return list;
}
String times(str){
//字符串的话,会复制三遍
return str*3;
}
匿名方法
(参数1,参数2. ..){
方法体….
return返回值
}
- 可赋值给变量,通过变量进行调用
- 可在其它方法中直接调用或传递给其它方法
void main(){
//匿名方法在方法内定义
//定义并调用
var func = (str){
print("Hello---$str");
};
func(30);//Hello---30
//定义,并直接调用()--不建议
// ((){
// print("Test");
// })();
var list2 = ["h","e","l","l","o"];
var result = listTimes(list2, (str){ return str * 3;});
print(result);//[hhh, eee, lll, lll, ooo]
print(listTimes2(list2));//[hhhhhhhhh, eeeeeeeee, lllllllll, lllllllll, ooooooooo]
}
List listTimes(List list ,String times(str)){
for(var index = 0;index < list.length;index++){
list[index] = times(list[index]);
}
return list;
}
List listTimes2(List list ){
var func = (str){ return str * 3;};
for(var index = 0;index < list.length;index++){
list[index] = func(list[index]);
}
return list;
}
闭包
- 闭包是一个方法(对象)
- 闭包定义在其它方法内部
- 闭包能够访问外部方法内的局部变量,并持有其状态
1、全局变量特点: 全局变量常驻内存、全局变量污染全局
2、局部变量的特点: 不常驻内存会被垃圾机制回收、不会污染全局
闭包: 函数嵌套函数, 内部函数会调用外部函数的变量或参数, 变量或参数不会被系统回收(不会释放内存)
闭包的写法: 函数嵌套函数,并return 里面的函数,这样就形成了闭包。
void main(){
//相当于初始化了一个对象
var func = a();
//调用方法调用的是里面的return部分
func();//0
func();//1
func();//2
func();//3
//重新初始化一个方法/对象,新的对象与上一个数据不会共通
var func2 = a();
func2();//0
}
//定义方法
a(){
int count = 0;
// printCount(){
// print(count++);
// }
return (){
print(count++);
};
}
面向对象编程
类与对象
- 使用关键字class声明一个类
- 使用关键字new创建一个对象, new可省略
- 所有对象都继承于Object类
属性与方法
- 属性默认会生成getter和setter方法
- 使用final声明的属性只有getter方法
- 属性和方法通过.访问
- 方法不能被重载(同一个名字的只能有一个)
类及成员可见性
- Dart中的可见性以library(库)为单位
- 默认情况下,每一个Dart文件就是一个库
- 使用
_
表示库的私有性(只能在本文件中使用) - 使用
import
导入库
class Person {
String name;
int age;
final String address = "";
void work(){
print("Name is $name,Age is $age,He is working...");
}
}
import 'person.dart';//不在同一个文件中,需要引入
void main() {
var person = new Person();
person.name = "Tom";
person.age = 20;
print(person.name);//Tom
print(person.address);//
person.work();//Name is Tom,Age is 20,He is working...
}
计算属性
属性默认会生成getter和setter方法,这里自定义get/set方法
- 顾名思义,计算属性的值是通过计算而来,本身不存储值
- 计算属性赋值,其实是通过计算转换到其它实例变量
get/set 字段 =>定义
set(参数){}
void main() {
var rect = new Rectangle();
rect.height = 20;
rect.width = 10;
print(rect.area);//200
print(rect.aa);//60
rect.area = 200;
print(rect.width);//10.0
rect.aa = 40;
print(rect.width);//20.0
print(rect.height);//20.0
}
class Rectangle {
num width, height;
//计算属性 area
// get xx
num get area => width * height;
set area(value) {
width = value / 20;
}
num get aa => aaval();
set aa(value) {
width = value / 2;
height = value / 2;
}
num aaval() {
return (width + height) * 2;
}
}
构造方法
- 如果没有自定义构造方法,则会有个默认构造方法
名(){}
- 如果存在自定义构造方法,则默认构造方法无效(不能重载,所以同名方法只有一个)
- 构造方法不能重载
class Person{
String name;
int age;
final String gender;
// final字段没有set方法,构造方法不能使用普通方法,只能this.
// Person(String name,int age, this.gender){
// this.name=name;
// this.age=age;
//
// }
Person(this.name,this.age,this.gender);
}
命名构造方法
- 使用命名构造方法,可以实现多个构造方法
- 使用
类名.方法
的形式实现
命名构造方法必须设置上final字段的值
void main() {
new Person.withName("John","12").work();
new Person.withAge(20,"35").work();
}
class Person{
String name;
int age;
final String gender;
Person.withName(String name, this.gender){
this.name = name;
}
Person.withAge(this.age, this.gender);
void work(){
print("Work...");
}
}
//Work...
//Work...
常量构造方法
- 如果类是不可变状态,可以把对象定义为编译时常量
- 使用const声明构造方法,并且所有变量都为final
- 使用const声明对象,可以省略
void main() {
//常量对应的构造方法也是常量,定义的const可以省略
// const person = Person("Tom", 20, "Male");
const person = const Person("Tom", 20, "Male");
person.work();
}
class Person {
//所有字段必须是final
final String name;
final int age;
final String gender;
//构造必须是常量
const Person(this.name, this.age, this.gender);
void work() {
print("Work...");
}
}
工厂构造方法
- 工厂构造方法类似于设计模式中的工厂模式
- 在构造方法前添加关键字
factory
实现一个工厂构造方法 - 在工厂构造方法中可返回对象(之前的构造方法没有返回值,都是void的)
class Logger {
final String name;
//new 省略了,里面的值是final的
static final Map<String, Logger> _cache = <String, Logger>{};
//工厂构造方法,可以设置返回值
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);
void log(String msg) {
print(msg);
}
}
初始化列表:
final的赋值需要在构造方法之前执行,或者使用语法糖
- 初始化列表会在构造方法体执行之前执行
- 使用逗号分隔初始化表达式
- 初始化列表常用于设置final变量的值
void main() {
new Person.withMap({"name": "aaa", "age": 12, "gender": "123"}).work();
//aaa null
//newName null 123
//1.参数构造方法调用的时候,先给name和gender赋值
//2.运行构造方法的时候,先输出了 name 和age
//3.name赋值
//4.调用work方法
}
class Person {
String name;
int age;
final String gender;
Person(this.name, this.age, this.gender);
//final可以直接在参数后面 :之后初始化
//构造方法之后使用 :可以给字段初始化,在构造方法运行之前
Person.withMap(Map map) : name = map["name"], gender = map["gender"] {
print("$name $age");
name = "newName";
}
void work() {
print("$name $age $gender");
}
}
静态成员static
- 使用
static
关键字来实现类级别的变量和函数 - 静态成员不能访问非静态成员,非静态成员可以访问静态成员
- 类中的常量需要使用
static const
声明
void main() {
var page = new Page();
page.scrollUp();
Page.scrollDown();
}
class Page {
//静态常量
static const int maxPage = 10;
//静态变量
static int currentPage = 1;
//静态方法
//下滑
static void scrollDown() {
currentPage = 1;
print("ScrollDown...$currentPage");
}
//普通方法
//上滑
void scrollUp() {
currentPage++;
print("scrollUp...$currentPage");
}
}
对象操作符
- 条件成员访问(左侧为空就不执行了):
?.
- 类型转换:
as
- 是否指定类型:
is
,is!
- 级联(链式)操作:
..
class Person{
String name;
int age;
void work(){
print("Work...$name,$age");
}
}
条件成员访问?.
Person person1;
// person1.work();//person1没有实例化,会异常
person1?.work();//person1没有实例化,不会继续执行
person1?.name="123";//person1没有实例化,不会继续执行
person1?.work();//person1没有实例化,不会继续执行
person1=new Person();
person1?.work();//Work...null,null
类型转换as
var person2;
person2="";
person2=new Person();
person2.name="12";
person2.work();//Work...12,null 调用work()没有代码提示,但是运行不会异常
(person2 as Person).work();//Work...12,null 类型转换,有代码提示
//若是没有 person2=new Person(); 在调用方法的时候会异常
是否指定类型is
/is!
var person2;
person2="";
if(person2 is Person){
person2.work();
}else{
print(person2 is! Person);//true
}
级联(链式)操作..
var person=new Person();
//级联操作,链式操作
person..name="Tom"..age=12..work()
..age=20..work();
或者 new Person()..name="Tom"..age=12..work()..age=20..work()
Work...Tom,12
Work...Tom,20
对象call方法
- 如果类实现了call()方法,则该类的对象可以作为方法使用
void main() {
var person = new Person();
print(person("Test",30));
}
class Person{
String name;
int age;
String call(String name,int age){
return "Name is $name,Age is $age";
}
}
继承
- 使用关键字extends继承一个类
- 子类会继承父类可见的属性和方法,不会继承构造方法
- 子类能够复写父类的方法、getter和setter
- 单继承,多态性
父类
class Person {
String name;
int age;
String _birthday;
bool get isAdult => age > 18;
void run(){
print("Person run...");
}
@override
String toString() {
return "Name is $name,Age is $age";
}
}
测试
void main() {
var student = new Student();
student.study();//Student study...
student.name = "Tom";
student.age = 16;
print(student.isAdult);//true
//_birthday是父类隐藏的,不能调用
student.run();//Student run...
Person person = new Student();
person.name = "Tom";
person.age = 18;
if(person is Student){
person.study();//Student study... (实例化的是Student)
}
print(person);//Name is Tom,Age is 18
}
class Student extends Person{//继承
void study(){
print("Student study...");
}
//重写方法
@override
bool get isAdult => age > 15;
@override
void run() {
print("Student run...");
}
}
直接print对象的时候,对象实现了toString的话,调用toString方法,没有实现,输出默认的方法Instance of '类型'
print(对象)--toString()
print(对象(字段))--call(..字段)
继承中的构造方法
- 子类的构造方法默认会调用父类的无名无参构造方法
- 如果父类没有无名无参构造方法,则需要显示调用父类构造方法
- 在构造方法参数后使用:显示调用父类构造方法
若父类的构造方法是无名无参的,会直接调用这个构造
Person(){print(123);}
----------
new Student();
若父类的构造是有参的,需专门调用(在初始化列表中)
Person(this.name);
Person.withName(this.name);
------
//需要专门调用构造方法
class Student extends Person{
int age;
final String gender;
Student(String name,String g) : gender = g, super.withName(name);
//父类的构造方法是必须调用的,上面的哪个都行
}
构造方法执行顺序
- 父类的构造方法在子类构造方法体开始执行的位置调用
- 如果有初始化列表,初始化列表会在父类构造方法之前执行
子类初始化列表>父类初始化列表>父类构造方法>子类构造方法
void main() {
var student = new Student("Tom","Male");
print(student.name);
}
class Person{
String name;
Person(this.name);
Person.withName(this.name);
}
class Student extends Person{
int age;
final String gender;
Student(String name,String g) : gender = g, super.withName(name);//父类构造在子类的最后一个初始化列表中
}
抽象类abstract
- 抽象类使用
abstract
表示,不能直接被实例化 - 抽象方法不用
abstract
修饰,无实现 - 抽象类可以没有抽象方法
- 有抽象方法的类一定得声明为抽象类
void main() {
// var person = new Person();//抽象类不能实例化,会异常
var person = new Student();
person.run();
}
abstract class Person{
void run();//抽象方法不用abstract修饰,无实现
}
class Student extends Person{ //实现抽象类
@override
void run() {
print("run...");
}
}
//run...
接口
- 类和接口是统一的,类就是接口
- 每个类都隐式的定义了一个包含所有实例成员的接口
使用继承 :复用已有类的实现,使用继承(extends)
使用接口:使用已有类的外在行为,使用接口(implements)(一般最好使用抽象类)
普通类作为接口
void main() {
var student = new Student();
student.run();
}
class Person{
String name;
int get age => 18;
void run(){
print("Person run...");
}
}
class Student implements Person{
// 实现接口的时候,接口层的所有内容都是接口,在实现的时候都要实现一遍
@override
String name;
@override
int get age => 1;
@override
void run() {
print("Student run...");
}
}
抽象类作为接口(一样)
abstract class Person{
String name;
int get age => 18;
void run(){
print("Person run...");
}
}
class Student implements Person{
@override
String name;
@override
int get age => throw UnimplementedError();
@override
void run() {
}
// 实现接口的时候,接口层的所有内容都是接口,在实现的时候都要实现一遍
}
Mixins
- Mixins类似于多继承,是在多类继承中重用一个类代码的方式
- 作为Mixin的类不能有显示声明构造方法(父类只能使用默认构造)
- 作为Mixin的类只能继承自Object(父类不能定义上级父类)
- 使用关键字with连接一个或多个mixin
- 格式 :
class A with b,c,...{}
- 同名方法,使用最后的一个
若要继承
class A extend a with b,c,...{}
class A =a with b,c,...{}
A,a可以自定义构造
bc不可以自定义构造,不能定义继承;
void main() {
var d = new D();
d.a();
}
class A {
void a() {
print("A.a()...");
}
}
class B {
void a() {
print("B.a()...");
}
void b() {
print("B.b()...");
}
}
class C {
void a() {
print("C.a()...");
}
void b() {
print("C.b()...");
}
void c() {
print("C.c()...");
}
}
//D同时继承 ABC
class D extends A with C, B {}
测试
abstract class Engine{
void work();
}
class OilEngine implements Engine{//实现接口,不是继承,可以作为Mixin的类
@override
void work() {
print("Work with oil...");
}
}
class ElectricEngine implements Engine{
@override
void work() {
print("Work with Electric...");
}
}
class Tyre{
String name;
void run(){}
}
//class Car extends Tyre with ElectricEngine{};
//没有其他操作的话--简写
class Car = Tyre with ElectricEngine;
class Bus = Tyre with OilEngine;
操作符覆写(重载运算符)
- 覆写操作符需要在类中定义
- 如果覆写==,还需要覆写对象的hashCode getter方法
operator
返回类型 operator 操作符(参数1,参数2, ...) {
实现体...
return 返回值
}
可覆写的操作符
< |
* |
` | ` | [] |
---|---|---|---|---|
> |
/ |
^ |
[]= |
|
<= |
~/ |
& |
~ |
|
>= |
* |
<< |
== |
|
- |
% |
>> |
void main() {
var person1 = new Person(20);
var person2 = new Person(20);
print(person1 > person2);
person1.age;
print(person1['age']);//20
print(person1 == person2);//false
}
class Person{
int age;
Person(this.age);
//重定义 > 操作符,返回 bool格式
bool operator >(Person person){
return this.age > person.age;
}
int operator [](String str){
if("age" == str){//查的是age,返回age的值,否则返回0
return age;
}
return 0;
}
//==的还要复写 hashCode的getter方法
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Person &&
runtimeType == other.runtimeType &&
age == other.age;
@override
int get hashCode => age.hashCode;
}
枚举&类型
枚举
- 枚举是一种有穷序列集的数据类型
- 使用关键字
enum
定义一个枚举 - 常用于代替常量,控制语句等
特性:
- index从0开始,依次累加
void main() {
var currentSeason = Season.winter;
print(currentSeason.index);//3
switch(currentSeason){
case Season.spring:
print("1-3月");
break;
case Season.summer:
print("4-6月");
break;
case Season.autumn:
print("7-9月");
break;
case Season.winter:
print("10-12月");//10-12月
break;
}
}
enum Season{
spring,
summer,
autumn,
winter
}
泛型<>
- Dart中类型是可选的,可使用泛型限定类型
使用:
- 类的泛型
- 方法的泛型
void main() {
//限制类型
var list = new List<int>();
list.add(1);
var util = new Util<int>();
util.put(1);//只能传入数字
var util2 = new Util();
util2.put(1);//1
util2.put("aaa");//aaa
var utils = new Utils();
utils.put<int>(1);//1
utils.put("aaa");//aaa
}
//类级别
class Util<T> {
void put(T element) {
print(element);
}
}
class Utils {
//方法级别
void put<T>(T element) {
print(element);
}
}
异步
Dart类库有非常多的返回Future
或者Stream
对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。
Future
Future
与JavaScript中的Promise
非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。
由于本身功能较多,这里我们只介绍其常用的API及特性。还有,请记住,Future
的所有API的返回值仍然是一个Future
对象,所以可以很方便的进行链式调用。
.then/.catchError
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print("success");
}).catchError((e){
//执行失败会走到这里
print(e);
});
///////或者使用then的可选参数处理异常
Future.delayed(new Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
.whenComplete
有些时候,我们会遇到无论异步任务执行成功或失败都需要做一些事的场景,比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。这种场景,有两种方法,第一种是分别在then
或catch
中关闭一下对话框,第二种就是使用Future
的whenComplete
回调,我们将上面示例改一下:
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print(data);
}).catchError((e){
//执行失败会走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
});
.wait
有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作,比如我们有一个界面,需要先分别从两个网络接口获取数据,获取成功后,我们需要将两个接口数据进行特定的处理后再显示到UI界面上,应该怎么做?答案是Future.wait
,它接受一个Future
数组参数,只有数组中所有Future
都执行成功后,才会触发then
的成功回调,只要有一个Future
执行失败,就会触发错误回调。下面,我们通过模拟Future.delayed
来模拟两个数据获取的异步任务,等两个异步任务都执行成功时,将两个异步任务的结果拼接打印出来,代码如下:
Future.wait([
// 2秒后返回结果
Future.delayed(new Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回结果
Future.delayed(new Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
Async/await
Dart中的async/await
和JavaScript中的async/await
功能和用法是一模一样的,如果你已经了解JavaScript中的async/await
的用法,可以直接跳过本节。
async
用来表示函数是异步的,定义的函数会返回一个Future
对象,可以使用then方法添加回调函数。await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走;await
必须出现在async
函数内部。
回调地狱(Callback Hell)
如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then
回调中套回调情况。举个例子,比如现在有个需求场景是用户先登录,登录成功后会获得用户ID,然后通过用户ID,再去请求用户个人信息,获取到用户个人信息后,为了使用方便,我们需要将其缓存在本地文件系统,代码如下:
//先分别定义各个异步任务
Future<String> login(String userName, String pwd){
...
//用户登录
};
Future<String> getUserInfo(String id){
...
//获取用户信息
};
Future saveUserInfo(String userInfo){
...
// 保存用户信息
};
接下来,执行整个任务流:
login("alice","******").then((id){
//登录成功后通过,id获取用户信息
getUserInfo(id).then((userInfo){
//获取用户信息后保存
saveUserInfo(userInfo).then((){
//保存用户信息,接下来执行其它操作
...
});
});
})
可以感受一下,如果业务逻辑中有大量异步依赖的情况,将会出现上面这种在回调里面套回调的情况,过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,这个问题被形象的称为回调地狱(Callback Hell)。回调地狱问题在之前JavaScript中非常突出,也是JavaScript被吐槽最多的点,但随着ECMAScript6和ECMAScript7标准发布后,这个问题得到了非常好的解决,而解决回调地狱的两大神器正是ECMAScript6引入了Promise
,以及ECMAScript7中引入的async/await
。 而在Dart中几乎是完全平移了JavaScript中的这两者:Future
相当于Promise
,而async/await
连名字都没改。接下来我们看看通过Future
和async/await
如何消除上面示例中的嵌套问题。
使用Future消除Callback Hell
login("alice","******").then((id){
return getUserInfo(id);
}).then((userInfo){
return saveUserInfo(userInfo);
}).then((e){
//执行接下来的操作
}).catchError((e){
//错误处理
print(e);
});
正如上文所述, Future
的所有API的返回值仍然是一个Future
对象,所以可以很方便的进行链式调用” ,如果在then中返回的是一个Future
的话,该future
会执行,执行结束后会触发后面的then
回调,这样依次向下,就避免了层层嵌套。
使用async/await消除callback hell
通过Future
回调中再返回Future
的方式虽然能避免层层嵌套,但是还是有一层回调,有没有一种方式能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式?答案是肯定的,这就要使用async/await
了,下面我们先直接看代码,然后再解释,代码如下:
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//执行接下来的操作
} catch(e){
//错误处理
print(e);
}
}
其实,无论是在JavaScript还是Dart中,
async/await
都只是一个语法糖,编译器或解释器最终都会将其转化为一个Promise(Future)的调用链。
Stream
Stream
也是用于接收异步事件数据,和Future
不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream
常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。举个例子:
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(new Duration(seconds: 1), () {
return "hello 1";
}),
// 抛出一个异常
Future.delayed(new Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
上面的代码依次会输出:
I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3
Dart和Java及JavaScript对比
Dart vs Java
客观的来讲,Dart在语法层面确实比Java更有表现力;在VM层面,Dart VM在内存回收和吞吐量都进行了反复的优化,但具体的性能对比,笔者没有找到相关测试数据,但在笔者看来,只要Dart语言能流行,VM的性能就不用担心,毕竟Google在Go(没用VM但有GC)、JavaScript(v8)、Dalvik(Android上的Java VM)上已经有了很多技术积淀。值得注意的是Dart在Flutter中已经可以将GC做到10ms以内,所以Dart和Java相比,决胜因素并不会是在性能方面。而在语法层面,Dart要比Java更有表现力,最重要的是Dart对函数式编程支持要远强于Java(目前只停留在Lambda表达式),而Dart目前真正的不足是生态,但笔者相信,随着Flutter的逐渐火热,会回过头来反推Dart生态加速发展,对于Dart来说,现在需要的是时间。
Dart vs JavaScript
JavaScript的弱类型一直被抓短,所以TypeScript、CoffeeScript甚至是Facebook的flow(虽然并不能算JavaScript的一个超集,但也通过标注和打包工具提供了静态类型检查)才有市场。就笔者使用过的脚本语言中(笔者曾使用过Python、PHP),JavaScript无疑是动态化支持最好的脚本语言,比如在JavaScript中,可以给任何对象在任何时候动态扩展属性,对于精通JavaScript的高手来说,这无疑是一把利剑。但是,任何事物都有两面性,JavaScript的强大的动态化特性也是把双刃剑,你可经常听到另一个声音,认为JavaScript的这种动态性糟糕透了,太过灵活反而导致代码很难预期,无法限制不被期望的修改。毕竟有些人总是对自己或别人写的代码不放心,他们希望能够让代码变得可控,并期望有一套静态类型检查系统来帮助自己减少错误。正因如此,在Flutter中,Dart几乎放弃了脚本语言动态化的特性,如不支持反射、也不支持动态创建函数等。并且Dart在2.0强制开启了类型检查(Strong Mode),原先的检查模式(checked mode)和可选类型(optional type)将淡出,所以在类型安全这个层面来说,Dart和TypeScript、CoffeeScript是差不多的,所以单从这一点来看,Dart并不具备什么明显优势,但综合起来看,Dart既能进行服务端脚本、APP开发、web开发,这就有优势了!
综上所述,笔者还是很看好Dart语言的将来,之所以表这个态,是因为在新技术发展初期,很多人可能还有所摇摆,有所犹豫,所以有必要给大家打一剂强心针,当然,这是一个见仁见智的问题,大家可以各抒己见。