基本语法
两种 main 方法的定义
main() {
print("drat demo 12312");
}
// 无返回值方法
void main() {
print("dasdas");
}
数据类型定义
var w = 12312;
// w = 'dasdas'; // 报错,类型已经被推导,无法重新赋值
w = 43534;
print(w);
int w1 = 12312;
w = 3242342;
print(w);
double w = 10; // 浮点型可以声明整形
print(w); // 10.0
int q = 10.2; // 整形不可声明浮点型
String str = '12312';
str = '1231231';
print(str);
const w = '12312';
// w = '312312'; // 报错,const 不能被重新赋值
print(w);
// 字符串相加
var w = '12312';
w = w + '-- 12312';
// w = w - '2'; // 不可减法
print(w);
// 多行文本拼接
w = ''' 123123
dasdasdasdas
''';
var q = 'value is: $w'; // 字符串模版使用 $xxx 声明;
print('$w $q');
var qq = DateTime.now(); // var 也是运行时变量
print(qq);
var q2 = DateTime.now;
print(''' $w --- $q ----- $DateTime $q2''');
bool a; // bool 类型
a = true;
print(a);
if (a) { // if else 语句
print(1);
} else {
print(2);
}
final w;
w = 123123;
// w = 423423423; // 报错 final 只能被赋值一次,可以在初始化时不给值;
print(w);
// const 在创建时,就需要赋值不可改变。final 创建时可以不赋值,后续只能赋值一次。
// const、final 最大的区别:cons 是编译时,final 是运行时(惰性)在代码运行到时,才确定值的类型
final dates = new DateTime.now();
print(dates);
const dates2 = new DateTime.now(); // 报错,因为 const 是编译时,编译时无法确定返回值的类型,因此报错。
list 类型
基础使用
var q = [1, '2', '', 4];
print(q); // 打印数组
print(q.length); // 集合长度
print(q[1]); // 获取数组指定位置值
q.add('dasdas'); // push 数据
print(q);
// 不论是 const 还是 final 都可以支持定义数组后修改
final w = [1, 2, 3];
w.add(4); // 可以修改
w.length = 2; // 可以修改
print(w); // [1,2]
q.length = 2; // 允许的操作,直接把数组截断元素也会被消失。
print('len slice $q'); // 输出:[1, '2']
var q2 = <int>[123123, 123, 123123];
print(q2); // 声明集合类型
// 创建固定长度的集合,不能扩容,也不修改长度,第二个参数为默认填充的值
var lengthList = List.filled(2, 1);
// lengthList.add(1); // 报错:不可add 数组长度已经定义好了。
lengthList[0] = 1;
// lengthList[1] = '2'; // 报错,开始已经声明了类型,类型不一致;
print(lengthList);
// 注意,这个地方和js存在明显的差异
final list = [];
list[0] = 123123;
print(list); // 报错,因为list的初始长度为空,直接操作下标赋值是不行的
list.fillRange(0, 10, 12312); // 直接操作api填充也是不行的,因为数组是空的。
list.add(123123); // 只能通过add方法来添加元素
高级用法
final list = [];
list.add(1);
list.add('12312');
list.add('456');
// 新增并返回列表本身(链式操作)
final lists = list
..addAll([1, 2, 3])
..add(2);
print(lists); // 输出:[1, 12312, 456, 1, 2, 3, 2]
// 删除
list.remove(1); // 根据值来删除数据
list.removeAt(0); // 根据索引来删除删除
list.removeRange(0, 2); // 删除集合的一段位置的数据(根据索引)
// 扩展运算符
final list3 = [...list];
print(list3); // 输出:[1, 12312, 456, 1, 2, 3, 2]
// 指定位置插入
list.insert(1, 123);
list.insertAll(2, [1, 2, 3]);
print(list); // 输出:[1, 123, 1, 2, 3, 12312, 456, 1, 2, 3, 2]
// 获取第一个元素
final firstItem = list.first; // 输出:1
// 查看数组是不是空
final isEmpty = list.isEmpty; // 输出:false
// 排序
final list2 = ['b21', 'a23'];
list2.sort(); // 会报错,sort 只能对相同类型的值进行排序,多类型的数组,不能直接排序
print(list2); // 输出:[a23, b21]
// 多类型数组排序, 需要预先声明一个排序函数
int typePriority(dynamic x) {
if (x is int) return 1; // 数字排第一
if (x is String) return 2; // 字符串排第二
if (x is bool) return 3; // 布尔值排第三
return 4;
}
final list4 = [1, true, null, '2'];
list4.sort((a, b) {
final aa = typePriority(a);
final bb = typePriority(b);
if (aa != bb) {
return aa.compareTo(bb);
}
return 0;
});
print(list4); // 输出:[1, 2, true, null]
list.forEach((item) => {print(item)}); // foreach 遍历
final doubleItem = list.map((item) => item * 2); // map 遍历 输出: (2, 1231212312)
final oneFMatch = list.firstWhere(
(item) => item == 1,
); // find 查找,只找第一个 输出:(1)
final oneLMatch = list.lastWhere(
(item) => item == 1,
); // find 查找,只找最后一个 输出:(1)
final oneMatch = list.where(
(item) => item == 1,
); // 全量查找,输出:(1) 条件为:item > 1 会报错。因为字符串和整形不可比较
final allMatch = list.every(
(item) => item is String,
); // 是否数组全部都满足条件,输出: false
final has_value = list.contains(1); // 查看是否包含一个值,输出:true
final index = list.indexOf(22); // 和js一样,返回索引不存在则返回-1,输出:-1
final reduceList = list.reduce((pre, item) => item); // 和 ts一样
// 累加
final sum = list.reduce((pre, item) {
if (item is int && pre is int) {
return item + pre;
}
return pre;
});
final sum2 = list.whereType<int>().fold(
0,
(pre, next) => pre + next,
); // 先将列表中的值提取物出来在累加, fold 可以预先声明一个初始值
print('$sum, $sum2');
// 指定位置填充
List list2 = [1, 2, 3, 4, 5, 6];
list2.fillRange(1, 4, [
"3",
"2",
]); // 将 start 和 end 之间的值填充为特定的值,输出;[1, [3, 2], [3, 2], [3, 2], 5, 6]
list2.fillRange(1, 2, "3"); // 输出:[1, 3, [3, 2], [3, 2], 5, 6]
// 列表与字符串互相转换
final q = list2.join("-"); // 输出:1-3-[3, 2]-[3, 2]-5-6
final w = q.split("-"); // 输出:[1, 3, [3, 2], [3, 2], 5, 6]
空类型
// dart 中空类型只有null,没有 undefined
final w = null;
print(w); // nll
// late 表示延后给变量赋值,保证一定会赋值
late String q;
// print(q); // 报错,变量还没赋值
q = '12312';
print(q);
var q2 = null;
String? qq;
if (q2 == null) { // 不能使用 is
qq ??= '12312'; // 如果 qq值为空,则给qq赋值
}
print(qq);
Map 类型
// new Map 初始化,和下面的是一样的。
Map<String, int> aa = {'aa': 12};
// 这个是 Map 自动类型推导
final k = {
'a': 1,
'b': 2,
'c': [1, true],
'w': [],
'aa': [1, 2, 3],
};
k['c'] = 123123; // final 支持随意修改、const 不支持修改
print(k);
(k['w'] as List).add('1'); // 必须类型断言
(k['w'] as List).add(2);
print(k);
// 存在浅拷贝的问题
final q = k['w'] as List;
q.length = 0;
print('$q ---- $k');
// 避免浅拷贝问题
final q2 = List.from(k['aa'] as Iterable); // 深拷贝赋值
q2.add('qqqq');
print('$q2 ---- $k');
// Map 集合(dynamic 类似于 ts 的 any 类型)
// 和 final users = [xxx] 一样
List<Map<String, dynamic>> users = [
{
'id': 1,
'name': 'Alice',
'tags': ['developer', 'runner'], // 嵌套列表
},
{'id': 2, 'name': 'Bob', 'tags': []},
];
// Map 取不存在的值
var ww = {'a': 123, 'b': '12312'};
// print(ww['bb']); // 输出: null
print(ww.containsValue('12312')); // 返回:该值是否存在Map中,输出;true
print(ww.containsKey('a')); // 返回;该key是否存在Map中,输出:true
ww['cc'] = '12312'; // 一次性添加一个值
ww.addAll({'qq': 'dasdas', 'ww': 'ww'}); // 一次性添加多个值
// print(ww); // 输出:{a: 123, b: 12312, cc: 12312, qq: dasdas, ww: ww}
// 深拷贝
final q5 = jsonDecode(jsonEncode(q1)); // json parser 转换
final q6 = {...q1}; // 单层拷贝,和js一样的,也可以用这个进行浅拷贝
Set类型
// Set 的使用
final qq = new Set();
qq.add(1);
qq.addAll([2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 6, 6, 6, 8, 8, 8]);
final www = qq.toList(); // 转换为 list,输出:[1,2,3,4];
final q2 = new Set()..addAll(www);
print(q2.toList()); // 在转换为数组,并去重; 输出:[1, 2, 3, 4, 6, 8]
// 一行代码实现
print(www.toSet().toList()); // 输出:[1, 2, 3, 4, 6, 8]
// set 的底层是根据地址来确定的
import "dart:convert";
final q1 = {'a': 1232};
final q3 = q1;
qq.add(q1);
qq.add(q3);
print(qq); // 输出:{1, 2, 3, 4, 6, 8, {a: 1232}},只会存在一个 {'a': 1232}, js 也是这样的。
类型判断
final users = [];
// 判断类型,比对类型 is 关键字
if (users is List) {
print(1);
} else {
print(2);
}
// 输出:1
链式操作符
// 级联操作符:执行操作,但忽略返回值,最终返回对象本身。
// 不用级联操作符
var button = Button();
button.text = 'Confirm';
button.color = 'Blue';
button.onClick(() => print('Clicked'));
// 使用级联操作符,始终获取的是第一步的返回值(第一步的值是动态的)。
var button =
Button() // 1. 创建对象(最初的接收者)
..text =
'Confirm' // 2. 在“1”的对象上执行赋值,完成后丢弃返回值,返回“1”
..color =
'Blue' // 3. 在“1”的对象上执行赋值,完成后丢弃返回值,返回“1”
..onClick(() => print('...')); // 4. 在“1”的对象上调用方法,完成后丢弃返回值,返回“1”
// 返回的第一步的值是动态的
final list = [];
final newList = list
..add(1)
..addAll([2, 3, 4]); // 输出:[1,2,3,4],最初list是空数组,最终输出的是最新的数组
函数
函数的基本使用
// 可选参数,表明参数可以传递,也可以不传递,通过 [] 来指定。当可选指定了类型,那么可选参数必须指定默认值
handler(String name, [int age = 10, String des = '默认值']) {
print('$name, $age, $des');
}
handler('张三', 10, '覆盖默认值...'); // 输出:张三, 10, 覆盖默认值...
// 命名参数,类似python 的元组方式传递。当命名指定了类型,那么命名参数必须指定默认值
handler2(String name, {int age = 10, String des = '默认值'}) {
print('$name, $age, $des');
}
handler2('张三', age: 10, des: '覆盖默认值'); // 输出:张三, 10, 覆盖默认值
// 方法作函数传递, String Function 表示声明 handler3的返回值类型
final String Function() handler3 = ([String name = 'xxx']) {
return '20';
};
// 在这里,不指定可选参数 fn 的类型,那么fn的类型就是 dynamic, 可以传递任意类型
// 当 fn 不传递值时,在函数内拿到的值就为 null
final handler4 = (String name, [fn]) {
if (fn != null) {
final age = fn();
print('name is $name, age is $age');
} else {
print('name is $name, age is null');
}
};
handler4('xxx'); // 输出:name is xxx, age is null
handler4('xxx', handler3); // 输出:name is xxx, age is 20
// handler4('xxx', 20); // 输出:Unhandled exception,因为 20 不是一个函数
函数的高级用法
// 箭头函数, 箭头函数不能有块语句,只能是一个表达式
final handler6 = () => 10;
// 箭头函数默认是会自动推导返回值类型,非要自定义类型也可以。
final int Function() handler7 = () => 10;
// 这样写,会将整个对象当作返回值返回
final handler8 = () => {'a': 123, 'b': 456};
print(handler8()); // 输出:{a: 123, b: 456}
// 另外一种写法,这样更标准
Map<String, dynamic> handler9() => {'name': 'xxx', 'age': 10};
print(handler9()); // 输出:{name: xxx, age: 10}
// 命名可选和位置可选,不能混在一起使用
// 命名可选
final handler10 = (String name, {int age = 10}) {
print('name is $name, age is $age');
};
// 位置可选
final handler11 = (String name, [int age = 10]) {
print('name is $name, age is $age');
};
// 自执行方法
((int n) {
print('12312 $n');
})(10); // 输出:12312 10
// 递归
deep(int n, int value) {
if (n <= 0) return value;
value *= value;
return deep(n - 1, value);
}
final k = deep(2, 2);
print(k); // 输出:16
// 闭包, 其作用域很gc都是和js是一样的
handler() {
var q = 1;
increment() {
q += 1;
};
getQ() {
return q;
}
return [increment, getQ];
}
var [increment, getQ] = handler();
increment(); // 累加
increment(); // 累加
print(getQ()); // 输出:3
类
基本使用
import "dart:convert";
import './lib/c.dart';
class Person {
String name = '';
int age = 0;
// 默认构造函数
Person(String name, int age) {
this.name = name;
this.age = age;
print('我是构造函数触发执行...');
}
// 命名构造函数,命名构造函数的名字可以随意指定,调用时需要指定构造函数的名字,可以存在多个
Person.named(String name, int age) {
this.name = name;
this.age = age;
print('我是命名构造函数触发执行...');
}
printInfo() {
print('name is ${this.name}, age is ${this.age}');
}
// 类似 python 的魔术方法,重写 toString 方法,打印对象时会调用该方法
@override
String toString() {
return {name: this.name, age: this.age}.toString();
}
}
Person p1 = new Person("张三", 10);
Person p2 = new Person.named("李四", 20);
p2.printInfo(); // 输出:name is 李四, age is 20
print(p1); // 输出:{name: 张三, age: 10}
进阶使用
// /lib/c.dart 文件
class Person2 {
String name;
int age;
String _priname = '私有属性';
// : name = "1232", age = 20 构造函数执行之前执行,给参数设置默认值
// Person2(String name, [age]) : name = "1232", age = 21 {
// this.name = name;
// if (age != null) {
// this.age = age;
// }
// print('我是构造函数触发执行...');
// }
// 类的自动赋值写法(命名可选,age 是可选传递的,但是必须指定默认值)
Person2(this.name, {this.age = 30}) {
print('我是构造函数触发执行...');
}
get info {
return this.name + this.age.toString();
}
set setName(String name) {
this.name = name;
}
}
// 私有属性,私有属性在类属性的前面加上一个下划线即可。但是要记得,这个私有属性类必须是外部文件引入的。
Person2 p3 = new Person2('王五');
print(p3.name); // 输出:王五, 无法访问:_priname
// getter 的使用,getter 是一个特殊的方法,可以像访问属性一样访问它。getter 方法前面需要加上 get 关键字。
print(p3.info); // 输出:王五30
// setter 的使用,setter 是一个特殊的方法,可以像访问属性一样访问它。setter 方法前面需要加上 set 关键字。
p3.setName = '赵六';
print(p3.info); // 输出:赵六30
// 类的自动赋值写法
class Person3 {
int age;
String name;
// 类的自动赋值写法, 因为上面的 name 没有给默认值,因此下面的第一个参数必须:this.name,不能 String name
Person2(this.name, {this.age = 30}) {
print('我是构造函数触发执行...');
}
}
静态属性
class Person5 {
int age = 10;
static String name = '1312';
Person5(this.age) {
print('我是构造函数触发执行...');
}
Person5.named(this.age) {
print('我是命名构造函数触发执行... ${this.age}');
}
static get info {
return name + new Person5(10).age.toString();
}
test() {
print('test function run...');
}
test2() {
print('test2 function run...');
}
}
print(Person5.info); // 输出:131210
类的继承
class Person6 extends Person5 {
String name = '12312';
// 继承
Person6(int age) : super(age) {} // 父类存在构造函数,并且存在 age 必填,子类的构造函数必须调用 super(父类的构造函数) 。使用 @override 也不行
Person6.fun(this.name, int age)
: super.named(age) {} // name 参数给自身的 name,age 参数给父类的 age。必须调用 super 传递 age 参数,因为父类的构造函数存在 age 必填
// 执行父类方法, 和js 一样,通过 super 关键字来调用父类方法
handler() {
super.test();
}
// 重写类
@override
test2() {
print(123123);
}
}
new Person6.fun('aa', 20); // 输出:我是命名构造函数触发执行... 20
new Person6(10).handler(); // 输出:test function run...
new Person6(10).test2(); // 输出:123123
抽象类 abstract
抽象类是定义接口标准的,不能被实例化(直接调用),只能被继承。dart里面没有 interface 定义接口,只能通过 abstract 抽象类来声明。 不同的是,抽象类中,是可以定义公共的方法的,这样的话,子类在继承时,就可以使用公共的方法了
abstract class Animal {
String name;
Animal(this.name);
eat();
publicinfo() {
print(123123);
}
}
class Dog extends Animal {
Dog(super.name);
@override
eat() {
print('狗吃骨头');
}
}
Dog d1 = new Dog('旺财');
d1.eat(); // 输出:狗吃骨头
d1.publicinfo(); // 公共方法调用,输出:123123
late, required 关键字在类中的使用
class Person10 {
late String name; // 表示稍后 name 赋值
int age;
String? des; // 当将参数作为允许为空的时候,则这个参数不在需要设置默认值
// Person10(this.age); // 这样肯定是可行的,构造函数执行赋值
// Person10({this.age = 20}); // 将 age 作为可选参数,那么必须要给定默认值
Person10({required this.age}); // 给参数加上 required 之后,则表示这个参数是必须要传递的
}
Person10 p10 = Person10(age: 20);
print(p10);
项目中使用(类参数的初始化)
class Person11 {
String name;
int age;
Person11({required this.name, required this.age}); // 使用 required 来标识赋值
}
泛型
与js的泛型不同的是,类型是写在前面的(T x)。而不是后面(x: T)
// 第一个T表示的是函数返回值
T handler1<T>(T value) {
return value;
}
print(handler1<String>('我是泛型参数'));
// 泛型类
abstract class FileHandler<T> {
readFile(T path) {
return T;
}
writeFile(T path) {
return T;
}
}
class File<T> extends FileHandler<T> {
@override
T readFile(T path) {
return path;
}
@override
T writeFile(T path) {
return path;
}
}
File f1 = new File<String>();
print(f1.readFile('我是泛型类参数'));
print(f1.writeFile('我是泛型类参数2'));
可空类型(“ ?”)
String? name = '12312';
name = null; // 不加 ? 赋值会报错
List<String>? l2 = ['1'];
l2 = null; // 不加 “?” 赋值会报错
dynamic handler2(int num) {
if (num > 1) {
return true;
} else if (num > 2) {
return '12312';
} else if (num > 3) {
return {};
} else {
return null;
}
}
print(handler2(0));
类型断言
String? name2 = '12312';
name2 = null;
// 在使用类型断言时,一定要使用try cache 包裹
try {
print(name2!.length); // 类型断言,告诉编译器name2一定是有值的。
} catch (e) {
print(e);
}
CONST 性能优化
首先确定,const 是编译时赋值、final 是运行时赋值
import "dart:core" show identical; // 内存地址判断
var v1 = new Object();
var v2 = new Object();
print(identical(v1, v2)); // false 虽然都是一样的对象,但是在内存中是两块内存
var v3 = const Object();
var v4 = const Object();
print(identical(v3, v4)); // true 一样的对象,在相同的上下文运行时环境中,共享一块内存
var l10 = const [1, 2, 3];
var l11 = const [1, 2, 3];
print(identical(l10, l11)); // true 一样的数据,共享一块内存
var l12 = const [1, 2, 3];
var l13 = const [1, 2, 4];
print(identical(l12, l13)); // false 虽然修饰了,但是数据不一样,不共享一块内存
const 在flutter中的使用是非常广泛的,例如,初始化100个相同的列表,是使用 const 就可以只使用一块内存空间。 在flutter 中,使用 const 声明的组件,在组件更新时,const声明的组件,不会重新渲染。
class Person11 {
// 常量构造函数的中的类参数,必须使用 final 修饰
final String name;
final int age;
const Person11({required this.name, required this.age}); // 常量构造函数
}
Person11 p11 = const Person11(name: '张三', age: 20);
Person11 p12 = const Person11(name: '张三', age: 20);
Person11 p13 = const Person11(name: '张三', age: 22);
print(identical(p11, p12)); // true 对类的构造函数为常量构造函数时,共享一块内存空间
print(identical(p11, p13)); // false 对类的构造函数为常量构造函数时,但是数据不一样,则不共享一块内存空间
包管理器
https://pub.dev 是 js 界的 npm pub 是 js 界的包管理器名称 pubspec.yaml 是 js 界的 package.json, 添加 dependencies 后,执行 pub get 来安装库
先编写 pubspec.yaml 包依赖文件
name: xxx
description: xxxxx
dependencies:
xxx:xxxx
安装包
pub get xxx // 安装包
项目内引入包
// 全量引入
import xxxx;
// 局部引入
import xxx show xxx //(倒入包中的某些模块)
import xxx hide xxx //(隐藏包中的某些模块)
// 引入冲突
import xxx as xxx //(使用别名,避免两个包中存在相同的函数)