1. 概述
1.1. 简介
- Dart 是谷歌开发的,类型安全的,面向对象的编程语言,被应用与Web、服务器、移动应用和物联网等领域。
- Dart 诞生于2011年10月10日。
- Dart 简单易学(类型TypeScript,是强类型的语言)
- 运行方式
- 原生虚拟机(Dart 代码可以运行在 Windows、Mac、Linux 上)
- JavaScript 引擎 (Dart 代码可以转为 JS 代码,然后运行在浏览器上)
1.2. Dart 与 JavaScript
1.3. 环境搭建
- 从 Flutter 1.21 版本开始,Flutter SDK 会同时包含完整的 Dart SDK。
- 如果你已经安装了 Flutter,就无需再下载 Dart SDK 了。
- 官网介绍:dart.cn/get-dart
- 绑定环境变量
- 先确定 Flutter SDK 的安装路径
- 例如:我的 Flutter SDK 的安装路径是 D:\flutter
- D:\flutter\bin
- Dark 和 Flutter 的相关执行命令
- D:\flutter\bin\cache\dart-sdk\bin
- Dart SDK 的相关命令的执行路径
- D:\flutter.pub-cache\bin
- Pub下载的公共模块的执行路径
- 先确定 Flutter SDK 的安装路径
1.4. 资源网站
- 官网:
- 在线运行代码:
- 生态
2. 语法基础
- 注释语法与 JS 一致
- 声明函数不需要关键字(JS 中通过
function关键字来声明函数)- 函数和参数前面都有类型声明,
void表示没有返回值,int是整型数字- 打印使用
console.log())- 每行代码结束时,必须写结束分号(;)
- 字符串使用引号包起来,支持模板字符串
main是入口函数,Dart 应用程序总是从main函数开始执行- 用
var声明的变量,其数据类型是动态的
2.1. 运行代码
- 在命令行中运行
dart hello.dart
- Dart 执行文件中的
main函数
void main(){
print('Hello, World');
}
- 输出:Hello, World
2.2. 注释
- 单行注释
//我是单行注释
- 多行注释
/*我是多行注释*/
- 文档注释
///我是文档注释- 可以通过
dartdoc将注释转成文档(文档注释支持markdown语法)
2.3. 变量
- 变量是一个引用,Dart 万物皆对象,变量存储的是对象的引用
- 声明变量:
- 明确指定类型:
int age = 18; - 不明确类型:
var age = 18;或者dynamic age = 18; - 变量名大小写敏感(age 和 Age 是两个不同的变量)
- 变量默认值是
null(JS 中变量的默认值是undefined) - Dart 变量的值不会进行隐式转换(
null不会自动转成false)
- 明确指定类型:
void main() {
// 声明变量
var age = 18;
print(age); // 18
// 指定数据类型的方式
String name = '张三';
// String name = 123; 报错,不能将数字类型123赋值给 String类型的 name
print(name); // 张三
// dynamic myName = 18;
dynamic myName = '李四';
print(myName); // 李四
// 变量的默认值
var defaultName;
print(defaultName); // null
// 变量名大小写敏感
var Age = 30;
print(Age); // 30
}
2.4. 常量
- 常量就是值不可变的变量(一旦声明,其值不可更改)
- 声明常量
const age = 18;final age = 18;
const和final的区别:const time = DateTime.now(); // 报错,无法将运行时的值赋值给 const 变量final time = DateTime.now(); // 成功,可以将运行时的值赋值给 final 变量
void main() {
const n1 = 1;
// n1 = 2; // 报错,常量一旦声明无法修改
print(n1); // 1
final n2 = 2;
print(n2); // 2
}
3. 数据类型
3.1. Number
-
Dart 中的数字由三个关键字描述
num数字类型(既可以是整数,也可以是小数)int表示整数(必须是整数)double表示浮点数(既可以是整数,也可以是小数)
-
常用API
void main() {
// 声明整数
int count = 3;
print(count); // 3
// 声明浮点数
// double price = 4; // 4.0
double price = 3.7;
print(price); // 3.7
// 声明数值类型,不确定具体的数字类型时使用
// num n1 = 3.8;
num n1 = 10;
print(n1); // 10
// 类型转换
print(n1.toString());
print(3.18.toInt()); // 向下取整
// 四舍五入
print(3.1415926.round()); // 3
print(3.1415926.toStringAsFixed(2)); // 3.14, 保留2位小数
// 返回余数
print(10.remainder(4)); // 2
// 数字比较: 0:相等,1:大于,-1:小于
print(10.compareTo(12)); // -1
// 返回最大公约数
print(12.gcd(18)); // 6
// 科学计数法
print(1000.toStringAsExponential(2)); // 1.00e+3
}
3.2. String
-
声明字符串(String)
- 单引号、双引号都可以
- 三个引号可以声明包含换行符的字符串
-
常见API
-
正则表达式
RegExp(r'正则表达式');RegExp(r'/d+');
void main() {
// 声明字符串
// var str1 = 'Hello World'; // 单引号
// print(str);
// var str2 = "你好,世界"; // 双引号
// print(str2);
String str1 = 'Hello World'; // 单引号
print(str1);
String str2 = "你好,世界"; // 双引号
print(str2);
// 三引号,可以是三个单引号,也可以是三个双引号,但必须成对
String str3 = '''Hello
World
''';
print(str3);
// 常见操作
// 字符串拼接
print(str1 + str2);
// 模板字符串写法
print("$str1 $str2");
// 字符串的分割
print(str1.split('')); // [H, e, l, l, o, , W, o, r, l, d]
// 字符串去除空格
print(' abc '.trim()); // abc
// 判断字符串是否为空
print(''.isEmpty); // true
print('abc'.isEmpty); // false
print(''.isNotEmpty); // false
print('abc'.length); // 3
// 字符串的替换
print(str1.replaceAll('World', 'Dart')); // Hello Dart
// 支持正则替换
print('h1n2b3v4k5j6l7'.replaceAll(RegExp(r'\d+'), '-')); // h-n-b-v-k-j-l-
// 通过正则匹配手机号
var isPhone = RegExp(r'^1\d{10}$');
print(isPhone.hasMatch('13333333333')); // true
print(isPhone.hasMatch('1333333333')); // false
// 查找字符串
print(str1.contains('e')); // true
// 定位字符串
print(str1.indexOf('e')); // 1
}
3.3. Boolean
- Dart 通过
bool关键字来表示布尔类型 - 布尔类型只有2个值:
true和false - 对变量进行判断时,要显式地检查布尔值
if(varName){...}×if(varName == 0){...}√if(varName == null){...}√
void main() {
// 声明布尔类型
bool flag1 = true;
print(flag1);
bool flag2 = false;
print(flag2);
// 显式地进行判断
// 因为 Dart 是强类型语言,声明变量时类型就确定了,所以在 Dart 中没有 ===,只有 == 用于判断是否相等
var flag3;
if (flag3 == null) {
print('真');
} else {
print('假');
}
// 特殊的判断
var n1 = 0 / 0;
print(n1); // NaN
// 判断一个变量是否是 NaN
print(n1.isNaN); // true
}
3.4. List
- Dart 中的数组,由
List对象表示。List有2种声明方式- 字面量方式
List list = []; // 不限定元素的数据类型List list = <int>[]; // 限定元素的数据类型是 int
- 构造函数方式
List list = new List.empty(growable:true); // 不限制长度的空列表List list = new List.filled(3, 0); // 声明指定长度的填充列表
- 字面量方式
- 扩展操作符(...)
var list = [1, 2, 3];var list2 = [0, ...list]; // [0, 1, 2, 3]
- 常用API
void main() {
// 声明List - 字面量
List l1 = ['a', 'b', 'c', 1, 2, 3];
print(l1); // [a, b, c, 1, 2, 3]
// 限定元素类型的声明方式
List l2 = <int>[1, 2, 3];
print(l2);
// 通过构造函数的声明方式,growable: true 表示数组可扩充
var l3 = new List.empty(growable: true);
l3.add(1);
print(l3);
var l4 = new List.filled(3, 6);
print(l4); // [6, 6, 6]
// 扩展运算符
var l5 = [0, ...l4];
print(l5); // [0, 6, 6, 6]
// 扩展运算符的一个变种, l6的默认值是null,不能使用扩展运算符,会报错,可使用 ...?
var l6;
var l7 = [7, ...?l6];
print(l7); // [7]
// 返回列表的长度
print(l1.length); // 6
// 列表的翻转
print(l1.reversed); // (3, 2, 1, c, b, a),翻转后的结果不再是数组了,但任然是可迭代的
print(l1.reversed.toList()); // [3, 2, 1, c, b, a],通过 toList() 方法转换为数组
// 添加元素
l3.addAll([4, 5, 6]);
print(l3); // [1, 4, 5, 6]
// 删除元素
l3.remove(6);
print(l3); // [1, 4, 5]
// 根据索引删除元素
l3.removeAt(1);
print(l3); // [1, 5]
// 在指定的位置添加元素
l3.insert(1, 9);
print(l3); // [1, 9, 5]
// 清空
l3.clear();
print(l3.length);
print(l3.isEmpty); // true
// 合并元素
List words = ['Hello', 'World'];
print(words.join('-')); // Hello-World
}
- 遍历 List
forEach()- 遍历列表
map()- 遍历并处理元素,然后生成新的列表
where()- 返回满足条件的数据
any()- 只要有一项满足条件,即返回
true
- 只要有一项满足条件,即返回
every()- 判断每一项是否都满足条件,都满足条件才返回
true
- 判断每一项是否都满足条件,都满足条件才返回
void main() {
var nums = [1, 2, 3];
// for 循环进行遍历
for (var i = 0; i < nums.length; i++) {
print(nums[i]);
}
// for...in
for (var item in nums) {
print(item);
}
// forEach
nums.forEach((item) {
print(item);
});
// map
var newNums = nums.map((e) {
return e * e;
});
print(newNums); // (1, 4, 9)
print(newNums.toList()); // [1, 4, 9]
// where 返回符合条件的元素
bool isOdd(n) => n % 2 == 1;
var oddNum = nums.where((item) => isOdd(item));
print(oddNum.toList()); // [1, 3]
// 使用 any() 来检测是否有奇数(至少一个)
print(nums.any(isOdd)); // true
// 使用 every() 来判断是否都是奇数
print(nums.every(isOdd)); // false
// 拉平数组
var pairs = [
[1, 2],
[3, 4]
];
var flattened = pairs.expand((item) => item).toList();
print(flattened); // [1, 2, 3, 4]
// 折叠 - 对列表中的每一个元素,做一个累计操作
int result = nums.fold(2, (pre, cur) => pre * cur); // 2 * (1 * 2 * 3)
print(result); // 12
}
3.5. Set
- Set 是一个无序的,元素唯一的集合
- Set 有字面量和构造函数两种声明方式(字面量中用大括号)
- 无法通过下标取值
- 具有集合特有的操作
- 例如:求交集、并集、差集等
- 常用API
void main() {
// 字面量
var nums = <int>{1, 2, 3};
print(nums); // {1, 2, 3}
// 构造函数
var fruits = new Set();
fruits.add('苹果');
fruits.add('香蕉');
fruits.add('橘子');
print(fruits); // {苹果, 香蕉, 橘子}
print(fruits.toList()); // [苹果, 香蕉, 橘子]
List myNums = [1, 2, 2, 3, 4];
print(myNums.toSet()); // {1, 2, 3, 4},可以将重复的元素过滤掉
// 集合特有的操作
var caocao = new Set();
caocao.addAll(['张辽', '司马懿', '关羽']);
var liubei = new Set();
liubei.addAll(['关羽', '张飞', '诸葛亮']);
// 求交集
print(caocao.intersection(liubei)); // {关羽}
// 求并集
print(caocao.union(liubei)); // {张辽, 司马懿, 关羽, 张飞, 诸葛亮}
// 求差集
print(caocao.difference(liubei)); // {张辽, 司马懿}
// 返回第一个元素
print(caocao.first); // 张辽
// 返回最后一个元素
print(caocao.last); // 关羽
// 集合不能通过下标取值
// print(caocao[1]); // 报错
}
3.6. Map
- Map 是一个无序的键值对(key - value)映射,通常被称为哈希或字典。
- 声明方式
var map = { key1: value1, key2: value2 };var map = new Map();map['key'] = value;
- 常用API
void main() {
// 字面量
var person = {
'name': 'zs',
'age': 20,
};
print(person); // {name: zs, age: 20}
// 构造函数
// var p = new Map();
var p = Map(); // new 关键字可以省略
p['name'] = 'ls';
p['age'] = 22;
print(p);
// 访问属性
print(p['name']); // ls
// 判断 Map 中的 key 是否存在
print(p.containsKey('name')); // true
print(p.containsKey('aaa')); // false
// 赋值
// 如果 key 不存在,我们才赋值(如果key已经存在,则不赋值)
p.putIfAbsent('gender', () => '男');
print(p); // {name: ls, age: 22, gender: 男}
p.putIfAbsent('gender', () => '女');
print(p); // {name: ls, age: 22, gender: 男}
// 获取 Map 中所有的 key
print(p.keys); // (name, age, gender)
// 获取 Map 中所有的 value
print(p.values); // (ls, 22, 男)
// 根据条件进行删除
p.removeWhere((key, value) => key == 'gender');
print(p); // {name: ls, age: 22}
}
3.7. 其他
- Runes(符文)
Runes对象是一个 32 位字符对象。它可以把文字转换成符号表情或特定的文字print('\u{1f44d}'); // 👍- copychar.cc/
- Symbol
- 在 Dart 中符号用 # 开头来表示的标识符
- dynamic
- 动态数据类型
void main() {
var str = '😍';
print(str); // 😍
print(str.length); // 2 UTF-16
print(str.runes.length); // 1 UTF-32
// Runes 可以将 UTF-32 字符集表示的内容转成符号
Runes input = new Runes('\u{1f680}');
print(new String.fromCharCodes(input)); // 🚀
// Symbol
var a = #abc;
print(a); // Symbol("abc")
var b = new Symbol('abc');
print(b); // Symbol("abc")
print(#abc == new Symbol('abc')); // true
// 声明动态类型的变量
dynamic foo = 'bar';
foo = 123;
print(foo); // 123
}
4. 运算符
- 地板除:求余数再向下取整(
~/) - 类型判断运算符(
is|is!) - 避空运算符(
??|??=) - 条件属性访问(
?.) - 级联运算符(
..)myObject.myMethod(); // 返回 myMethod 的返回值myObject..myMethod(); // 返回 myObject 对象的引用
void main() {
// 地板除
print(7 ~/ 4); // 1
// 类型判断运算符
List list = [];
if (list is List) {
print('list is List');
} else {
print('list is not List');
} // list is List
if (list is! List) {
print('不是列表');
} else {
print('是列表');
} // 是列表
// 避空运算符
print(1 ?? 3); // 1
print(null ?? 12); // 12
var foo;
// foo = 6;
print(foo ?? 0); // 如果 foo 是 null,则返回 0
var a;
// if (a == null) {
// a = 3;
// }
a ??= 3;
print(a); // 3
a ??= 6; // 如果 a 不是 null,则赋值失败
print(a); // 3
// 条件属性运算符(保护可能为空的属性)
var m = new Map();
print(m.length); // 0
var obj;
// print(obj.length); // TypeError: Cannot read properties of null (reading 'get$length')
print(obj?.length); // null
// 级联运算符
// Set s = new Set();
// s.add(1);
// s.add(2);
// s.add(3);
// s.remove(2);
// print(s); // {1, 3}
Set s = new Set();
s
..add(1)
..add(2)
..add(3)
..remove(2);
print(s); // {1, 3}
}
5. 函数
5.1. 声明函数
- 直接声明
- Dart 中声明函数不需要
function关键字
- Dart 中声明函数不需要
- 箭头函数
- Dart 中的箭头函数,函数体只能写一行且不能带有结束的分号
- Dart 中的箭头函数,只是函数的一种简写形式,没有其他副作用(这点与 JS 不同)
- 匿名函数
- 立即执行函数
// Dart 中声明函数,不需要 function 关键字
void printInfo() {
print('Hello World');
}
// 返回值,与函数声明的类型要一致
int getNum() {
// return 'Hello'; // 不能返回字符串类型
return 1;
}
void main() {
// 调用函数
printInfo(); // Hello World
print(getNum()); // 1
// 匿名函数
var myPrint = (value) {
print(value);
};
List fruits = ['苹果', '香蕉', '橘子'];
fruits.forEach(myPrint);
// 箭头函数
fruits.forEach((item) => {
print(item) // 箭头函数中,不能写结束的分号(;)
});
fruits.forEach((item) => print(item));
// 立即执行函数
((int n) {
print(n); // 17
})(17);
}
5.2. 函数参数
- 必填参数
- 参数类型参数名称
- 可选参数
- 放在必选参数后面
- 通过中括号包裹起来
- 带默认值的可选参数
- 命名参数
- 用大括号包裹起来
- 调用函数时,命名参数的名称与声明函数中的名称保持一致
- 函数参数
void main() {
// 必填参数
String userInfo(String name) {
return '你好:$name';
}
String res = userInfo('张三');
print(res); // 你好:张三
// 可选参数
String userInfo2(String name, [int age = 0]){
return '你好:$name,年龄:$age';
}
String res2 = userInfo2('李四');
print(res2); // 你好:李四,年龄:0
// 命名参数
String userInfo3(String name, {int age = 0}){
return '你好:$name,年龄:$age';
}
// 命名参数调用时,需要与声明的形参一致
String res3 = userInfo3('李四', age: 20);
print(res3); // 你好:李四,年龄:20
// 函数参数
var myPrint = (value) {
print(value);
};
List fruits = ['苹果', '香蕉', '橘子'];
// 将匿名函数 myPrint 传递给函数 forEach
fruits.forEach(myPrint);
}
5.3. 作用域与闭包
- 作用域和闭包与JS的一样
- Dart 中闭包的实现方式与JavaScript 中完全一致
- 使用时机:即能重用变量,又保护变量不被污染
- 实现原理:外层函数被调用后,外层函数的作用域对象 (AO)被内层函数引用着,导致外层函数的作用域对象无法释放,从而形成闭包
var globalNum = 10;
void main() {
printInfo() {
// 局部变量
var localNum = 100;
print(localNum); // 100
print(globalNum); // 10 我们可以在函数作用域中,访问全局变量
}
printInfo();
// print(localNum); // 不能再全局作用域中,访问局部变量
// 闭包
parent() {
var money = 1000;
return () {
money -= 100;
print(money);
};
}
var p = parent();
p(); // 900
p(); // 800
p(); // 700
}
5.4. 异步函数
-
JavaScript 中,异步调用通过
Promise来实现async函数返回一个Promise。await用于等待Promise
-
Dart 中,异步调用通过
Future来实现async函数返回一个Future,await用于等待Future
-
Future 详情
-
第一种用法:
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
// https://httpbin.org/ip 返回 IP 地址
Future getIpAddress() {
var url = Uri.https('httpbin.org', 'ip');
return http.get(url).then((response) {
print(response.body);
String ip = convert.jsonDecode(response.body)['origin'];
return ip;
});
}
void main() {
// 调用 getIpAddress
getIpAddress()
.then((ip) => print(ip))
.catchError((error) => print(error)); // 107.189.14.105
}
- 第二种用法:
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
// https://httpbin.org/ip 返回 IP 地址
Future getIpAddress() async {
var url = Uri.https('httpbin.org', 'ip');
final response = await http.get(url);
String ip = convert.jsonDecode(response.body)['origin'];
return ip;
}
void main() async {
// 调用 getIpAddress
try {
final ip = await getIpAddress();
print(ip); // 107.189.14.105
} catch (error) {
print(error);
} finally {
print('finally'); // finally
}
}
6. 类与对象
6.1. 类
6.1.1. 类的简介
- 类是通过
class声明的代码段,包含属性和方法- 属性: 用来描述类的变量
- 方法:类中的函数称为类的方法
- 对象是类的实例化结果 (
var obj= new Myclass()) - 编程方式
- 面向对象编程(OOP)
- 面向过程编程(POP)
// 声明类
class Person {
// 类的属性
String name = '张三';
// 类的方法
void getInfo() {
print('我是:$name');
}
}
void main() {
// 实例化类,然后得到一个对象
Person p = new Person();
// 访问类中的属性
print(p.name); // 张三
// 访问类中的方法
p.getInfo(); // 我是:张三
// Dart 中所有的内容都是对象
Map m = new Map();
print(m.length); // 0
m.addAll({'name': '李四', 'age': 20});
print(m.length); // 2
}
6.1.2. 构造器(构造函数)
- 默认构造函数
- 与类同名的函数,在实例化时,自动被调用
// 声明类
class Point {
var x, y;
// 声明普通构造函数
Point() {
print('我是默认构造函数,会第一个被调用');
// this 可以省略
// x = 0;
// y = 0;
// 当命名指向有歧义时,this 不能省略
this.x = 0;
this.y = 0;
}
}
void main() {
var p = new Point();
print(p.x); // 0
}
// 声明类
class Point {
var x, y;
// 声明带参数的普通构造函数
Point(num x, num y) {
// 当命名指向有歧义时,this 不能省略
this.x = x;
this.y = y;
}
}
void main() {
var p = new Point(3, 4);
print(p.y); // 4
}
// 声明类
class Point {
var x, y;
// 声明带参数的普通构造函数的简写形式
Point(this.x, this.y);
}
void main() {
var p = new Point(3, 4);
print(p.y); // 4
}
- 命名构造函数
- 在类中使用命名构造器(
类名.函数名)实现多个构造器,可以提供额外的清晰度
- 在类中使用命名构造器(
// 声明类
class Point {
var x, y;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
// 命名构造函数
Point.fromJson({x = 0, y = 0}) {
this.x = x;
this.y = y;
}
}
void main() {
// 默认坐标
Point p1 = new Point.origin();
print(p1.y); // 0
// 手动设置坐标
Point p2 = new Point.fromJson(x: 3, y: 6);
print(p2.x); // 3
}
- 常量构造函数
- 如果类生成的对象不会改变,您可以通过常量构造函数使这些对象成为编译时常量
- 使用不可变对象可以提高 Flutter 的渲染效率
class Point {
var x, y;
Point(this.x, this.y);
}
class ImmutablePoint {
// 属性必须通过 final 声明
final num x;
final num y;
// 常量构造函数,必须通过 const 声明,同时不能有函数体
const ImmutablePoint(this.x, this.y);
}
void main() {
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
print(p1 == p2); // false
// 常量构造函数,可以当做普通构造函数使用
var p3 = new ImmutablePoint(1, 2);
var p4 = new ImmutablePoint(1, 2);
print(p3 == p4); // false
// 声明不可变对象,必须通过 const 关键字
var p5 = const ImmutablePoint(1, 2);
var p6 = const ImmutablePoint(1, 2);
print(p5 == p6); // true
// 实例化时,new 关键字,可以省略
var p7 = ImmutablePoint(1, 2);
var p8 = ImmutablePoint(1, 2);
print(p7 == p8); // false
}
- 工厂构造函数
- 通过
factory关键字声明,工厂函数不会自动生成实例,而是通过代码来决定返回的实例
- 通过
class Person {
String name;
static var instance;
// 工厂构造函数
factory Person([String name = '刘备']) {
// 工厂构造函数中,不能使用 this 关键字
// print(this.name);
if (Person.instance == null) {
// 第一次实例化
Person.instance = Person.newSelf(name);
}
// 非第一次实例化
return Person.instance;
}
// 命名构造函数
Person.newSelf(this.name);
}
void main() {
// 实例化操作
Person p1 = new Person('关羽');
print(p1.name); // 关羽
Person p2 = new Person('张飞');
print(p2.name); // 关羽
print(p1 == p2); // true
}
6.1.3. 访问修饰
- Dart 与 TypeScript 不同,没有访问修饰符 (
public、protected、private) - Dart 类中,默认的访问修饰符是公开的 (即
public) - 如果属性或方法以
_(下划线)开头,则表示私有 (即private) - 只有把类单独抽离出去,私有属性和方法才起作用
lib/Person.dartimport 'lib/Person.dart'
class Person {
String name;
// 声明私有属性
num _money = 100;
Person(this.name);
}
void main() {
var p = new Person('张三');
print(p.name); // 张三
// 访问私有属性,因为 Person 类和 main 函数平级,所以 Person 类的私有属性不起作用,仍然可以访问到,要让私有属性起作用,需要将 Person 类抽离出去
print(p._money); // 100
}
抽离后:
// lib/Person.dart
class Person {
String name;
// 声明私有属性
num _money = 100;
Person(this.name);
num getMoney() {
return this._money;
}
// 声明私有方法
void _wife() {
print('我是 $name 的老婆');
}
}
import 'lib/Person.dart';
void main() {
var p = new Person('张三');
print(p.name); // 张三
// 被抽离后就访问不到私有属性了
// print(p._money);
print(p.getMoney()); // 100
// 访问私有方法
// p._wife(); // 不能访问
}
6.1.4. Getter 和 Setter
- Getter (获取器) 是通过
get关键字修饰的方法- 函数没有小括号,访问时也没有小括号(像访问属性一样访问方法)
- Setter (修改器)是通过
set关键字修饰的方法- 访问时,像设置属性一样给函数传参
class Circle {
final double PI = 3.1415;
num r;
Circle(this.r);
// num area() {
// return this.PI * this.r * this.r;
// }
// 使用 get 声明的方法,不能有小括号
num get area {
return this.PI * this.r * this.r;
}
// Setter
set setR(value) {
this.r = value;
}
}
void main() {
var c = new Circle(100);
// print(c.area()); // 31415.000000000004
// 通过 Setter 修改属性
c.setR = 20;
print(c.area); // 1256.6000000000001
}
6.1.5. 初始化列表
- 作用:在构造函数中设置属性的默认值
- 时机:在构造函数体执行之前执行
- 语法:使用逗号分隔初始化表达式
- 场景:常用于设置
final常量的值
class Rect {
var height;
var width;
// Rect(this.height, this.width);
// Rect([int height = 2, int width = 10]) {
// this.height = height;
// this.width = width;
// print('${this.height} --- ${this.width}');
// }
// Rect({int height = 2, int width = 10}) {
// this.height = height;
// this.width = width;
// print('${this.height} --- ${this.width}');
// }
// 初始化列表
Rect()
: height = 4,
width = 20 {
print('${this.height} --- ${this.width}');
}
}
class Point {
double x, y, z;
Point(this.x, this.y, this.z);
// 初始化列表的特殊用法(重定向构造函数)
Point.twoD(double x, double y) : this(x, y, 0);
}
void main() {
var r = new Rect();
// 实例化点
var p = new Point(1, 2, 3);
print(p.z); // 3
var p2 = new Point.twoD(4, 5);
print(p2.z); // 0
}
6.1.6. static
static关键字用来指定静态成员- 通过
static修饰的属性是静态属性 - 通过
static修饰的方法是静态方法
- 通过
- 静态成员可以通过类名称直接访问 (不需要实例化)
- 实例化是比较消耗资源的,声明静态成员,可以提高程序性能
- 静态方法不能访问非静态成员,非静态方法可以访问静态成员
- 静态方法中不能使用
this关键字 - 不能使用
this关键字,访问静态属性
- 静态方法中不能使用
class Person {
static String name = '张三';
int age = 18;
static void printInfo() {
// print(this.name); // 不能通过 this 关键字访问静态属性
print(name);
// 静态方法中不能访问非静态属性
// print(age);
}
printUserInfo() {
// 非静态方法,可以访问静态属性
print(name);
print(age);
}
}
void main() {
// 静态成员,可以通过类名称直接访问
print(Person.name); // 张三
Person.printInfo(); // 张三
// 不能通过类名称,直接访问非静态方法
// print(Person.printUserInfo());
var p = new Person();
// print(p.name); // 不能通过实例化对象访问静态属性
p.printUserInfo();
}
6.1.7. 元数据
- 元数据以
@开头,可以给代码标记一些额外的信息- 元数据可以用来库,类,构造器,函数,字段,参数或变量声明的前面
@override(重写)- 某方法添加该注解后,表示重写了父类中的同名方法
@required(必填)- 可以通过
@required来注解 Dart 中的命名参数,用来指示它是必填参数
- 可以通过
@deprecated(弃用)- 若某类或某方法加上该注解之后,表示此方法或类不再建议使用
class Phone {
// 这是旧版本中的开机方法,会在将来的版本中移除
@deprecated
activate(){
turnOn();
}
turnOn(){
print('开机');
}
}
void main() {
var p = new Phone();
p.activate(); // 开机
}
6.2. 继承
- 根据类的先后顺序,可以将类分成父类和子类
- 子类通过
extends关键字 继承父类- 继承后,子类可以使用父类中,可见的内容 (属性或方法)
- 子类中,可以通过
@override元数据来标记“覆写”方法- “覆写”方法:子类中与父类中同名的方法
- 子类中,可以通过
super关键字来引用父类中,可见的内容- 属性
- 方法(普通构造函数,命名构造函数)
class Father {
String name = '刘备';
num money = 10000;
say() {
print('我是:$name');
}
}
class Son extends Father {
@override
say() {
print('我是:刘禅');
}
}
void main() {
var f = new Father();
print(f.name);
var s = new Son();
print(s.name);
print(s.money);
s.say();
}
接下来演示私有属性和方法
// Father.dart
class Father {
String name = '刘备';
num _money = 10000;
String job;
Father(this.job);
// 命名构造函数
Father.origin(this.job);
say() {
print('我是:$name');
}
get getMoney {
return this._money;
}
}
// Son.dart
import 'Father.dart';
class Son extends Father {
String name = '刘禅';
// 通过 super 继承父类的普通构造函数
Son(String job) : super(job);
// 继承命名构造函数
// Son(String job) : super.origin(job);
Son.origin(String job) : super.origin(job);
@override
say() {
super.say(); // 调用父类中的方法
print('我是:刘禅,我爹是${super.name},他的工作是${super.job}');
}
}
// main.dart
import 'lib/Father.dart';
import 'lib/Son.dart';
void main() {
var f = new Father('皇帝');
print(f.name);
// var s = new Son('皇帝');
var s = new Son.origin('卖草鞋的');
print(s.name);
// print(s._money); // 子类不能访问父类中的私有内容
print(s.getMoney); // 10000
s.say();
}
6.3. 抽象类
- 抽象类是用
abstract关键字修饰的类 - 抽象类的作用是充当普通类的模板,约定一些必要的属性和方法
- 抽象方法是指没有方法体的方法
- 抽象类中一般都有抽象方法,也可以没有抽象方法
- 普通类中,不能有抽象方法
- 抽象类不能被实例化 (不能被
new) - 抽象类可以被普通类继承 (
extends)- 如果普通类继承抽象类,必须实现抽象类中所有的抽象方法
- 抽象类还可以充当接口被实现 (
implements)- 如果把抽象类当做接口实现的话,普通类必须得实现抽象类里面定义的所有属性和方法
// 1. 抽象类,必须通过 abstract 关键字声明
// 2. 抽象类中,可以有抽象方法,也可以没有抽象方法,一般来说,抽象类都有抽象方法
abstract class Phone {
// 声明抽象方法
void processor(); // 手机的处理器
void camera(); // 手机的摄像头
void info() {
print('我是抽象类中的一个普通方法');
}
}
class Xiaomi extends Phone {
// 普通类继承了抽象类,就必须实现抽象类中所有的抽象方法
@override
void processor() {
print('晓龙888');
}
@override
void camera() {
print('1200万');
}
// 普通类中不能有抽象方法
// void aaa();
}
class Huawei extends Phone {
@override
void processor() {
print('麒麟990');
}
@override
void camera() {
print('徕卡摄像头');
}
}
void main() {
// 抽象类,不能被实例化
// var p = new Phone();
Xiaomi m = new Xiaomi();
m.processor();
m.camera();
m.info();
Huawei h = new Huawei();
h.processor();
h.camera();
h.info();
}
6.4. 接口
- 接口在 Dart 中就是一个类 (只是用法不同)
- 与Java 不同,Java 中的接口需要用
interface关键字声明;Dart 中不需要 - 接口可以是任意类,但一般使用抽象类做接口
- 与Java 不同,Java 中的接口需要用
- 一个类可以实现 (
implements) 多个接口,多个接口用逗号分隔class MyClass implements Interface1, Interface2 {...}- 接口可以看成一个个小零件。类实现接口就相当于组装零件
- 普通类实现接口后,必须重写接口中所有的属性和方法
// 手机的处理器
abstract class Processor {
var cores; // 内核:2核,4核
arch(String name); // 芯片制程:7nm,5nm
}
// 手机的摄像头
abstract class Camera {
var resolution; // 分辨率:1000万,3000万
brand(String name); // 品牌:三星,徕卡,蔡司
}
// 通过普通类实现接口
class Phone implements Processor, Camera {
@override
var cores;
@override
var resolution;
Phone(this.cores, this.resolution);
@override
arch(String name) {
print('芯片制程:' + name);
}
@override
brand(String name) {
print('相机品牌:' + name);
}
}
void main() {
Phone p = new Phone('4核', '3000万');
p.arch('5nm');
p.brand('徕卡');
}
6.5. 混入
- 混入 (Mixin)是一段公共代码。混入有两种声明方式:
将类当作混入class MixinA { ...}作为Mixin的类只能继承自object,不能继承其他类作为 Mixin 的类不能有构造函数
- 使用
mixin关键字声明mixin MixinB {..}
- 混入(Mixin)可以提高代码复用的效率,普通类可以通过
with来使用混入class MyClass with MixinA, MixinB {...}
- 使用多个混入时,后引入的混入会覆盖之前混入中的重复的内容
- MixinA 和 MixinB 中都有
hello()方法,MyClass 会使用 MixinB 中的
- MixinA 和 MixinB 中都有
mixin MixinA {
String name = 'MixinA';
void printA() {
print('A');
}
void run() {
print('A is runing');
}
}
mixin MixinB {
String name = 'MixinB';
void printB() {
print('B');
}
void run() {
print('B is runing');
}
}
class MyClass with MixinA, MixinB {}
void main() {
var c = new MyClass();
c.printA();
c.printB();
// 后引入的混入,会覆盖之前引入的混入中的重复的内容
print(c.name); // MixinB
c.run(); // B is runing
}
6.6. 泛型
- 泛型是在函数、类、接口中指定宽泛数据类型的语法
- 泛型函数
- 泛型类
- 泛型接口
- 通常,在尖括号中,使用一个字母来代表类型,例如E, T, S, K,和 V 等
返回类型 函数名<输入类型>(参数类型 参数) { 函数体 }T getData<T>(T value) { return value; }
- 作用:使用泛型可以减少重复的代码
6.6.1. 泛型函数
// String getData(String value) {
// return value;
// }
// int getData(int value) {
// return value;
// }
// 不指定数据类型的函数
// getData(value) {
// return value;
// }
// 泛型函数
// T getData<T>(T value) {
// return value;
// }
// 只约定参数类型,不约定函数返回值的类型
getData<T>(T value) {
return value;
}
void main() {
// print(getData('Hello'));
// print(getData(10));
// 调用泛型函数
print(getData<int>(20));
print(getData<String>('Hello'));
}
6.6.2. 泛型类
class CommonClass {
Set s = new Set<int>();
void add(int value) {
this.s.add(value);
}
void info() {
print(this.s);
}
}
// 泛型类
class GenericsClass<T> {
Set s = new Set<T>();
void add(T value) {
this.s.add(value);
}
void info() {
print(this.s);
}
}
void main() {
CommonClass c = new CommonClass();
c.add(1);
c.info();
// 实例化泛型类
GenericsClass g = new GenericsClass<int>();
g.add(1);
// g.add('2'); // 报错
g.info();
GenericsClass g1 = new GenericsClass<String>();
g1.add('Hello');
g1.add('World');
g1.info();
// Set s = new Set();
// s.add(1);
// s.add('Hello');
// print(s);
// Set s = new Set<int>();
// s.add(1);
// // s.add('Hello'); // 报错
// print(s);
// 字面量形式的泛型
Set s = <int>{};
s.add(1);
// s.add('Hello'); // 报错
print(s);
}
6.6.3. 泛型接口
abstract class ObjectCache {
getByKey(String key);
void setByKey(String key, Object value);
}
abstract class StringCache {
getByKey(String key);
void setByKey(String key, String value);
}
// 泛型接口
abstract class Cache<T> {
getByKey(String key);
void setByKey(String key, T value);
}
// 文件缓存
class FileCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print('文件缓存:key=${key} value=${value}');
}
}
// 内存缓存
class MemoryCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print('内存缓存:key=${key} value=${value}');
}
}
void main() {
// 文件缓存 - 缓存字符串
FileCache fc = new FileCache<String>();
fc.setByKey('foo', 'bar');
// fc.setByKey('f', 2); // 报错
// 文件缓存 - 缓存Map
FileCache<Map> fcm = new FileCache<Map>();
fcm.setByKey('index', {'name': '张三丰', 'age': 218});
// 内存缓存 - 缓存字符串
MemoryCache mc = new MemoryCache<String>();
mc.setByKey('foo', 'bar');
// 内存缓存 - 缓存集合
MemoryCache<Map> mcm = new MemoryCache<Map>();
mcm.setByKey('home', {'name': '张三丰', 'age': 218});
}
6.6.4. 泛型类型限制
class SomeBaseClass {
// ...
}
class Foo<T extends SomeBaseClass> {
String toString() => "Instance of 'Foo<$T>'";
}
// 子类
class Extender extends SomeBaseClass {}
class AnotherClass {
// ...
}
void main() {
var someBaseClassFoo = Foo<SomeBaseClass>();
print(someBaseClassFoo); // Instance of 'Foo<SomeBaseClass>'
var extenderFoo = Foo<Extender>();
print(extenderFoo); // Instance of 'Foo<Extender>'
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
// var f = Foo<AnotherClass>(); // 类型不对,报错
// print(f);
}
6.7. 枚举
- 枚举是数量固定的常量值,通过
enum关键字声明enum Color {red, green, blue}
- 枚举的
values常量,可以获取所有枚举值列表List<Color> colors = Color.values;
- 可以通过
index获取值的索引assert(Colorgreen.index == 1)
enum Color { red, green, blue }
void main() {
// 通过 index 返回枚举中具体常量的值
print(Color.green.index); // 1
// 通过 values 返回常量列表
print(Color.values); // [Color.red, Color.green, Color.blue]
List<Color> colors = Color.values;
print(colors); // [Color.red, Color.green, Color.blue]
// 通过下标,访问列表中的内容
print(colors[0]); // Color.red
// 通过 forEach 去遍历列表的内容
colors.forEach((color) => print('value: $color, index: ${color.index}'));
}
7. 库与生态
- Dart 中的库就是具有特定功能的模块
- 可能包含单个文件,也可能包含多个文件
- 按照库的作者进行划分,库可以分成三类
- 自定义库 (工程师自己写的)
- 系统库 (Dart 中自带的)
- 第三方库 (Dart 生态中的)
- Dart生态
- pub.dev
- pub 命令 (D:\flutter\bin\cacheldart-sdk\bin)
7.1. 自定义库
7.1.1. 通过 library 来声明库
- 每个 Dart 文件默认都是一个库,只是没有使用
library来显示声明
void main() {
print('Hello World');
}
---------------------------------------------------------------------
library main; // 默认隐藏了一个 main 的 library 的声明
void main() {
print('Hello World');
}
- Dart 使用
_(下划线)开头的标识符,表示库内访问可见 (私有) library关键字声明的库名称建议使用: 小写字母+下划线
// library MyCustom;
library my_custom; // 建议写成小写字母+下划线的形式
class MyCustom {
String name = 'MyCustom';
static num version = 1.0;
void info() {
print('我是自定义库');
}
}
7.1.2. 通过 import 来引入库
- 不同类型的库,引入方式不同
- 自定义库 (
import '库的位置/库名称.dart') - 系统库 (
import 'dart:库名称') - 第三方库(后面单独讲)
- 自定义库 (
- 引入部分库 (仅引入需要的内容)
- 包含引入 (
show) - 排除引入 (
hide)
- 包含引入 (
- 指定库的前缀
- 当库名冲突时,可以通过
as关键字,给库声明一个前缀
- 当库名冲突时,可以通过
- 延迟引入(懒加载)
- 使用
deferred as关键字来标识需要延时加载的库
- 使用
// 引入自定义库
import 'lib/MyCustom.dart';
void main() {
MyCustom mc = new MyCustom();
mc.info(); // 我是自定义库
print(MyCustom.version); // 1.0
}
7.1.3. 通过 part 和 part of 来组装库
7.2. 系统库的引入
// 引入系统库
import 'dart:math';
// import 'dart:core'; // core 库会被默认引入
void main() {
print(pi); // 3.141592653589793
print(min(3, 6)); // 3
print(max(3, 6)); // 6
}
7.3. 引入部分库
// 部分引入
void f1() {
print('f1 is running');
}
void f2() {
print('f2 is running');
}
void f3() {
print('f3 is running');
}
-------------------------------------------------------------------------
// show 后面指定包含引入的内容
import 'lib/common.dart' show f1, f3;
void main() {
f1();
// f2();
f3();
}
-------------------------------------------------------------------------
// hide 会隐藏后面的内容
import 'lib/common.dart' hide f1, f3;
void main() {
// f1();
f2();
// f3();
}
7.4. 引入冲突处理
import 'lib/common.dart';
import 'lib/function.dart' as func; // 给库添加前缀,解决命名冲突问题
void main() {
f1(); // f1 is running
func.f1(); // f1 is of function running
}
7.5. 延迟引入
import 'lib/function.dart' deferred as func;
void main() {
// func.hello(); // 直接调用会报错
print(1);
greet();
print(2);
print(3);
}
Future greet() async {
await func.loadLibrary();
func.hello(); // Hello World
}
7.6. part 与 part of
7.7. 系统库
- 系统库(也叫核心库)是 Dart 提供的常用内置库
- 不需要单独下载,就可以直接使用
- 引入
import 'dart:库名';import 'dart:core'; // 会自动引入(无需手动引入)
- 系统库列表
void main() {
// 创建当前时间
var now = new DateTime.now();
print(now); // 2023-11-16 11:00:43.071
// 通过普通构造函数创建时间
var d = new DateTime(2021, 1, 20, 9, 30);
print(d); // 2021-01-20 09:30:00.000
// 创建标准时间
var d1 = DateTime.parse('2021-01-20 12:30:30');
print(d1); // 2021-01-20 12:30:30.000
var d2 = DateTime.parse('2021-01-20 12:30:30+0800');
print(d2); // 2021-01-20 04:30:30.000Z
// 时间增量
print(now.add(new Duration(hours: 2))); // 2023-11-16 13:00:43.071
print(now.add(new Duration(hours: -3))); // 2023-11-16 08:00:43.071
// 时间比较
print(d1.isAfter(d2)); // d1 是否在 d2 之后
print(d1.isBefore(d2)); // d1 是否在 d2 之前
// 时间差
var d3 = new DateTime(2021, 1, 1);
var d4 = new DateTime(2021, 1, 4);
var difference = d3.difference(d4);
print([difference.inDays, difference.inHours]); // [-3, -72] d3 与 d4 相差的天数与小时
// 时间戳
print(now.millisecondsSinceEpoch); // 1700104215379 单位毫秒,13位时间戳
print(now.microsecondsSinceEpoch); // 1700104215379000 单位微妙,16位时间戳
// 格式化
print(now.month.toString().padLeft(2, '0')); // 11
String timestamp = "${now.year.toString()}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')} ${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}";
print(timestamp); // 2023-11-16 11:16
}
7.8. 第三方库
- 来源
- 使用
- 在项目目录下创建
pubspec.yaml - 在
pubspec.yaml中声明第三方库(依赖) - 命令行中进入
pubspec.yaml所在目录,执行pub get进行安装 - 项目中引入已安装的第三方库(
import 'package:xxxx/xxxx.dart';)
- 在项目目录下创建
- 第三方库的结构
-
一个第三方库,必须包含一个
pubspec.yaml -
pubspec.yaml
-