Dart 介绍
- Dart 是由谷歌开发的计算机语言,可以用于web、服务器、移动应用和物联网等领域的开发。它诞生于2011年,号称要取代JavaScript,但过去几年一直不温不火,直到flutter的出现才被人们重新重视。
- Dart 官网:https://dart.dev/
Dart 语言特性
借助于先进的工具链和编译器,Dart 是少数同时支持 JIT和 AOT的语言之一。
- JIT :(Just In Time,即时编译)在运行时即时编译,在开发周期中使用,可以动态下发和执行代码,开发测试效率高,但运行速度和执行性能则会因为运行时即时编译受到影响。在开发期使用 JIT 编译,可以缩短产品的开发周期。
- AOT:(Ahead of Time,运行前编译)即提前编译,可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低。在发布期使用 AOT,就不需要像 React Native 那样在跨平台 JavaScript 代码和原生 Android、iOS 代码之间建立低效的方法调用映射关系,所以说Dart具有运行速度快,执行性能好的特点。
内存分配与垃圾回收
- Dart VM 的内存分配策略比较简单,创建对象时只需要在堆上移动指针,内存增长始终是线性的,省去了查找可用内存的过程。
- 在 Dart 中,并发是通过 Isolate 实现的。Isolate 是类似于线程但不共享内存,独立运行的 worker。这样的机制,就可以让 Dart 实现无锁的快速分配。
- Dart 的垃圾回收,则是采用了多生代算法。新生代在回收内存时采用“半空间”机制,触发垃圾回收时,Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存。回收过程中,Dart 只需要操作少量的“活跃”对象,没有引用的大量“死亡”对象则被忽略,这样的回收机制很适合 Flutter 框架中大量 Widget 销毁重建的场景。
单线程模型
- 支持并发执行线程的高级语言(比如,C++、Java、Objective-C),大都以抢占式的方式切换线程,即:每个线程都会被分配一个固定的时间片来执行,超过了时间片后线程上下文将被抢占后切换。如果这时正在更新线程间的共享资源,抢占后就可能导致数据不同步的问题。
- 解决这一问题的典型方法是,使用锁来保护共享资源,但锁本身又可能会带来性能损耗,甚至出现死锁等更严重的问题。
- 这时,Dart 是单线程模型的优势就体现出来了,因为它天然不存在资源竞争和状态同步的问题。这就意味着,一旦某个函数开始执行,就将执行到这个函数结束,而不会被其他 Dart 代码打断。
- 所以,Dart 中并没有线程,只有 Isolate(隔离区)。Isolates 之间不会共享内存,就像几个运行在不同进程中的 worker,通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信。
环境搭建
- 本地开发Dart程序的话需要先安装Dart sdk。MAC 下执行下面两条指令,
brew tap dart-lang/dart
成功后执行brew install dart
。执行命令中如有报错,根据报错内容自行解决。 - 这里我们选用VSCode作为开发工具。需要给VSCode安装两个插件,首先在VC中的Extensions中搜索dart插件安装,然后搜索code runner 安装,Code Runner 可以运行我们的文件。
注释
- Dart 支持行注释和块注释
// var str='this is var'; 双斜杠注释
/// String str='this is var'; 三斜杠注释
//块注释
/*
var str='this is var';
String str='this is var';
*/
入口函数
- dart 使用main函数作为入口函数
main() {
}
// 或者
void main() {
}
变量和常量
变量
- dart是一门强大的脚本类语言,可以不预先定义变量类型,会根据赋值的具体内容推导出变量的类型。
- dart 使用var 和 具体类型修饰变量。例如:
var str = ' this is var; '
等同于String str = ' this is var; '
。这里需要注意用var了就不要再指定类型,写了类型就不要再写var,如果两个同时写的话会报错。例如:var int a = 0;
会报错Error: Variables can't be declared using both 'var' and a type name.
常量
- dart 使用final和const 修饰常量,并且都只能赋值一次。
- const 修饰的常量一开始就得赋值。
const age = 20;
- final 修饰的常量可以开始不赋值,它不仅有const的编译时常量的特性,最重要的是它是运行时常量,并且final是惰性初始化,即在运行时第一次使用前才初始化。比如我们程序中需要获取当前时间
final a = new DateTime.now();
,这个肯定是运行时才能确定的量,而不能在编译时赋值,这个时候就需要用final修饰。
命名规则
- 变量名必须由数字、字母、下划线、美元符($)组成。
- 标识符开头不能是数字
- 标识符不能是保留字和关键字
- 变量的名字是区分大小写的如: age和Age是不同的变量。在实际的运用中,也建议,不要用一个单词大小写区分两个变量
- 标识符(变量名称)一定要见名思意 :变量名称建议用名词,方法名称建议用动词
数据类型
字符串
- 定义字符串可以用双引号也可以用单引号,也可以用三个引号(单引号 双引号都可以)定义多行字符串
String str1 = "string1";
String str2 = 'string2';
String str3 = '''
duo
hang
string
''';
- 字符串拼接: 多个字符串可以直接使用 + 拼接
- 判断字符串是否为空:
var str = ''; str.isEmpty
数值类型
- 整型(int): 必须赋值整型
- 浮点型(double):既可以赋值浮点型也可以赋值整型
bool类型
- 只能是false 或者 true
List(数组)
var list = ['0','11','222'];
var list = new List();
var list = new List<String>();
- 常用属性:
length
长度
reversed
翻转,但是反转后的结果是一个集合而不是列表,如果要转换成列表,还需要调用toList()方法
isEmpty
是否为空
isNotEmpty
是否不为空 - 常用方法:
add
增加
addAll
拼接数组
indexOf
查找 传入具体值
remove
删除 传入具体值
removeAt
删除 传入索引值
fillRange
修改,例如myList.fillRange(1, 2,'aaa');
insert(index,value);
指定位置插入
insertAll(index,list)
指定位置插入List
toList()
其他类型转换成List
join()
List转换成字符串
split()
字符串转化成List,以某个字符切割字符串
forEach
便利数组myList.forEach((value){ print("$value"); });
map
主要用于修改集合数据var newList=myList.map((value){ return value*2; });
where
过滤数据var newList=myList.where((value){return value>5;});
any
数组里面只要有一个满足条件就返回truevar f=myList.any((value){ return value>5;});
every
数组里面每一个都满足条件才返回truevar f=myList.every((value){ return value>5;});
Maps(字典)
var person={"name":"zhangsan","age":20};
- 常用属性:
keys
获取所有的key值
values
获取所有的value值
isEmpty
是否为空
isNotEmpty
是否不为空 - 常用方法:
remove(key)
删除指定key的数据
addAll({...})
合并映射 给映射内增加属性
containsValue
查看映射内的值 返回true/false
forEach
map
where
any
every
Set
- Set是没有顺序且不能重复的集合,所以不能通过索引去获取值
var s=new Set();
s.add('香蕉');
s.add('苹果');
s.add('苹果');
print(s); //{香蕉, 苹果}
判断数据类型
- Dart中使用is关键字来判断数据类型
var list = ['0','11','222'];
if (list is String) {
print("list is string");
} else if (list is int) {
print("list is int");
} else if (list is List) {
print("list is list");
}
运算符
- 算术运算符: 加(+)、减(-)、乘(*)、除(/)、取整(~/)、取余(%)
- 关系运算符:恒等(==)、不等(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)
- 逻辑运算符: 非(!)、且(&&)、或(||)
赋值运算符
- 基础赋值运算符:=、??=
int b = 10; b ??= 20; //该语句表示如果b的值为空则把20赋值给b,如果b的值不为空,则不作处理。如果是 int b; b??= 20; // 此时b的值为20
- 复合赋值运算符:+=、-=、*=、/=、%=、~/=、++、--
- 三目运算: 与C语言三目运算一样
数值类型与String之间的转换
- 数值类型转换成String:
var num = 12; var string = num.toString();
var num = 12.0; var string = num.toString();
- String 转化成数值类型:
var string = '12'; var num = int.parse(string);
var string = '12.0'; var num = double.parse(string);
注意1:
如果字符串内容是浮点型,则只能用double.parse()转换,如果使用int.parse()转换则会报错。如果字符串内容是整型,既可以用double.parse() 又可以用int.parse()转换
注意2:
如果字符串是空或者字符串内容不是数值,转换成数值类型的时候会报错,可以用try ... catch 处理
String price='';
try{
var myNum=double.parse(price);
print(myNum);
}catch(err){
print(0);
}
函数
- 函数格式
返回类型 方法名称(参数1,参数2,...){
方法体
return 返回值;
}
- 嵌套函数
void xxx(){
aaa(){
print(getList());
print('aaa');
}
aaa();
}
- 可选参数: 用[]括起来的参数都是可选参数,可选参数也可以设置默认参数
String printUserInfo(String username,[String home = "中国",int age]){ //行参
if(age!=null){
return "姓名:$username---家乡:$home---年龄:$age";
}
return "姓名:$username---家乡:$home---年龄保密";
}
print(printUserInfo('张三',21,'上海'));
print(printUserInfo('张三'));
- 命名参数:命名参数用{}括起来,调用命名参数的函数时,需要写明命名参数的参数名
String printUserInfo(String username,{int age,String sex='男'}){ //行参
if(age!=null){
return "姓名:$username---性别:$sex--年龄:$age";
}
return "姓名:$username---性别:$sex--年龄保密";
}
print(printUserInfo('张三',age:20,sex:'未知'));
- 匿名函数
var fn=(){
print('我是一个匿名方法');
};
fn();
- 函数也可以作为另一个函数的参数
//方法
fn1(){
print('fn1');
}
//方法
fn2(fn){
fn();
}
//调用fn2这个方法 把fn1这个方法当做参数传入
fn2(fn1);
- 箭头函数:箭头函数的函数体只能有一句代码
var newList=list.map((value)=>value>2?value*2:value); // 将list中大于2的元素乘以2返回
- 自执行函数:程序运行起来就会执行的函数
((int n){
print(n);
print('我是自执行方法');
})(12);
- 闭包:函数嵌套函数, 内部函数会调用外部函数的变量或参数, 变量或参数不会被系统回收(不会释放内存)。可以实现常驻内存,不污染全局。 闭包的写法: 函数嵌套函数,并return 里面的函数,这样就形成了闭包。
fn(){
var a=123; /*不会污染全局 常驻内存*/
return(){
a++;
print(a);
};
}
var b=fn();
b(); //124
b(); //125
类
- Dart所有的东西都是对象,所有的对象都继承自Object类。
- Dart是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是Object的子类。
构造函数
默认构造函数
- dart的默认构造函数只能有一个,由类名+(参数s){}构成
Person(String name,int age){this.name = name; this.age = age;}
- 默认构造函数简写格式为类名+(接收参数的属性s)
Person(this.name, this.age);// 将接收到的第一个参数赋值给this.name,接收到的第二个参数,赋值给this.age,等同于上面的Person(String name,int age){this.name = name; this.age = age;}
命名构造函数
- dart 中命名构造函数可以有多个
- 命名构造函数格式为类名.函数名(参数s) { }
Person.student(){函数体}
。使用命名构造函数生成一个实例:Person p = new Person.student();
Dart中的私有属性和方法
- dart中没有public、private、protected访问修饰符
- dart中使用下划线(_)将一个属性或者方法变为私有。需要注意的是加了下划线的私有属性或者方法依然可以被当前文件中的其他类访问,只是不能被别的文件中的模块访问。如果需要只能当前类访问,需要将该类放到一个单独的文件中
。
getter 和 setter
class Rect{
num height;
num width;
//Rect(this.height,this.width);
Rect():height=2,width=10{//在构造函数体运行之前初始化实例变量
}
get area{
return this.height*this.width;
}
set areaHeight(value){
this.height=value;
}
}
静态成员和静态方法
- dart中使用static关键字来实现类级别的变量和函数
- 静态方法不能访问非静态成员,非静态成员方法可以访问静态成员
class Person {
static String name = '张三';
int age=20;
static void show() {
print(name);
}
void printInfo(){ /*非静态方法可以访问静态成员以及非静态成员*/
print(name); //访问静态属性
print(this.age); //访问非静态属性
show(); //调用静态方法
}
static void printUserInfo(){//静态方法
print(name); //静态属性
show(); //静态方法
// print(this.age); //静态方法没法访问非静态的属性 报错
// this.printInfo(); //静态方法没法访问非静态的方法 报错
// printInfo(); //静态方法没发访问非静态的方法 报错
Person p = Person();
p.printInfo();
}
}
Dart 中的对象操作符
- ? 条件运算符
Perosn p; p?.getInfo();// 如果p有值调用getInfo方法,如果没值则什么也不做
- as 类型转换
var p;
p='';
p=new Person();
(p as Person).printInfo(); //如果p不是Person类型则报错
- is 类型判断
Person p=new Person('张三', 20);
if(p is Person){
p.name="李四";
}
- .. 级联操作(连缀)
Person p1=new Person('张三1', 20);
p1.printInfo();
p1..name="李四"
..age=30
..printInfo(); //等同于下面三句
// p1.name='张三222';
// p1.age=40;
// p1.printInfo();
Dart 中的继承
- 子类使用extends关键词来继承父类
- 子类会继承父类里面可见的属性和方法 但是不会继承构造函数(默认构造函数和命名构造函数)
- 子类能复写父类的方法 getter和setter
- 子类重写父类方法时用@override关键字,默认不用也可以,一般建议使用
- 子类中使用super关键字调用父类方法
class Person {
String name;
num age;
Person(this.name,this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
}
class Web extends Person{
Web(String name, num age) : super(name, age){ //子类实现自己的构造方法,同时调用父类的构造方法
}
}
Dart中的抽象类
- Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
- 抽象类通过abstract 关键字来定义
- Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。
- 如果子类继承抽象类必须得实现里面的抽象方法
- 如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。
- 抽象类不能被实例化,只有继承它的子类可以
extends抽象类 和 implements的区别:
- 如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用extends继承抽象类
- 如果只是把抽象类当做标准的话我们就用implements实现抽象类
abstract class Animal{
eat(); //抽象方法
run(); //抽象方法
printInfo(){
print('我是一个抽象类里面的普通方法');
}
}
class Dog extends Animal{
@override
eat() {
print('小狗在吃骨头');
}
@override
run() {
print('小狗在跑');
}
}
class Cat extends Animal{
@override
eat() {
print('小猫在吃老鼠');
}
@override
run() {
print('小猫在跑');
}
}
Dart 中的多态
- 允许将子类类型的指针赋值给父类类型的指针, 同一个函数调用会有不同的执行效果 。
- 子类的实例赋值给父类的引用。
- 多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类有不同的表现。
Dart中的接口
- dart的接口没有interface关键字定义接口,而是普通类或抽象类都可以作为接口被实现。
- 使用implements关键字进行实现
- 如果实现的类是普通类,会将普通类和抽象中的属性的方法全部需要覆写一遍。因为抽象类可以定义抽象方法,普通类不可以.
Dart 中的接口
- 一个类实现多个接口
abstract class A{
String name;
printA();
}
abstract class B{
printB();
}
class C implements A,B{
@override
String name;
@override
printA() {
print('printA');
}
@override
printB() {
return null;
}
}
Dart 中的混入
- mixins意思是混入,就是在类中混入其他功能。
- 在Dart中可以使用mixins实现类似多继承的功能
- mixins使用的条件,随着Dart版本一直在变,这里说的是Dart2.x中使用mixins的条件:
1、作为mixins的类只能继承自Object,不能继承其他类
2、作为mixins的类不能有构造函数
3、一个类可以mixins多个mixins类
4、mixins绝不是继承,也不是接口,而是一种全新的特性
class Person{
String name;
num age;
Person(this.name,this.age);
printInfo(){print('${this.name}----${this.age}');}
void run(){print("Person Run");}
}
class A {
String info="this is A";
void printA(){ print("A");}
void run(){print("run")}
}
class B {
void printB(){print("B");}
void run(){print("B Run");}
}
class C extends Person with B,A{
C(String name, num age) : super(name, age);
}
- mixins的类型就是其超类的子类型。
class A {
String info="this is A";
void printA(){
print("A");
}
}
class B {
void printB(){
print("B");
}
}
class C with A,B{
}
void main(){
var c=new C();
print(c is C); //true
print(c is A); //true
print(c is B); //true
}
泛型
- 泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验)
T getData<T>(T value){
return value;
}
库
- Dart中使用import关键字引入
- Dart中的库主要有三种:
自定义库
- 通过import "库路径"导入
import 'lib/xxx.dart';
系统内置库
import 'dart: math';
三方库
1、从下面网址找到要用的库
https://pub.dev/packages
https://pub.flutter-io.cn/packages
https://pub.dartlang.org/flutter/
2、需要在自己想项目根目录新建一个pubspec.yaml
3、在pubspec.yaml文件 然后配置名称 、描述、依赖等信息,内容如下
name: xxx
description: A new flutter module project.
dependencies:
http: ^0.12.0+2
date_format: ^1.0.6
4、然后运行 pub get 获取包下载到本地
5、看文档引入库使用
async和await
- 使用async和await 这两个关键字的使用只需要记住两点:
1、只有async方法才能使用await关键字调用方法
2、如果调用别的async方法必须使用await关键字 - async是让方法变成异步。
- await是等待异步方法执行完成。
void main() async{
var result = await testAsync();
print(result);
}
//异步方法
testAsync() async{
return 'Hello async';
}
Dart库中的冲突解决
- 当引入两个库中有相同名称的标识符的时候就会出现冲突,出现冲突的时候可以使用as关键字来指定库的前缀
import 'lib/Person1.dart';
import 'lib/Person2.dart' as lib; // 这里使用as关键字指定Person2.dart库的前缀为lib
main(List<String> args) {
Person p1=new Person('张三', 20); // 这里调用的是Person1.dart库中的Person
p1.printInfo();
lib.Person p2=new lib.Person('李四', 20);// 这里通过lib前缀调用的是Person2.dart中的Person
p2.printInfo();
}
部分导入
- 导入库的一部分有两种模式
只导入需要的部分
- 使用show关键字导入需要的部分,其他部分不导入
import 'package:lib1/lib1.dart' show foo;// 这里只导入lib1.dart库中的foo函数,其他不导入
隐藏不需要导入的部分
- 使用hide关键字隐藏不需要导入的部分,其他部分导入
import 'package:lib2/lib2.dart' hide foo; // 导入/lib2.dart库中除foo函数外的其他功能
延迟加载
- 也称为懒加载,可以在需要的时候再进行加载。懒加载的最大好处是可以减少APP的启动时间。
- 懒加载使用deferred as关键字来指定
import 'package:deferred/hello.dart' deferred as hello;
- 当需要使用的时候,需要使用loadLibrary()方法来加载:
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
Flutter 包管理
1、pub仓库下载的包
dependencies:
english_words: ^3.1.3
2、 本地包
dependencies:
pkg1:
path: ../../code/pkg1 // 本地包路径
3、使用git 方式引入第三方库
dependencies:
carousel_pro:
git:
url: git://github.com/jlouage/flutter-carousel-pro.git // url 提供仓库的地址
ref: master // ref参数将依赖关系固定到特定git commit,branch或tag
path: path/subPath // Pub假定包位于Git存储库的根目录中,如果不是这种情况,您可以使用path参数指定位置