【Flutter从入门到入坑之二】Dart语言基础概述

官方文档:https://api.dart.cn/stable/2.16.2/dart-core/dart-core-library.html

关于新技术的学习,一直以来我都非常认同一个观点:千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样才能从高维度理解问题。 所以,为了我们更高效地掌握 Dart,以最快的速度具备开发一款 Flutter 应用的能力,这里,我会先从 Flutter 开发的角度,介绍 Dart 语言的基础知识。


我们在学习 Dart 基础知识之前,我们先来了解一下 Dart 的由来以及 Dart 的特性。

Dart 概述

Dart 是什么?

2011年10月,在 GOTO 大会上,Google 发布了一种新的编程语言 Dart。Dart 的诞生正是要解决 JavaScript 存在的、在语言本质上无法改进的缺陷。出于对 JavaScript 的不满,Google 的程序员们决定自己写一个新语言来换掉它,所以 Dart 的最初定位也是一种运行在浏览器中的脚本语言。为了推广 Dart,Google 甚至将自己的 Chrome 浏览器内置了 Dart VM,可以直接高效地运行 Dart 代码。
由于缺少顶级项目的使用,Dart 始终不温不火。2015 年,在听取了大量开发者的反馈后,Google 决定将内置的 Dart VM 引擎从 Chrome 移除,这对 Dart 的发展来说是重大挫折,替代 JavaScript 就更无从谈起了。
Dart 也借此机会开始转型:在 Google 内部孵化了移动开发框架 Flutter,弯道超车进入了移动开发的领域。Dart 也成为专注大前端与跨平台生态的语言。

Dart 的特性

  • JIT 与 AOT
    Dart 是少数同时支持 JIT(Just In Time,即时编译)和 AOT(Ahead of Time,运行前编译)的语言之一。
    语言在运行之前通常都需要编译,JIT 和 AOT 则是最常见的两种编译模式。

    • JIT 在运行时即时编译,在开发周期中使用,可以动态下发和执行代码,开发测试效率高,但运行速度和执行性能则会因为运行时即时编译受到影响。
    • AOT 即提前编译,可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低。

    总结来讲,在开发期使用 JIT 编译,可以缩短产品的开发周期。Flutter 最受欢迎的功能之一热重载,正是基于此特性。而在发布期使用 AOT,就不需要像 React Native 那样在跨平台 JavaScript 代码和原生 Android、iOS 代码之间建立低效的方法调用映射关系。所以说,Dart 具有运行速度快、执行性能好的特点。

  • 内存分配与垃圾回收
    Dart VM 的内存分配策略比较简单,创建对象时只需要在堆上移动指针,内存增长始终是线性的,省去了查找可用内存的过程。
    在 Dart 中,并发是通过 Isolate 实现的。Isolate 是类似于线程但不共享内存,独立运行的 worker。这样的机制,就可以让 Dart 实现无锁的快速分配。

    Dart 的垃圾回收,则是采用了多生代算法。新生代在回收内存时采用“半空间”机制,触发垃圾回收时,Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存。回收过程中,Dart 只需要操作少量的“活跃”对象,没有引用的大量“死亡”对象则被忽略,这样的回收机制很适合 Flutter 框架中大量 Widget 销毁重建的场景。

  • 单线程模型
    Dart 中并没有线程,只有 Isolate(隔离区)。Isolates 之间不会共享内存,就像几个运行在不同进程中的 worker,通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信。

  • 无需单独的声明式布局语言
    Dart 声明式编程布局易于阅读和可视化,使得 Flutter 并不需要类似 JSX 或 XML 的声明式布局语言。所有的布局都使用同一种格式,也使得 Flutter 很容易提供高级工具使布局更简单。


一、基本语法

  1. 主函数(入口函数)

    void main(List<String> arguments) {
    	print('Hello world! ${arguments}');
    }
    
  2. 基本类型

    // 基本类型
    // int
    int age = 20;
    // double
    double count = 10.0;
    // String
    String 
    // bool
    bool flag = true;
    // List
    List list = [1,2,3,4,5,6];
    // Set
    Set set = new Set();
    set.addAll(list);
    // Map
    Map user = {'name': 'bajie', 'age': 18};
    // 类型可推导
    => var user = {'name': 'bajie', 'age': 18};
    print("${user['name']}")
    null
    // 常量
    const
    final
    /*
    * 区别:
    * const 必须先赋初值
    * final 可以后面赋值一次
    */ 
    // 变量
    var
    
  3. 函数

    // 1,函数创建
    // 2,函数传值
    // 3. 可选参数
    printName(String name, int age, [String sex = '男']) {
    	print("name is ${name}, age is ${age}, sex is ${sex}")
    }
    // 4. 命名函数
    printName1({name, age=18, sex}) {
    	print("name is ${name}, age is ${age}, sex is ${sex}")
    }
    void main(List<String> arguments) {
    	printName('八戒', 18);
    	printName1({name: '八戒', age: 20, sex: '男'});
    }	
    

二、面向对象

  1. 类的封装

    /*
    * _ 标识私有属性
    */
    // 创建一个类
    // lib/animal.dart
    class Animal {
    	String? name;
    	Animal() {};
    	Animal.initFromName({ this.name });
    	void eat() {
    		print('${name} is eating!')
    	}
    } 
    
    // main.dart
    void main() {
    	Animal a = Animal.initFromName(name: 'bajie')
    	a.eat();
    }
    
  2. 类的继承

    // 继承一个类
    // lib/cat.dart
    class Cat {
    	// : => 在执行这个构造方法之前,先执行的操作
    	Cat.initFromName({ required String?name }) : super.initFromName(name: name);
    } 
    
    void main() {
    	Animal a = Animal.initFromName(name: 'bajie');
    	a.eat();
    	// 继承
    	// 在继承时,子类没有继承parent的命名构造函数
    	Cat c = Cat.initFromName(name: 'Tom');
    	c.eat();
    }
    
  3. 多态

    // lib/cat.dart
    class Cat {
    	// : => 在执行这个构造方法之前,先执行的操作
    	Cat.initFromName({ required String?name }) : super.initFromName(name: name);
    	// 重写一个方法
    	void eat() {
    		print('miaomiao~')
    	}
    } 
    
    void main() {
    	Animal a = Animal.initFromName(name: 'bajie');
    	a.eat();
    	// 多态
    	Animal c = Cat.initFromName(name: 'Tom');
    	c.eat();
    }
    

三、空安全 Null safety

1. 为什么需要空安全?
1.1 什么是空安全?
1.2 服务端——客户端
1.3 运行时——编译时
1.4 目标:对于空安全而言,我们的目标是让您对代码中的 null 可见且可控,并且确保它不会传递至某些位置从而引发崩溃。

在这里插入图片描述
在这里插入图片描述

  1. 怎么做的空安全 String? str;
    String? 就是 String|Null
    在这里插入图片描述
    我们将类型世界划分为了非空可空的两半。为了保持代码的健全和我们的原则:“除非您需要,否则您永远不会在运行时遇到空引用错误”,我们需要保证 null 不会出现在非空一侧的任何类型里。

看下面代码. 它将在调用 .length 时抛出 NoSuchMethodError 异常。null 值是 Null 类的一个实例,而 Null 没有 "length" getter

// Without null safety:
bool isEmpty(String string) => string.length == 0;
main() {
  isEmpty(null);
}

?!late

// In null-safe Dart, none of these can ever be null.var i = 42; // Inferred to be an int.String name = getFileName();final b = Foo();
var i = 42; 
String name = getFileName();
final b = Foo();
  • 如果这些变量 可以 为空值 ( null ), 在类型声明处 加上 ?

    int? aNullableInt = null;
    
  • 在您已经明确一个非空变量一定会在使用前初始化, 而 Dart 分析器仍然无法明确的情况下, 您可以在变量的类型前 加上 late

    class IntProvider {
      late int aRealInt;
      IntProvider() {
        aRealInt = calculate();
      }
    }
    
  • 当您正在调用一个可空的变量或者表达式时, 请确保您自己处理了空值。例如:您可以使用 if 条件句、?? 操作符 或是 ?. 操作符来处理可能为空的值。

    // 使用 ?? 操作符来避免将非空变量赋予空值
    int value = aNullableInt ?? 0; // 0 if it's null; otherwise, the integer
    
  • 如果您能确定一条可空的表达式不为空, 您可以在其后添加 ! 让 Dart 处理为非空。

    int? aNullableInt = 2;
    int value = aNullableInt!;
    
  • 如果您想改变一个可空变量的类型,您可以使用 类型转换操作符 (as), 这是 ! 操作符做不到的。

    // 使用了 as 将 num? 转换为 int
    return maybeNum() as int;
    
  • 一旦您开始使用空安全,当操作对象可能为空时, 您将不再能使用 成员访问符 (.)。取而代之的是可空版本的 ?.

    double? d;  print(d?.floor()); // Uses `?.` instead of `.` to invoke `floor()`.
    

四、异步编程

Dart异步原理
Dart 是一门单线程编程语言。
异步 IO + 事件循环

在这里插入图片描述
异步操作

1. Future

Future 对象封装了Dart 的异步操作,它有未完成(uncompleted)和已完成(completed)两种状态。
completed 状态也有两种:一种是代表操作成功,返回结果;另一种代表操作失败,返回错误。

Future<String> fetchUserOrder() {
  //想象这是个耗时的数据库操作
  return Future(() => 'Large Latte');
}

void main() {
  fetchUserOrder().then((result){print(result)})
  print('Fetching user order...');
}

通过.then来回调成功结果,main会先于Future里面的操作,输出结果:

Fetching user order...
Large Latte

Future 同名构造器是 factory Future(FutureOr<T> computation()),它的函数参数返回值为 FutureOr<T> 类型,我们发现还有很多 Future 中的方法比如Future.thenFuture.microtask 的参数类型也是 FutureOr<T>,看来有必要了解一下这个对象。
FutureOr<T> 是个特殊的类型,它没有类成员,不能实例化,也不可以继承,看来它很可能只是一个语法糖。

abstract class FutureOr<T> {
  // Private generative constructor, so that it is not subclassable, mixable, or
  // instantiable.
  FutureOr._() {
    throw new UnsupportedError("FutureOr can't be instantiated");
  }
}

2. async 和 await

想象一个这样的场景:

  1. 先调用登录接口;
  2. 根据登录接口返回的token获取用户信息;
  3. 最后把用户信息缓存到本机。
Future<String> login(String name,String password){
  //登录
}
Future<User> fetchUserInfo(String token){
  //获取用户信息
}
Future saveUserInfo(User user){
  // 缓存用户信息
}

Future 大概可以这样写:

login('name','password')
  .then((token) => fetchUserInfo(token))
  .then((user) => saveUserInfo(user));

换成 asyncawait 则可以这样:

void doLogin() async {
  String token = await login('name','password'); //await 必须在 async 函数体内
  User user = await fetchUserInfo(token);
  await saveUserInfo(user);
}

声明了 async 的函数,返回值是必须是 Future 对象。即便你在 async 函数里面直接返回 T 类型数据,编译器会自动帮你包装成 Future<T> 类型的对象,如果是 void 函数,则返回 Future<void> 对象。在遇到 await 的时候,又会把 Futrue 类型拆包,又会原来的数据类型暴露出来,请注意,await 所在的函数必须添加 async 关键词

await 的代码发生异常,捕获方式跟同步调用函数一样:

void doLogin() async {
  try {
    var token = await login('name','password');
    var user = await fetchUserInfo(token);
    await saveUserInfo(user);
  } catch (err) {
    print('Caught error: $err');
  }
}

语法糖:

Future<String> getUserInfo() async {
  return 'aaa';
}
//等价于:
Future<String> getUserInfo() async {
  return Future.value('aaa');
}

总结

以上内容基本属于 Dart语言的基础部分,我们在学习 Flutter 之前先对Dart有一个简单的了解,那么对于后面的学习会更加顺利。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值