跳至主要內容

Dart基础

xiaoye大约 16 分钟FlutterFlutterDart

官网地址:选择你的开发平台,开始使用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutteropen in new window

文档地址:第二版序 | 《Flutter实战·第二版》 (flutterchina.club)open in new window

Dart

1、第一个Dart

// 入口
main(){
  print('你好 dart');//print来打印
}

2、变量声明

2.1、var 关键字

注意区分与js的区别,赋值后不能再改变其类型

var t = "hi world";
// 下面代码在dart中会报错,因为变量t的类型已经确定为String,
// 类型一旦确定后则不能再更改其类型。
t = 1000;

2.2、dynamicObject

Object 是 Dart 所有对象的根基类,也就是说在 Dart 中所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象。 dynamicObject声明的变量都可以赋值任意对象,且后期可以改变赋值的类型

dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代码没有问题
t = 1000;
x = 1000;

dynamicObject不同的是dynamic声明的对象编译器会提供所有可能的组合,而Object声明的对象只能使用 Object 的属性与方法, 否则编译器会报错

 dynamic a;
 Object b = "";
 main() {
   a = "";
   printLengths();
 }   

 printLengths() {
   // 正常
   print(a.length);
   // 报错 The getter 'length' is not defined for the class 'Object'
   print(b.length);
 }

dynamic 的这个特点使得我们在使用它时需要格外注意,这很容易引入一个运行时错误,比如下面代码在编译时不会报错,而在运行时会报错:

print(a.xx); // a是字符串,没有"xx"属性,编译时不会报错,运行时会报错

2.3、finalconst

如果您从未打算更改一个变量,那么使用 finalconst,不是var,也不是一个类型。 一个 final 变量只能被设置一次,两者区别在于:const 变量是一个编译时常量(编译时直接替换为常量值),final变量在第一次使用时被初始化。被final或者const修饰的变量,变量类型可以省略,如:

//可以省略String这个类型声明
final str = "hi world";
//final String str = "hi world"; 
const str1 = "hi world";
//const String str1 = "hi world";

2.4、List

2.4.1、List的常用属性

属性说明
length长度
reversed翻转 翻转后再调用toList()转List
isEmpty是否为空
isNotEmpty是否不为空

2.4.2、List的常用方法

方法说明
add(value)增加
addAll(List)拼接数组
indexOf(value)查找 传入具体值 找不到返回-1 找到返回索引
remove(value)删除 传入值
removeAt(index)删除 传入索引值
fillRange(start,end,value)修改 开始位置,结束位置,修改的内容
insert(index,value)指定index的前面插入数据
insert(index,List)指定index的前面插入List
toList()其它类型转List
join()List转字符串
split()字符串转List
forEach()
map()
where
any
every

2.4.3、案例

void main() {
  // 创建可扩容的list

  // 不指定类型
  var ll = ['a', 1, true];

  // 指定类型
  var l1 = <String>['1', '2'];
  print(l1);
  List l2 = <String>['1', '2'];
  l2.add('3'); //追加数据
  print(l2);

  //创建不可扩容的list,但是可以修改内容
  List L4 = List.filled(2, '');
  // L4.add('5');//要报错
  L4[0] = '你好';
  print(L4);
}

2.5、Set

集合去重

void main() {
  var s = new Set();
  s.add(1);
  s.add(2);
  s.add(2); //Set中不允许重复的元素
  print(s); //{1, 2}
  print(s.toList()); //[1, 2]
}

2.6、Map

2.6.1、Map的常用属性

属性说明
keys获取所有的key值
values获取所有的value值
isEmpty是否为空
isNotEmpty是否不为空

2.6.2、Map的常用方法

方法说明
remove(key)删除指定的key
addAll({...})合并map,给map增加属性
constainsValue(value)查看map内的值 返回true/false
forEach()
map()
where
any
every

2.6.3、案例

类似JSON对象 取值要用person[key]

void main() {
  //定义方法1
  Map person = {
    "name": "zhnagsan",
    "age": 20,
  };
  print(person);

  //定义方法2
  var person2 = new Map();
  person2["name"] = "zhnagsan";
  print(person2);
}

2.7、List和Set和Map的通用循环方法

方法说明
forEach((value)=>{})遍历
map()遍历返回新目标
where返回满足条件的新目标
any只要一个满足就返回true否则false
every每一个都要满足条件才返回true否则false

3、函数

3.1、函数声明

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断

typedef bool CALLBACK();

//不指定返回类型,此时默认为dynamic,不是bool
isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void test(CALLBACK cb){
   print(cb()); 
}
//报错,isNoble不是bool类型
test(isNoble);

对于只包含一个表达式的函数,可以使用简写语法:

bool isNoble (int atomicNumber)=> true ;

3.2、函数作为变量

var say = (str){
  print(str);
};
say("hi world");

3.3、函数作为参数传递

//定义函数execute,它的参数类型为函数
void execute(var callback) {
    callback(); //执行传入的函数
}
//调用execute,将箭头函数作为参数传递
execute(() => print("xxx"))
  • 可选的位置参数

    包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:

    String say(String from, String msg, [String? device]) {
      var result = '$from says $msg';
      if (device != null) {
        result = '$result with a $device';
      }
      return result;
    }
    

    下面是一个不带可选参数调用这个函数的例子:

    say('Bob', 'Howdy'); //结果是: Bob says Howdy
    

    下面是用第三个参数调用这个函数的例子:

    say('Bob', 'Howdy', 'smoke signal'); //结果是:Bob says Howdy with a smoke signal
    
  • 可选的命名参数

    定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。例如:

    //设置[bold]和[hidden]标志
    void enableFlags({bool bold, bool hidden}) {
        // ... 
    }
    

    调用函数时,可以使用指定命名参数。例如:paramName: value

    enableFlags(bold: true, hidden: false);
    

    *可选命名参数在Flutter中使用非常多。注意,不能同时使用可选的位置参数和可选的命名参数

  • 默认参数加上=号给默认值

4、mixin

Dart 是不支持多继承的,但是它支持 mixin,简单来讲 mixin 可以 “组合” 多个类,我们通过一个例子来理解。

定义一个 Person 类,实现吃饭、说话、走路和写代码功能,同时定义一个 Dog 类,实现吃饭、和走路功能:

class Person {
  say() {
    print('say');
  }
}

mixin Eat {
  eat() {
    print('eat');
  }
}

mixin Walk {
  walk() {
    print('walk');
  }
}

mixin Code {
  code() {
    print('key');
  }
}

class Dog with Eat, Walk{}
class Man extends Person with Eat, Walk, Code{}

*我们定义了几个 mixin,然后通过 with 关键字将它们组合成不同的类。有一点需要注意:如果多个mixin 中有同名方法,with 时,会默认使用最后面的 mixin 的,mixin 方法中可以通过 super 关键字调用之前 mixin 或类中的方法。我们这里只介绍 mixin 最基本的特性,关于 mixin 更详细的内容读者可以自行百度。

5、异步支持

Dart类库有非常多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。

asyncawait关键词支持了异步编程,允许您写出和同步代码很像的异步代码。

5.1、Future

Future与JavaScript中的Promise非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。

由于本身功能较多,这里我们只介绍其常用的API及特性。还有,请记住,Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。

5.1.1、Future.then

为了方便示例,在本例中我们使用Future.delayed 创建了一个延时任务(实际场景会是一个真正的耗时任务,比如一次网络请求),即2秒后返回结果字符串"hi world!",然后我们在then中接收异步结果并打印结果,代码如下:

Future.delayed(Duration(seconds: 2),(){
   return "hi world!";
}).then((data){
   print(data);
});

5.1.2Future.catchError

如果异步任务发生错误,我们可以在catchError中捕获错误,我们将上面示例改为

Future.delayed(Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");  
}).then((data){
   //执行成功会走到这里  
   print("success");
}).catchError((e){
   //执行失败会走到这里  
   print(e);
});

在本示例中,我们在异步任务中抛出了一个异常,then的回调函数将不会被执行,取而代之的是 catchError回调函数将被调用;但是,并不是只有 catchError回调才能捕获错误,then方法还有一个可选参数onError,我们也可以用它来捕获异常:

Future.delayed(Duration(seconds: 2), () {
	//return "hi world!";
	throw AssertionError("Error");
}).then((data) {
	print("success");
}, onError: (e) {
	print(e);
});

5.1.3Future.whenComplete

有些时候,我们会遇到无论异步任务执行成功或失败都需要做一些事的场景,比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。这种场景,有两种方法,第一种是分别在thencatch中关闭一下对话框,第二种就是使用FuturewhenComplete回调,我们将上面示例改一下:

Future.delayed(Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");
}).then((data){
   //执行成功会走到这里 
   print(data);
}).catchError((e){
   //执行失败会走到这里   
   print(e);
}).whenComplete((){
   //无论成功或失败都会走到这里
});

5.1.4Future.wait

有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作,比如我们有一个界面,需要先分别从两个网络接口获取数据,获取成功后,我们需要将两个接口数据进行特定的处理后再显示到UI界面上,应该怎么做?答案是Future.wait,它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。下面,我们通过模拟Future.delayed 来模拟两个数据获取的异步任务,等两个异步任务都执行成功时,将两个异步任务的结果拼接打印出来,代码如下:

Future.wait([
  // 2秒后返回结果  
  Future.delayed(Duration(seconds: 2), () {
    return "hello";
  }),
  // 4秒后返回结果  
  Future.delayed(Duration(seconds: 4), () {
    return " world";
  })
]).then((results){
  print(results[0]+results[1]);
}).catchError((e){
  print(e);
});

*执行上面代码,4秒后你会在控制台中看到“hello world”

5.2async/await

Dart中的async/await 和JavaScript中的async/await功能是一样的:异步任务串行化。如果你已经了解JavaScript中的async/await的用法,可以直接跳过本节。

6、Stream

Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。举个例子:

Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(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

代码很简单,就不赘述了。

既然 Stream 可以接收多次事件,那能不能用 Stream 来实现一个订阅者模式的事件总线?

7、dart的类(class)

//定义类
class Person{
  String name = "张三";
  int age = 23; 
  //构造函数   语法名称就是Person
  /*Person(String name,int age){//实例化必须传入参数
      this.name=name;
      this.age=age;
    print('构造函数在实例化触发');
  }*/
    //也可以简写构造函数
  Person(this.name,this.age);
  void getInfo() {
   	print("姓名${age}--年龄${age}");
  }
   
}

// 使用类
void mian(){
    Person p1=new Person('李四',24);//实例化类
    p1.getInfo();//调用方法
}

8、文件单独抽离,自定义、系统、第三方库

8.1、自定义库 (文件抽离)

  1. 创建文件

    class Animal {
      String _name; //单独抽离的文件  加上_表示私有
      int age;
      Animal(String this._name, int this.age);
      void printInfo() {
        print("姓名:${this._name}---年龄:${this.age}");
      }
    
      String getName() {
        this._run();
        //通过当前类访问返回私有属性
        return this._name;
      }
    
      _run() {
        //私有方法
        print('我是私有方法');
      }
    }
    
  2. 引入后直接使用

    import 'lib/xxx.dart';
    
    void main(){
        Animal an=new Animal('张三',20);
    }
    

8.2、系统库

使用io库发起http请求

import 'dart:io';
import 'dart:convert';

void main() async {//异步处理
  var res = await _getData();
  print(res);
}

// http请求  
_getData() async {
  //1.创建HttpClient对象
  var httpClient = new HttpClient();
  // 2.创建uri对象
  var uri = new Uri.https('services.laimihp.com', '/api/banner/index');
  // 3.发起请求,等待请求
  var request = await httpClient.getUrl(uri);
  // 4.关闭请求,等待响应
  var response = await request.close();
  // 5.解码响应内容  需要转换成utf8编码  需要引入 dart:convert
  return await response.transform(utf8.decoder).join();
}

8.3、第三方库

pub包管理系统

  1. 从下面网址找到要用的库

  2. 下载 http

    dart pub add http
    

    你也可以直接跳过下载 在第三步配置后执行:flutter pub get来下载依赖

  3. 在pubspec.yaml中的dependencies:下添加

    name: xxx
    description: "A new Flutter project."
    dependencies:
      http: ^1.2.0
    

    安装依赖时可能会提示你版本过高 那么你需要降低flutterSDK的版本 =>在系统变量中更换路径

  4. 导入它 按照文档来

    import 'package:http/http.dart';
    

8.4、部分导入(按需引入)

只需要导入库中的一部分 使用hide 你要导入的东西

show:只引入

hide:隐藏

myMath.datr

void getName(){
    print('姓名');
}
void getAge(){
    print(20);
}

index.dart

import 'myMath.datr';//全部引入
import 'myMath.datr' show getName;//只引入getName
import 'myMath.datr' hide getAge;//除了getAge外其他全部引入
void main(){
    getName();
    //getName();//保错  getAge被隐藏了
}

8.5、库的冲突解决

Persoin1与Persoin1中都有叫Persoin的class 使用as 自定义库名

import 'lib/Persoin1.dart';
//import 'lib/Persoin2.dart';//这样就会报冲突错
import 'lib/Persoin2.dart' as libp;//重命名为p2

void main(){
    //实例化重命名的p2
    libp.Person p2=libp.Person('xxx',520);
}

9、Dart2.13之后的新特性

9.1、Null safety(空安全)

9.1.1、可空类型(?)

使用?来表示可为空

void main() {
  int a = 123; //表示非空的int类型
  // a = null;//不能直接复制null给int
  String? userName = '名字';//加上?表示可空类型
  userName = null;
  List<String>? l1 = ['zhan', 'wang'];
  l1 = null;
}

在方法中返回可空类型同样在声明后面加上?

String? getData(apiUrl) {
    if (apiUrl == null) {
      return null;//返回空
    }
    return apiUrl;
  }

9.1.2、类型断言(!)

使用!表示断言:如果不为空则执行

 String? str = 'this is srt';
 str = null;
 print(str!.length);//str不为打印长度,否则抛出异常   
//类型断言一般配合try  catch使用
try {
    print(str!.length);
} catch (err) {
    print('str is null');
}

9.2、late关键字

没有构造函数的类中使用late 延迟初始化

class Person {
  late String name;
  late int age;
  // 没有构造函数使用late声明变量 否则报错
  void setInfo(String name, int age) {
    this.name = name;
    this.age = age;
  }

  String getName() {
    return "${this.name}---${this.age}";
  }
}

void main() {
  Person p = new Person();
  p.setInfo('zhans', 20);
}

9.3、required关键字

加上required表示必须传入 通常在class和函数中加上required必须传入

void main() {
  String printUsetrInfo(String name, {required int age, String gender = "男"}) {
    return "姓名:$name,年龄:$age,性别:$gender";
  }
}

10、性能优化常量、常量构造函数

10.1、回顾常量 dart通过 final和const修饰符

void main() {
  //const
  const PI = 3.14; //const定义在编译时无法改变  声明时必须赋值

  // final
  final a; //声明时必可以不赋值
  a = 3.0; //但是只能赋值一次
  print(a);
}

10.2、判断是否指向同一个存储空间

使用identical(o1, 02);来判断是否使用同一个存储空间

var o1 = Object();
var o2 = Object();
print(identical(o1, o2));//false
print(identical(o1, o1));//true

//如果使用const定义
var o1 = const Object();
var o2 = const Object();
print(identical(o1, o2));//true
print(identical(o1, o1));//true

const声明的常量如果创建相同对象 内存中只保留一个对象 共用同一个地址

10.3、常量构造函数不共享存储空间

// 使用final来定义常量构造函数
class Container {
  final int width;
  final int height;
  Container({required this.width, required this.height});
}

void main() {
  var c1 = Container(width: 10, height: 20);
  var c2 = Container(width: 10, height: 20);
  print(identical(c1, c2)); //false
}

10.4、在构造函数上使用const 共享

构造函数使用const,实例化使用const

class Container {
  final int width;//使用final定义变量
  final int height;
  const Container({required this.width, required this.height});
}

void main() {
  var c1 = const Container(width: 10, height: 20);
  var c2 = const Container(width: 10, height: 20);
  print(identical(c1, c2)); //true  因为实例化对象数据相同
}

对于重复的数据 使用const 只需要使用一个存储空间 以达到性能优化

11、Dart和JavaScript对比

JavaScript 的“弱类型”一直被诟病,所以 TypeScript (JavaScript语言的超集,语法兼容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 开发,这就有优势了!