1. 认识DartGoogle为Flutter选择了Dart语言已经是既

发布于:2025-04-20 ⋅ 阅读:(62) ⋅ 点赞:(0)

1. 认识Dart

Google为Flutter选择了Dart语言已经是既定的事实,无论你多么想用你熟悉的语言,比如JavaScript、TypeScript、ArkTS等来开发Flutter,至少目前都是不可以的。

  • Dart 是由谷歌开发的计算机编程语言,它可以被应用于 Web/服务器/移动应用和物联网等领域开发。
  • Dart 也是 Flutter 的基础,Dart 作为 Flutter 应用程序的编程语言,为驱动应用运行提供了环境。
  • 因此, 要学习Flutter, 则首先得会Dart, Dart 目前最新稳定版本:v3.4.0

好消息:如果你会 JavaScriptTypescriptJava 中任何一门语言,你将很快且很容易就能学会Dart的使用。

2. 搭建Dart开发环境

为什么需要安装Dart呢?

事实上如果提前安装了Flutter SDK,它已经内置了Dart了,我们完全可以直接使用Flutter去进行Dart的编写并且运行。

但是,如果想单独学习Dart,并且运行自己的Dart代码,最好去安装一个Dart SDK。

要在本地开发Dart程序, 首先需要安装Dart SDK

官方文档: Get the Dart SDK | Dart , 中文文档: Dart 编程语言主页 | Dart 中文文档 | Dart

无论是什么操作系统,安装方式都是有两种:通过工具安装直接下载SDK,配置环境变量

a.通过工具安装

  • Windows可以通过Chocolatey
  • macOS可以通过homebrew
  • 具体安装操作官网网站有详细的解释

b.直接下载SDK,配置环境变量

  • 下载地址:https://dart.dev/tools/sdk/archive
  • 我采用了这个安装方式。
  • 下载完成后,根据路径配置环境变量即可。

1. Windows环境安装Dart
1.1 下载压缩包, 然后解压放在任意盘符下(注意不要是中文目录下)

1.2 找到bin目录, 复制完整路径, 配置环境变量

1.3 cmd 窗口执行 dart --version 看到dart-sdk的版本代表OK

2. Mac环境安装Dart
  • macOS支持的架构: x64、ARM64

2.1 下载并解压Dart SDK,放到电脑的某个目录下面(不要是中文)

2.2 配置环境变量

打开终端,‌使用文本编辑器(‌如vinano)‌编辑~/.zshrc~/.bash_profile文件。‌在文件的末尾添加以下行,‌将Dart SDK的路径添加到系统的PATH中:‌

在终端中运行vim ~/.zshrc命令

export PATH="/path/to/dart/sdk/bin:$PATH"

注意:

  1. /path/to/dart/sdk/bin替换为你解压Dart SDK的实际路径。‌
  2. 保存并关闭文件:‌保存并关闭文本编辑器(按下esc,输入:wq)
2.3 使环境变量生效

在终端中运行source ~/.zshrc命令,‌使新的环境变量设置立即生效。‌

2.4 验证安装

运行dart --version命令来检查Dart的版本信息,‌如果正确显示版本信息,‌则表示Dart已经成功安装。‌

3. Dart初体验

3.1 VSCode中安装常用插件
  • Dart插件:可以帮助我们在VSCode中更加高效的编写Dart代码
    提供了友好的代码提示,代码高亮,以及代码的重构、运行和重载

  • Flutter插件:为Flutter开发准备的

  • Code Runner:可以点击右上角的按钮快速运行代码

3.2 第一个Dart程序
  1. 创建dart文件
  • dart文件名必须以 .dart 结尾:01-第一个dart程序.dart

  1. 编写dart代码

需求:打印字符串 'hello dart!'

// 程序的入口:main函数
void main() {
  // 需求:打印字符串 'hello itcast'
  print('hello itcast'); // hello itcast
}
  1. 执行dart代码
  • 方式一:终端中执行:终端打开dart文件所在目录
dart 01-dart初体验.dart

  • 方式二:
    • VSCode中执行

    • VSCode中查看代码执行结果

小结:

1、Dart语言的入口也是main函数,并且必须显示的进行定义;

2、Dart的入口函数main是没有返回值的;

3、定义字符串的时候,可以使用单引号或双引号;

4、每行语句必须使用分号结尾,很多语言并不需要分号,比如JavaScript;

4. Dart基础语法

4.1 变量和常量(存储数据)
4.1.1 变量(存储并修改数据)

需求:存储一个姓名和年龄并修改年龄?

实现:变量关键字:var

要点:

  1. 声明变量:var 变量名 = 表达式;
  2. 修改变量:变量名 = 新值;
  3. 类型推断:var关键字声明的变量支持类型推断,修改变量时会检查之前存储数据的类型
void main() {
  // 1. 声明变量:var 变量名 = 表达式;
  var name = 'itheima';
  print(name); // itheima
  var age = 17;
  print(age); // 17

  // 2. 修改变量:变量名 = 新值;
  age = 18;
  print(age); // 18

  // 3. 类型推断:var关键字声明的变量支持类型推断,修改变量时会检查之前存储数据的类型
  // 报错:A value of type 'String' can't be assigned to a variable of type 'int'.
  // age = 'itcast';
}
4.1.2 常量(存储不变的数据)

需求:存储不可被修改的数据

实现:常量关键字:constfinal

区别:final是运行时常量,值在运行时赋值;const是编译期常量,值在编译时赋值;

void main() {
  // 1. const声明常量
  const num1 = 10;
  print(num1); // 10
  // 报错:Constant variables can't be assigned a value.
  // num1 = 20;

  // 2. final声明常量
  final num2 = 30;
  print(num2); // 30
  // 报错:The final variable 'num2' can only be set once.
  // num2 = 40;

  // 3. const和final的区别:
  var x = 1;
  var y = 2;

  // 3.1 final:运行时常量,值在运行时赋值
  final ret1 = x + y;
  print(ret1);

  // 3.2 const:编译期常量,值在编译时赋值
  // 报错:Const variables must be initialized with a constant value.
  // const ret2 = x + y;
  const ret3 = 1 + 2;
}

注意: const 和 final的区别

final:运行时常量,值在运行时赋值

const:编译期常量,值在编译时赋值

4.2 数据类型(可存储数据类型)
4.2.1 num(数字)

需求:存储并修改整数和小数

实现:关键字:numintdouble

注意点:

num类型的变量既可以存整数也可以存小数

int类型的值可以赋值给double类型的变量,但是double类型的值不能赋值给int类型的变量

void main() {
  // 1. num类型 (整数、小数)
  num n1 = 10;
  print(n1); // 10
  n1 = 10.1;
  print(n1); // 10.1

  // 2. int类型 (整数)
  int n2 = 20;
  print(n2); // 20
  // 测试:将小数赋值给int
  // 报错:A value of type 'double' can't be assigned to a variable of type 'int'.
  // n2 = 20.2;

  // 3. double类型 (小数)
  double n3 = 30.3;
  print(n3); // 30.3
  // 测试:将整数赋值给double
  n3 = 30;
  print(n3); // 30.0
}
4.2.2 String(字符串)

Dart字符串是UTF-16编码单元的序列。您可以使用单引号或双引号创建一个字符串

需求:声明字符串,修改字符串,拼接字符串

注意:模板字符串支持运算

实现:关键字:String

void main() {
  // 1. 定义字符串
  String name = '张三';
  String gender = "男";

  // 2. 修改字符串
  name = '李四';
  gender = '女';

  print(name);
  print(gender);

  // 3. 字符串拼接
  double money = 99991.99;
  String intro = '姓名:$name,性别:$gender,钱包:${money * 100}';
  print(intro);

  // 4. 字符串换行
  String tag = '''
  通知
  明天天气不好
  注意放假
  ''';
  print(tag);
}
4.2.3 bool(布尔)

需求:存储并修改真或假

实现:关键字:bool

void main() {
  // 登录成功:true
  bool isLogin = true;
  print(isLogin); // true

  // 登录失败:false
  isLogin = false;
  print(isLogin); // false
}
4.2.4 List(列表)

需求:使用一个变量有序的存储多个值

实现:列表(数组)关键字:List

  • 定义列表 List 变量名 = [元素1, 元素2, ..., 元素n];
    • 1.1 需求:按序存储数字 1 3 5 7
    • 1.2 需求:按序存储字符串 '居家' '美食' '服饰'
    • 1.3 列表中可以存储任意类型的数据
void main() {
  // 1. 定义列表:List 变量名 = [元素1, 元素2, ..., 元素n];
  // 1.1 需求:按序存储数字 1 3 5 7
  List nums = [1, 3, 5, 7];
  print(nums);
  
  // 1.2 需求:按序存储字符串 '居家' '美食' '服饰'
  List categories = ['居家', '美食', '服饰'];
  print(categories);
  
  // 1.3 列表中可以存储任意类型的数据
  List ret = [
    18,
    18.8,
    '美食',
    true,
    ['itcast', 10],
    {}
  ];
  print(ret);
}
  • 使用列表:查改增删
    • 查询列表长度
    • 查询指定的元素
    • 修改:列表[索引] = 新值
    • 新增:列表.add(新元素)、列表.addAll(新列表)
    • 指定位置添加:列表.insert(索引, 内容');
    • 删除:使用元素删除、使用索引删除
    • 遍历列表:读取出列表中每一个元素remove,removeAt
// 2. 使用列表:查改增删
// 2.1 查询:
// 查询列表的长度(列表内部元素的个数)
int len = categories.length;
print(len); // 3
// 查询指定的元素: 列表[索引]
String category = categories[1];
print(category); // 美食

// 2.2 修改:列表[索引] = 新值
categories[1] = '美食家';
print(categories); // [居家, 美食家, 服饰]

// 2.3 新增:
// 一次新增一个元素:列表.add(新元素)
categories.add('母婴');
print(categories); // [居家, 美食家, 服饰, 母婴]
// 一次新增多个元素:列表.addAll(新列表)
categories.addAll(['办公', '生鲜']);
print(categories); // [居家, 美食家, 服饰, 母婴, 办公, 生鲜]
// 在指定位置新增元素
categories.insert(2, '教育');
print(categories); // [居家, 美食家, 教育, 服饰, 母婴, 办公, 生鲜]

// 2.4 删除
// 使用元素删除
categories.remove('生鲜');
print(categories); // [居家, 美食家, 教育, 服饰, 母婴, 办公]
// 使用索引删除
categories.removeAt(4);
print(categories); // [居家, 美食家, 教育, 服饰, 办公]
  • 遍历列表:读取出列表中每一个元素
// 3. 遍历列表:读取出列表中每一个元素
// element: 列表中读取出来的每一个元素
categories.forEach((element) {
  print(element);
});
4.2.5 Map(字典)

需求:声明键值对的集合,并使用与之相关联的键从中获取值(类似JS中的对象)

实现:字典关键字:Map

  1. 存储商品分类的编号 和 名称
  2. 对字典数据进行查改增删

2.1 查询:字典[key]

2.2 修改:字典[key] = 新值

2.3 新增:字典[新key] = 新值 (注意:key必须是当前字典中不存在的key,如果key已存在就是修改)

2.4 删除:remove(key) (注意:如果key不存在,不会报错,也不会执行删除操作)

  1. 遍历字典
  • 定义字典语法
Map 变量名 = {
    '键1': 值1,
    '键2': 值2,
    ...,
};
  • 例子:存储商品分类的编号 和 名称
void main() {
  // 1. 例子:存储商品分类的编号 和 名称
  Map category = {
    'id': 1,
    'name': '居家',
  };
  print(category); // {id: 1, name: 居家}
}
  • 使用字典:查改增删
// 2. 使用字典:查改增删
// 2.1 查询:字典[key]
String name = category['name'];
print(name); // 居家

// 2.2 修改:字典[key] = 新值
category['name'] = '美食';
print(category); // {id: 1, name: 美食}

// 2.3 新增:字典[新key] = 新值
// 注意:key必须是当前字典中不存在的key,如果key已存在就是修改
category['price'] = 199.9;
print(category); // {id: 1, name: 美食, price: 199.9}

// 2.4 删除:remove(key)
// 注意:如果key不存在,不会报错,也不会执行删除操作
category.remove('name');
print(category); // {id: 1, price: 199.9}
  • 遍历字典
// 3. 遍历字典
category.forEach((key, value) {
  print('$key -- $value');
  // id -- 1
  // price -- 199.9
});
4.2.6 Dart空安全机制

如何尽早的发现并解决null带来的异常?

Dart提供了健全的空安全机制,默认所有的变量都是非空的,如果某个变量得到了一个null,则代码在编译期就会报错

  1. 无法正常执行的代码:在代码编译期就会报错
  2. 解决办法:使用 ? 显示的指定变量可以为空
  3. 使用可以为空的变量
void main() {
  // 1. 可以正常执行的代码
  String name1 = 'itcast';
  print(name1.length);

  // 2. 无法正常执行的代码:在代码编译期就会报错
  // String name2;
  // The non-nullable local variable 'name2' must be assigned before it can be used.
  // 报错:Null check operator used on a null value
  // print(name2.length);

  // 3. 解决办法:使用 ? 显示的指定变量可以为空
  String? name3 = null;
  print(name3);

  // 4. 使用可以为空的变量
  // name3? : 表示非空检查,如果name3为空,不去调用属性或方法,如果name3不为空,就去调用属性或方法
  print(name3?.length);
}
4.3 运算符(数据如何做运算)
4.3.1 算术运算符

如何对数字做加减乘除等运算?

多了取整 ~/

void main() {
  int n1 = 10;
  int n2 = 3;

  // 加 +
  print(n1 + n2); // 13
  // 减 -
  print(n1 - n2); // 7
  // 乘 *
  print(n1 * n2); // 30
  // 除 /
  print(n1 / n2); // 3.3333333333333335

  // 取整:取除法结果的整数部分 ~/
  print(n1 ~/ n2); // 3
  // 取模:取除法结果的余数 %
  print(n1 % n2); // 1

  // 案例:计算购物车商品总价格:商品A一件,每件289.0元;商品B二件,每件39.0元
  double total = 289.0 + (2 * 39.0);
  print(total); // 367.0
}
4.3.2 赋值运算符(同TS)

如何对数字做赋值运算?

void main() {
  // 等于 =
  int n1 = 10;

  // 加等于 +=
  // n1 = n1 + 5;
  n1 += 5;
  print(n1); // 15

  // 减等于 -=
  n1 -= 5;
  print(n1); // 10

  // 乘等于 *=
  n1 *= 5;
  print(n1); // 50

  // 除等于 /=
  // 注意:double类型的数据才能做除等于的操作
  // A value of type 'double' can't be assigned to a variable of type 'int'.
  // n1 /= 5;

  double n2 = 50;
  n2 /= 5;
  print(n2); // 10.0

  // 取余等于 %=
  int n3 = 10;
  n3 %= 3;
  print(n3); // 1

  // 自增:在原有数值上加1 ++
  int a = 10;
  a++;
  print(a); // 11

  // 自减:在原有数值上减1 --
  int b = 20;
  b--;
  print(b); // 19
}
4.3.3 比较运算符

如何比较数字大小?

没有 === 和 !== 运算符

void main() {
  int n1 = 10;
  int n2 = 20;

  // 大于 >
  print(n1 > n2); // false
  // 小于 <
  print(n1 < n2); // true

  // 大于等于 >=
  print(n1 >= n2); // false
  // 小于等于 <=
  print(n1 <= n2); // true

  // 等于 ==
  print(n1 == n2); // false
  print('itcast' == 'itheima'); // false
  // 不等于 !=
  print(n1 != n2); // true
  print('itcast' != 'itheima'); // true
}
4.3.4 逻辑运算符(同TS)

如果表示数据之间的逻辑关系?

void main() {
  // 年龄
  int age = 33;
  // 工作年限
  int years = 10;

  // 1. 逻辑与:一假则假
  // 年龄大于28岁,并且工作年限大于4年
  bool ret1 = age > 35 && years > 4;
  print(ret1); // false

  // 2. 逻辑或:一真则真
  // 年龄大于23岁,或者工作年限大于2年
  bool ret2 = age > 35 || years > 2;
  print(ret2); // true

  // 3. 逻辑非:真变假,假变真
  print(!true); // false
  print(!false); // true

  // 工作年限不小于9年
  bool ret3 = years >= 9;
  // bool ret3 = !(years < 9);
  print(ret3); // true
}

4.4 流程控制(选择或重复执行)
4.1.1 if分支语句(同TS)

单分支、双分支、多分支

void main() {
  // 1. if单分支语句
  // 准备高考成绩,如果分数大于等于700分,则输出 '恭喜考入黑马程序员'
  int score1 = 699;
  if (score1 >= 700) {
    print('恭喜考入黑马程序员');
  }

  // 2. if双分支语句
  // 准备高考成绩,如果分数大于等于700分,则输出 '恭喜考入黑马程序员',反之,则输出 '继续努力'
  int score2 = 699;
  if (score2 >= 700) {
    print('恭喜考入黑马程序员');
  } else {
    print('继续努力');
  }

  // 3. if多分支语句
  // 根据学生分数划分学生成绩等级:
  // 优秀:分数大于等于90分
  // 良好:分数小于90分,且大于等于80分
  // 中等:分数小于80分,且大于等于60分
  // 不及格:分数小于60分
  int score3 = 58;
  if (score3 >= 90) {
    print('优秀');
  } else if (score3 >= 80) {
    print('良好');
  } else if (score3 >= 60) {
    print('中等');
  } else {
    print('不及格');
  }
}

4.1.2 三元运算符(同TS)

简化简单的if双分支语句

void main() {
  // 需求:准备高考成绩,如果分数大于等于700分,则输出 '恭喜考入黑马程序员',反之,则输出 '继续努力'
  // 思考:以下代码可以简化吗?
  int score1 = 699;
  // if (score1 >= 700) {
  //   print('恭喜考入黑马程序员');
  // } else {
  //   print('继续努力');
  // }

  // 1. 使用三元运算符简化if双语句:条件表达式 ? 表达式1 : 表达式2
  score1 >= 700 ? print('恭喜考入黑马程序员') : print('继续努力');

  // 2. 思考:以下代码适合使用三元运算符改写吗?
  int score2 = 88;
  if (score2 >= 90) {
    print('优秀');
  } else if (score2 >= 80) {
    print('良好');
  } else if (score2 >= 60) {
    print('中等');
  } else {
    print('不及格');
  }
}

4.1.3 switch case 语句(同TS)

如果分支很多,且条件是判断相等,则switch case 语句性能比 if 分支语句要好

void main() {
  // 根据订单状态,打印出订单状态描述信息
  // 订单状态:1为待付款、2为待发货、3为待收货、4为待评价
  int orderState = 3;
  switch (orderState) {
    case 1:
      print('待付款');
      break;
    case 2:
      print('待发货');
      break;
    case 3:
      print('待收货');
      break;
    case 4:
      print('待评价');
      break;
    default:
      print('其他');
  }
}

4.1.4 循环语句(同TS)

如何让代码重复执行?

循环语句:while循环for循环

void main() {
    // 1. while循环
  // 重复打印10次 '月薪过万'
  int n = 0;
  while (n < 10) {
    print('$n -- 月薪过万');
    n++;
  }

  // 2. for循环
  // 重复打印5次 '李白姓白'
  for (var i = 0; i < 5; i++) {
    print('$i -- 李白姓白');
  }

  // 3. 使用循环遍历列表
  // 3.1 遍历列表:for循环
  List categories = ['居家', '美食', '服饰'];
  for (var i = 0; i < categories.length; i++) {
    String name = categories[i];
    print(name);
  }

  // 3.2 遍历列表:for ... in
  for (var item in categories) {
    // item就是遍历出来的元素
    print(item);
  }

  // 4. 终止循环
  // 4.1 break:中断整个循环
  for (var i = 0; i < 5; i++) {
    if (i == 2) {
      // 吃到第三个苹果发现了虫子,剩下的苹果没胃口都不吃了
      break;
    }
    print('我把第 ${i + 1} 个苹果吃了');
  }

  // 4.2 continue:跳过本次循环直接进入下一次循环
  for (var i = 0; i < 5; i++) {
    if (i == 2) {
      // 吃到第三个桃子发现了虫子,第三个桃子不吃了,剩下的桃子接着吃
      continue;
    }
    print('我把第 ${i + 1} 个桃子吃了');
  }
}
4.5 基础语法 - 综合案例

需求:计算购物车数据中,被勾选商品的总价

void main() {
  // 准备购物车数据
  List carts = [
    {"count": 2, "price": 10.0, "selected": true},
    {"count": 1, "price": 30.0, "selected": false},
    {"count": 5, "price": 20.0, "selected": true}
  ];

  // 记录总金额
  double totalAmount = 0.0;
  // 遍历购物车数据
  carts.forEach((element) {
    // 读取商品的勾选状态
    bool selected = element['selected'];
    // 如果商品被勾选 ,读取该商品的单价和数量,并计算价格小计
    if (selected) {
      double amount = element['count'] * element['price'];
      // 累加价格小计,计算总价
      totalAmount += amount;
    }
  });

  print(totalAmount);
}

五、函数(复用代码)

5.1 函数的定义
  1. 定义函数:无参数无返回值函数
  2. 定义函数:有参数有返回值函数
  3. 函数的特点:
  • 返回值类型和参数类型是可以省略的
void main() {
  // 2. 调用无参数无返回值函数
  func();
    
  // 4.调用有参数有返回值函数 
  int ret = sum(10, 20);
  print(ret); // 30
}

// 1. 定义函数:无参数无返回值函数
void func() {
  print('这是一个无参数无返回值函数');
}

// 3. 定义函数:有参数有返回值函数
// 需求:定义函数,计算任意两个整数的和,并返回计算结果
int sum(int a, int b) {
  int ret = a + b;
  return ret;
}
  • 函数的特点:
    • 返回值类型和参数类型是可以省略的
void main() {
  // 2. 调用无参数无返回值函数
  // func();
  
  // 4. 调用有参数有返回值函数
  int ret = sum(10, 20);
  print(ret); // 30
}

// 1. 定义函数:无参数无返回值函数
func() {
  print('这是一个无参数无返回值函数');
}

// 3. 定义函数:有参数有返回值函数
// 需求:定义函数,计算任意两个整数的和,并返回计算结果
// 特点2:返回值类型和参数类型是可以省略的
sum(a, b) {
  int ret = a + b;
  return ret;
}
5.2 函数的参数

函数的参数可以分为:必传参数(位置参数)、可选参数(关键字参数)

注意点:必传参数不能为空,可选参数可以为空,且参数都可以设置默认值

void main() {
  printString('张三丰');
  printString('李四', age: 18);
  printString('王五', location: '昌平区');
  printString('赵六', age: 18, location: '海淀区');
}

/**
 * name:必传参数
 * age:可选参数
 * location:可选参数,并有默认值
 */
void printString(String name, {int? age, String? location = '昌平区'}) {
  print('$name - $age - $location');
}

5.3 函数对象

函数可以作为对象赋值给其他变量

函数可以作为参数传递给其他函数

void main() {
  // 1.2 定义一个变量接收函数
  // var f = funcDemo1;
  Function f = funcDemo1;
  f();

  // 2.2 函数作为参数
  funcDemo2(funcDemo3);
}

// 1.1 函数可以作为对象赋值给其他变量
void funcDemo1() {
  print('funcDemo1');
}

// 2.1 函数可以作为参数传递给其他函数
void funcDemo2(Function func) {
  // 调用外界传入的函数
  func();
}

// 定义作为参数的函数: 把funcDemo3传入到funcDemo2
void funcDemo3() {
  print('funcDemo3');
}

5.4 匿名函数
  1. 匿名函数赋值给变量,并调用
  2. 可以作为参数传递给其他函数去调用(回调函数)
void main() {
  // 匿名函数
  // 1. 匿名函数赋值给变量,并调用
  Function f = () {
    print('这是一个匿名函数');
  };
  f();

  // 2. 可以作为参数传递给其他函数去调用(回调函数)
  funcDemo(() {
    print('这个匿名函数是个参数');
  });
}

// 定义一个接收函数作为参数的函数
void funcDemo(Function func) {
  func();
}

5.5 箭头函数

当函数体只有一行代码时,可以使用箭头函数简写

void main() {
  int ret1 = sum1(10, 20);
  print(ret1);

  int ret2 = sum2(30, 40);
  print(ret2);
}

// 思考:以下代码可以简写吗?
sum1(a, b) {
  return a + b; // 函数体只有一行代码
}

// 箭头函数简写函数体:简写只有一行代码的函数体
sum2(a, b) => a + b;

5.6 函数 - 综合案例

需求:计算购物车中商品是否全选

void main() {
    // 准备购物车数据
  List carts = [
    {"count": 2, "price": 10.0, "selected": true},
    {"count": 1, "price": 30.0, "selected": false},
    {"count": 5, "price": 20.0, "selected": true}
  ];

  // 调用封装的函数
  bool isSelectedAll = getSelectedState(carts);
  if (isSelectedAll) {
    print('全选');
  } else {
    print('非全选');
  }
}

// 核心逻辑:只要有任何一个商品是未勾选的,那么就是非全选
bool getSelectedState(List carts) {
  // 购物车初始的状态:假设默认是全选
  bool isSelectedAll = true;

  carts.forEach((element) {
    bool selected = element['selected'];
    // 核心代码:只要有任何一个商品是非勾选的,则购物车就是非全选
    if (selected == false) {
      isSelectedAll = selected;
    }
  });
    // 返回是否全选结果
  return isSelectedAll;
}

六、类(面向对象编程)

6.1 类的定义

需求:定义Person类,属性:名字和年龄,方法:吃饭

void main() {
  // 创建Person对象
  Person person = Person();
  // 1.1 属性赋值
  person.name = 'itheima';
  person.age = 17;
  // 1.2 读取属性
  print(person.name); // itheima
  print(person.age); // 17
  // 2.1 调用方法
  person.eat(); // 我是干饭人
}

// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {
  // 1. 属性
  String? name;
  int? age;

  // 2. 方法
  void eat() {
    print('我是干饭人');
  }
}

存在问题: 我们new一个对象时, 不能直接给对象绑定属性?

答案: 借助构造函数

6.2 构造函数
6.2.1 默认构造函数

无参数,默认隐藏

void main() {
  // 使用默认的构造函数创建对象
  Person person1 = Person();
  // 属性赋值
  person1.name = '张三';
  person1.age = 18;
  // 读取属性
  print(person1.name); // 张三
  print(person1.age); // 18
  // 调用方法
  person1.eat();
}

// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {
  // 默认的构造函数(无参数,默认隐藏)
  Person() {
    print('我是默认的构造函数');
  }

  // 属性
  String? name;
  int? age;

  // 方法
  void eat() {
    print('我是干饭人');
  }
}

6.2.2 自定义与类同名构造函数

自定义与类同名的构造函数时,可以有参数

注意点:与类同名的构造函数只能有一个,如果自定义了该类名构造函数,那么默认的构造函数就失效

void main() {
  // 使用自定义与类同名构造函数创建对象
  Person person2 = Person('李四', 19);
  // 读取属性
  print(person2.name); // 李四
  print(person2.age); // 19
  // 调用方法
  person2.eat();
}

// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {
  // 自定义与类同名构造函数:可以有参数
  // Person(String name, int age) {
  //   this.name = name;
  //   this.age = age;
  // }

  // 简写自定义与类同名构造函数:自定义与类同名构造函数时,如果函数的参数和类的属性同名可以简写
  Person(this.name, this.age);

  // 属性
  String? name;
  int? age;

  // 方法
  void eat() {
    print('我是干饭人');
  }
}

6.2.3 命名构造函数

实际开发中, 经常会发现这么一种写法: 类名.方法(参数...), 然后返回一个实例化对象, 这种写法在Dart中被称为命名构造函数

void main() {
  // 使用命名构造函数创建对象
  Person person3 = Person.withInfo('王五', 20);
  // // 读取属性
  print(person3.name); // 王五
  print(person3.age); // 20
  // 调用方法
  person3.eat();
}

// 定义Person类,属性:名字和年龄,方法:吃饭
class Person {
  // 定义命名构造函数
  // Person.withInfo(String name, int age) {
  //   this.name = name;
  //   this.age = age;
  // }
  
  // 简写命名构造函数
  Person.withInfo(this.name, this.age);

  // 属性
  String? name;
  int? age;

  // 方法
  void eat() {
    print('我是干饭人');
  }
}

6.3 私有属性和方法

公有属性和方法:供类自身或者其他外部文件和类使用的属性和方法

私有属性和方法:仅供自身使用的属性和方法,其他外部文件和类无法访问

class Dog {
  // 公有属性
  String? name;
  // 私有属性
  int? _age;

  // 公有方法
  void eat() {
    print('dog eat');
  }

  // 私有方法
  void _run() {
    print('dog run');
  }
}

其他dart文件中使用:28-类-私有属性和方法2-使用.dart

// 导入Dart文件、库
import 'lib/Dog.dart';

void main() {
  // 创建Dog对象
  Dog dog = Dog();

  dog.name = '旺财';
  print(dog.name);

  // 私有属性调用失败
  // print(dog._age);

  dog.eat();
  // 私有方法调用失败
  // dog._run();
}

6.4 继承(extends)
  • [思考]:如下定义的的两个类ManWoman是否有重复的部分,是否可以优化?
void main() {
  // 创建男人对象
  Man man = Man('李雷', 13);
  print(man.name);
  man.eat();

  // 创建女人对象
  Woman woman = Woman('韩梅梅', 14);
  print(woman.name);
  woman.eat();
}

/// 男人类
class Man {
  Man(this.name, this.age);

  String? name;
  int? age;

  void eat() {
    print('$name -- eat');
  }
}

/// 女人类
class Woman {
  Woman(this.name, this.age);

  String? name;
  int? age;

  void eat() {
    print('$name -- eat');
  }
}
  • 使用继承优化代码:继承的基本使用

[思考]:如下定义的的两个类Man和Woman是否有重复的部分,是否可以优化?
[解决]:定义父类Person,用于封装公共的属性和方法,ManWoman类作为子类去继承父类Person的属性和方法

void main() {
  // 创建男人对象
  Man man = Man('李雷', 13);
  print(man.name);
  man.eat();

  // 创建女人对象
  Woman woman = Woman('韩梅梅', 14);
  print(woman.name);
  woman.eat();
}

/// 人类:父类
class Person {
  Person(this.name, this.age);

  String? name;
  int? age;

  void eat() {
    print('$name -- eat');
  }
}

/// 男人类:子类
class Man extends Person {
  // 定义子类构造函数
  // Man(String name, int age) : super(name, age);
  Man(super.name, super.age);
}

/// 女人类:子类
class Woman extends Person {
  // 定义子类构造函数
  Woman(super.name, super.age);
}
  • 提示:子类中可以重写父类的方法
void main() {
  // 创建男人对象
  Man man = Man('李雷', 13);
  print(man.name);
  man.eat();

  // 创建女人对象
  Woman woman = Woman('韩梅梅', 14);
  print(woman.name);
  woman.eat();
}

/// 人类:父类
class Person {
  Person(this.name, this.age);

  String? name;
  int? age;

  void eat() {
    print('$name -- eat');
  }
}

/// 男人类:子类
class Man extends Person {
  // 定义子类构造函数
  // Man(String name, int age) : super(name, age);
  Man(super.name, super.age);

  // 提示:子类中可以重写父类的方法,执行子类自己的逻辑
  @override
  void eat() {
    print('我是$name,我爱吃肉');
  }
}

/// 女人类:子类
class Woman extends Person {
  // 定义子类构造函数
  Woman(super.name, super.age);

  // 提示:子类中可以重写父类的方法,执行子类自己的逻辑
  @override
  void eat() {
    print('我是$name,我爱吃蔬菜');
  }
}

6.5 混入(mixin、with)
  • [思考]:以下代码如何让子类Woman也有唱歌的方法?
void main() {
  // 创建男人对象
  Man man = Man('李雷', 13);
  print(man.name);
  man.eat();
  man.sing();

  // 创建女人对象
  Woman woman = Woman('韩梅梅', 14);
  print(woman.name);
  woman.eat();
}

/// 人类
class Person {
  Person(this.name, this.age);

  String? name;
  int? age;

  void eat() {
    print('$name -- eat');
  }
}

/// 男人类
class Man extends Person {
  // 定义子类构造函数
  Man(super.name, super.age);

  // 唱歌的方法
  void sing() {
    print('$name -- 爱唱歌');
  }
}

/// 女人类
class Woman extends Person {
  // 定义子类构造函数
  Woman(super.name, super.age);
}
  • 使用Mixin扩展优化代码:Mixin扩展的基本使用

[思考]:如何让子类Woman也有唱歌的方法?

[解决]:

方式1:将唱歌的方法,定义到子类Woman中(代码冗余)

方式2:将唱歌的方法,定义到父类Person中(代码扩展性不好,唱歌的方法不能被其他类复用)

方式3:使用mixin扩展一个类,扩展类中定义唱歌的方法

[mixin] 表示一个没有构造函数的类,这个类的方法可以组合到其他类中实现代码复用

[mixin] 可以同时对某个类设置多个扩展类,也可以扩展属性

void main() {
  // 创建男人对象
  Man man = Man('李雷', 13);
  // 继承的属性和方法
  print(man.name);
  man.eat();
  // 通过mixin扩展的方法
  man.sing(man.name);

  // 创建女人对象
  Woman woman = Woman('韩梅梅', 14);
  // 继承的属性和方法
  print(woman.name);
  woman.eat();
  // 通过mixin扩展的方法
  woman.sing(woman.name);
  woman.dance(woman.name);
  woman.danceType = '街舞';
  print(woman.danceType);
}

/// 人类:父类
class Person {
  Person(this.name, this.age);

  String? name;
  int? age;

  void eat() {
    print('$name -- eat');
  }
}

// 唱歌方法的扩展类
mixin SingMixin {
  // 唱歌的方法
  void sing(name) {
    print('$name -- 爱唱歌');
  }
}

// 跳舞方法的扩展类
mixin DanceMixin {
  // 舞种
  String? danceType;
  // 跳舞的方法
  void dance(String? name) {
    print('$name -- 爱跳舞');
  }
}

/// 男人类:子类
class Man extends Person with SingMixin {
  // 定义子类构造函数
  Man(super.name, super.age);
}

/// 女人类:子类
class Woman extends Person with SingMixin, DanceMixin {
  // 定义子类构造函数
  Woman(super.name, super.age);
}

七、异步编程

7.1 Dart是单线程的
7.1.1 程序中的耗时操作
开发中的耗时操作
  • 在开发中,我们经常会遇到一些耗时的操作需要完成,比如网络请求、文件读取等等;
  • 如果我们的主线程一直在等待这些耗时的操作完成,那么就会进行阻塞,无法响应其它事件,比如用户的点击;
  • 显然,我们不能这么干!!
如何处理耗时的操作呢?
  • 针对如何处理耗时的操作,不同的语言有不同的处理方式。
  • 处理方式一: 多线程,比如Java、C++、鸿蒙中,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
  • 处理方式二: 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。

接下来, 我们一起来看看在Dart中如何去处理一些耗时的操作吧!

7.2 Future
7.2.1 同步网络请求

我们先来看一个例子吧:

  • 在这个例子中,我使用getNetworkData来模拟了一个网络请求;
  • 该网络请求需要5秒钟的时间,之后返回数据;
import 'dart:io';

void main() {
  print('开始执行main函数');
  print(getNetworkData());
  print('这是不能被阻塞的代码');
}

String getNetworkData() {
  sleep(Duration(seconds: 5));
  return '返回的网络数据';
}

这段代码会运行怎么的结果呢?

  • getNetworkData会阻塞main函数的执行
开始执行main函数
// 等待5秒  
返回的网络数据
这是不能被阻塞的代码

显然,上面的代码不是我们想要的执行效果,因为网络请求阻塞了main函数,那么意味着其后所有的代码都无法正常的继续执行。

7.2.2 Future基本使用

我们来对上面的代码进行改进,代码如下:

  • 和刚才的代码唯一的区别在于使用了Future对象来将耗时的操作放在了其中传入的函数中;
  • 稍后,我们会讲解它具体的一些API,我们就暂时知道我创建了一个Future实例即可;
import 'dart:io';

void main() {
  print('开始执行main函数');
  print(getNetworkData());
  print('这是不能被阻塞的代码');
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 5));
    return '返回的网络数据';
  });
}

我们来看一下代码的运行结果:

  • 这一次的代码顺序执行,没有出现任何的阻塞现象;
  • 和之前直接打印结果不同,这次我们打印了一个Future实例;
  • 结论:我们将一个耗时的操作隔离了起来,这个操作不会再影响我们的主线程执行了。
  • 问题:我们如何去拿到最终的结果呢?
开始执行main函数
Instance of 'Future<String>'
这是不能被阻塞的代码

7.2.3 Future链式调用

有了Future之后,如何去获取请求到的结果:通过.then的回调

import 'dart:io';

void main() {
  print('开始执行main函数');
  // print(getNetworkData());
  getNetworkData().then((value) {
    print(value);
  });

  print('这是不能被阻塞的代码');
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    return '返回的网络数据';
  });
}

上面代码的执行结果:

开始执行main函数
这是不能被阻塞的代码
// 3s后执行下面的代码  
返回的网络数据

执行中出现异常

如果调用过程中出现了异常,拿不到结果,如何获取到异常的信息呢?

import 'dart:io';

void main() {
  print('开始执行main函数');
  getNetworkData().then((value) {
    print(value);
  }).catchError((e) {
    print(e);
  });

  print('这是不能被阻塞的代码');
}

Future<String> getNetworkData() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    // 不再返回结果,而是出现异常
    // return '返回的网络数据';
    throw Exception('网络请求出现错误');
  });
}

上面代码的执行结果:

开始执行main函数
这是不能被阻塞的代码
// 3s后没有拿到结果,但是我们捕获到了异常
Exception: 网络请求出现错误

补充一:上面内容的小结

我们通过一个案例来学习了一些Future的使用过程:

1、创建一个Future(可能是我们创建的,也可能是调用内部API或者第三方API获取到的一个Future,总之你需要获取到一个Future实例,Future通常会对一些异步的操作进行封装);

2、通过.then(成功回调函数)的方式来监听Future内部执行完成时获取到的结果;

3、通过.catchError(失败或异常回调函数)的方式来监听Future内部执行失败或者出现异常时的错误信息;

补充二:Future的两种状态

事实上Future在执行的整个过程中,我们通常把它划分成了两种状态:

状态一:未完成状态(uncompleted)

  • 执行Future内部的操作时(在上面的案例中就是具体的网络请求过程,我们使用了延迟来模拟),我们称这个过程为未完成状态

状态二:完成状态(completed)

  • 当Future内部的操作执行完成,通常会返回一个值,或者抛出一个异常。
  • 这两种情况,我们都称Future为完成状态。

Dart官网有对这两种状态解析,之所以拿出来说是为了区别于Promise的三种状态

7.2.4 Future链式调用小练习

例子:用户先登录,登录成功之后拿到token,然后再保存token到本地

// 用户先登录,登录成功之后拿到token,然后再保存token到本地
import 'dart:io';

void main() {
  print('开始执行main函数');

  login().then((token) {
    setToken(token).then((res) {
      if (res == 'ok') {
        print('本地存储token成功, token是$token');
      }
    }).catchError((e) {
      print(e);
    });
  }).catchError((e) {
    print(e);
  });

  print('这是不能被阻塞的代码');
}

// 1. 模拟耗时的登录操作
Future<String> login() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    print('假装登录成功');
    String token = '666888';
    return token;
  });
}

// 2. 模拟耗时的本地存储操作
Future<String> setToken(String token) {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    print('假装本地存储token');
    return 'ok';
  });
}

打印结果如下:

开始执行main函数
这是不能被阻塞的代码
// 3s后打印  
假装登录成功
// 3s后打印
假装本地存储token
本地存储token成功, token是666888

7.3 async和await

如果你已经完全搞懂了Future,那么学习await、async应该没有什么难度。

await、async是什么呢?

  • 它们是Dart中的关键字
  • 它们可以让我们用同步的代码格式,去实现异步的调用过程
  • 并且,通常一个async的函数会返回一个Future。

我们已经知道,Future可以做到不阻塞我们的线程,让线程继续执行,并且在完成某个操作时改变自己的状态,并且回调then或者errorCatch回调。

如何生成一个Future呢?

  • 1、通过我们前面学习的Future构造函数,或者后面学习的Future其他API都可以。
  • 2、还有一种就是通过async的函数。

通常使用 async await 解决Future链式调用带来的回调地狱的问题

// 用户先登录,登录成功之后拿到token,然后再保存token到本地
import 'dart:io';

void main() {
  print('开始执行main函数');

  doLogin();

  print('这是不能被阻塞的代码');
}

void doLogin() async {
  String token = await login();
  String res = await setToken(token);
  if (res == 'ok') {
    print('本地存储token $token 成功');
  }
}

// 1. 模拟耗时的登录操作
Future<String> login() {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    print('假装登录成功');
    String token = '666888';
    return token;
  });
}

// 2. 模拟耗时的本地存储操作
Future<String> setToken(String token) {
  return Future<String>(() {
    sleep(Duration(seconds: 3));
    print('假装本地存储token');
    return 'ok';
  });
}

八、泛型

[思考]为什么ListMap中可以存储任意类型的数据?

[原因]Dart在封装List和Map时,使用了泛型去限定了List和Map中数据的类型为dynamic类型

[泛型]可用于限定数据的类型,比如可以限定List和Map中数据的类型

abstract class List<E> implements EfficientLengthIterable<E>

[问题演示]保存商品分类名称时,不应该出现100、true这样类型的数据

List categories = ['居家', '美食', 100, true];

[解决]使用[泛型]限定List或者Map中元素的类型

void main() {
  // 1. 使用泛型限定List中元素的类型
  // 1.1 让列表中的元素只能是字符串类型
  // List categories = ['居家', '美食', 100, true];
  // List<String> categories = ['居家', '美食'];

  // 1.2 让列表中的元素只能是数字类型
  // List<num> nums = [100, 89, 10.99];

  // 2. 使用泛型限定Map中键和值的类型
  // 2.1 键和值都可以是任意类型
  Map categories1 = {1: 2, true: false, 'name': '张三', 'id': 1};
  print(categories1);

  // 2.2 键和值都只能是字符串类型
  Map<String, String> categories2 = {
    'id': '1',
    'name': '居家',
  };
  print(categories2);

  // 2.3 键字符串类型, 值是任意类型
  Map<String, dynamic> categories3 = {'id': 1, 'name': '居家', 'isMan': true};
  print(categories3);
}

[泛型的作用]:在程序设计中提供一种机制,使得代码能够在编写时不指定具体类型,从而实现更灵活、可复用、类型安全的代码结构,能适应多种不同类型的数据处理需求。

比如:List和Map中的元素使用泛型限定为可以是任意类型的,从而不需要单独去封装存储某一种数据类型的List和Map

/*
 泛型的作用:使用泛型可以减少重复的代码
 封装函数:接收字符串就返回字符串,接收数字就返回数字,接收bool就返回bool
 */
void main() {
  // 1. 普通封装
  // String demoString(String str) {
  //   return str;
  // }

  // int demoInt(int a) {
  //   return a;
  // }

  // bool demoBool(bool b) {
  //   return b;
  // }

  // 2. 基于泛型封装
  T demo<T>(T parm) {
    return parm;
  }

  // 调用
  String ret1 = demo<String>('itcast');
  print(ret1);
  int ret2 = demo<int>(17);
  print(ret2);
  bool ret3 = demo<bool>(true);
  print(ret3);
}

九、异常处理

void main() {
  // 1. 捕获异常:try catch
  // try {
  //   dynamic name = 'itheima';
  //   name.haha();
  // } catch (e) {
  //   print(e);
  // } finally {
  //   // 无论是否有异常都会执行这个代码块
  //   print('finally');
  // }

  // 2. 手动抛出异常:判断字符串是否相等,如果不相等手动抛出异常
  try {
    String str = 'itcast';
    if (str == 'yjh') {
      print('ok');
    } else {
      // 手动抛出异常
      throw Exception('字符串不相等');
    }
  } catch (e) {
    print(e);
  }
}

📎dart_code.zip

面试参考话术:

  1. 说一下 flutter?flutter 你会吗?

前提:flutter 项目都是比较早了 ,1 年及以前:

  • flutter 是一个跨平台的开发框架,由谷歌推出,一次开发发布多平台,ios,安卓,鸿蒙,web 都是可以适配的。
  • 开发的语言用的是 Dart,和 ArkTS 非常的类似,但是也有不同的地方,我挑几个说一下:
    • 声明变量:
      • dart:var,const,final
      • ArkTS:let,const
    • 类型
      • 基本类型差不多
      • ArkTS 中的 Record 在dart中叫做 map
      • ArkTS 中的 数组在 dart 中叫 list
    • 异步管理
      • ArkTS 中用的是 Promise,可以简写为 async 和 await
      • dart 中是 future,也可以简写为 async 和 await
      • 他俩基本一样
    • 还有一些 运算符,类子类的语法都是一样的,所以当时学习 ArkTS,包括之前学习前端的时候
    • 之前学习 dart 的经验给了我很多的帮助
    • 而且 dart,flutter 现在 ai 的语料已经很充足了,就算现在让我写,也问题不大

网站公告

今日签到

点亮在社区的每一天
去签到