(原创)
很多人都玩过“找别扭”这款游戏,下面大家一起来看看 Dart 有何与众不同。在讨论Dart 之前,还是按照惯例,先做一个“计划”,需要了解的东西划分一下重点,然后再一个个看。
划分重点
- 数据类型 & 变量
- 函数
- 控制流
- 类
- 库
- 编程范式
- 编译 & 运行
数据类型 & 变量
一切皆对象
(有没有很像JS?)
在 Dart 中,一切变量都是引用的对象,每个对象都是一个类型的实例,因此,所有的类型都是 对象类型。
继承
如上所述,一切皆对象,而万物皆有根,此根在dart 中是 Object 类型,所有类型都是 Object 类型,专业一点说,叫都继承自 Ojbect 类型。不仅包括了函数,也包括了 int 这种可能被惯性意识认为的“基本类型”
Inheritance
Object > num > int
在很多语言中,对象类型变量默认值是null,在dart 中也是如此,而且所有类型的默认值都是 null。
一些常见类型
- int
- double
- String
- bool
- List
- Set
- Map
- Runes
- Symbol
关于类型的详细说明不在这里赘述, 可以参考 https://www.dartcn.com/guides/language/language-tour
API 文档:
https://api.flutter.dev/flutter/dart-core/dart-core-library.html
常量
常量可以通过用 Final 和 Const 来修饰变量
- const: 在编译时初始化,此后不可修改,若初始化未赋值,编译不通过
- final: 在第一次赋值后不可修改
变量初始化
-
字面量初始化
以上几种类型都支持字面量初始化
例如通过字面量初始化一个String类型对象:
String stringInstance = "abc";
-
通过构造函数初始化
由于一切对象都属于类,是类就有构造函数,因此也可以通过构造函数初始化
String i = String.fromCharCode(69);
作用域
Dart 使用词法作用域(也称为静态作用域),即变量的作用域在定义时确定。
作用域范围由变量所处代码块(大括号)的层级决定,层级越深,作用域越小,层级越浅,作用域越大。
不被任何代码块包含的变量通常称为顶层变量,它们的作用域最大
全局变量
在dart 中,似乎并没有看到像C语言一样的全局变量,多个文件可以访问同一个变量(同一块内存)。那如何实现呢?
这里有个关键点: “多个文件引用同一个dart文件,只会执行一次。变量是共享的。”
所以,在一个文件中定义全局的一些变量,在其它文件中引入使用就好了。
私有变量
在变量前面加前缀 _ 就是私有变量(仅Library 内部可见)
静态变量
在类中生命的成员变脸添加 Static 关键字,为静态成员,静态成员属于类本身(类对象本身),所有该类对象共有一份存储,文件外部可通过类直接访问。
Class People {
// 声明
static String peopleCount = "60亿";
}
再来看看使用:
print(People.peopleCount);
强类型
可以指明类型
//例如:
int a = 1;
编译时,会进行类型检查,因此,是类型安全的。如果对 int 类型 赋予 double 类型的值,也会编译报错: 如下:
//例如:
int a = 1.0;
得到如下编译错误:
Error compiling to JavaScript:
main.dart:2:11:
Error: A value of type 'double' can't be assigned to a variable of type 'int'.
int a = 1.0;
^
Error: Compilation failed.
类型推断
我们也可以不指明类型声明变量,如:
var a = 1;
看起来像极了JS 的“弱类型”, 但它并不是真的弱类型,因为与 js 不同的是,dart 会在编译的时候进行类型推断和安全校验,所以它还是类型安全的。
函数
基本写法
可以像像C函数一样
int getSum(int a, int b) {
return a + b;
}
函数体内单行实现也可以这样写:
int getSum(int a, int b) => return a + b;
参数特殊处理
甚至可以跟JS一样,省略参数:
int getSum(a, b) {
return a + b;
}
可选参数,花括号 {} 或中括号 [] 中的参数是可选的:
int getSum(a, b, {c}) {
return a + b + c;
}
//也可以是
int getSum(a, b, [c]) {
return a + b + c;
}
参数默认值:
//像JS一样,设置参数c默认值,如果C没有传,默认设置为1
int getSum(a, b, {c = 1}) {
return a + b + c;
}
函数也是对象,也可以作为参数:
void main() {
var sum = caculateSum(getSum);
print(sum); //console: 4
}
int getSum(a, int b) {
return a + b;
}
int caculateSum(Function getSumFunc){
return getSumFunc(1, 2);
}
闭包
闭包特点, 引述 https://www.jianshu.com/p/ec2cd19234f9 的归纳:
- 闭包是一个方法(对象)
- 闭包定义在其他方法内部
- 闭包能够访问外部方法内的局部变量,并持有其状态(这是闭包最大的作用,可以通过闭包的方式,将其暴露出去,提供给外部访问)
示例:
//闭包的包裹函数, 闭包在函数内声明, 生成并返回一个闭包
Function makeA_BiBao () {
int count = 0;
Function woShiBiBao = () => {
print(count ++)
};
return woShiBiBao;
}
void main() {
Function woShiBiBao = makeA_BiBao(); //生成闭包
//调用5次闭包
for (int index = 0; index < 5; index ++) {
woShiBiBao();
}
}
console 输出:
0
1
2
3
4
流程控制
条件判断
if-else
if (true) {
print("true");
} else {
print("false");
}
switch 和 case
(这里比较的值不仅限于枚举、整数,还可以是其他对象,如字符串,或者编译时常量,使用 == 进行比较,⚠️:“比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写”)
照搬 “ https://www.dartcn.com/guides/language/language-tour#控制流程语句 ”
中的例子:
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
循环
经典for循环
for (int index = 0; index < 5; index ++) {
print(index); // 0 1 2 3 4
}
for-each
[8,9].forEach((int value)=> print(value)); //8 9
for-in
for (var x in [8,9]) {
print(x); // 8 9
}
while
int i = 0;
while (i < 5) {
i++;
print(i);
}
do-while
int i = 0;
do {
print(i);
i ++;
} while (i < 5);
break 和 continue
(没啥特别)
断言 assert
退出当前流程,仅在debug模式中生效
assert(text != null); //确认值不为空,如果为空则终端机程序流程
异常
当出现了异常,程序不会继续顺序执行,而跳转到异常处理对应的分支执行。
- 抛出异常
Dart 提供了 Exception 和 Error 类型, 以及一些子类型。 当然也可以定义自己的异常类型。 但是,此外 Dart 程序可以抛出任何非 null 对象, 不仅限 Exception 和 Error 对象。
throw FormatException('Expected at least 1 section');
或任意非null对象
throw "this is a exception";
- 捕获异常
- try - catch - finally, 当 try 块中发生了异常,跳转到catch中执行,不管是否有异常,最终都会执行 finally。
- 捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。
-
catch()
函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个 StackTrace 对象 )。
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
} finally {
print('最终一定会执行');
}
类
类的定义
类由成员变量、构造函数、成员函数等部分组成, 如下
class Person {
static int humanCount = 5; //类变量
String name; //成员变量
//构造函数
Person(String name) {
this.name = name;
}
//成员函数
String getName(){
return this.name;
}
//类函数
static int getHumanCount() {
return humanCount;
}
}
void main() {
Person p = Person("隔壁老王"); //调用构造函数,创建Person类实例p
print(p.name); // 隔壁老王
print(p.getName()); // 隔壁老王
print(Person.getHumanCount()); // 5
}
构造函数还有多种使用方法,参考: https://www.dartcn.com/guides/language/language-tour#构造函数
抽象类
抽象类,一般不能实例化,通过定义一个工厂构造函数后可实例化
abstract class AbstractContainer {
// 定义构造函数,字段,方法...
void updateChildren(); // 抽象方法。
}
接口(隐式)
每个类(包括抽象类)都隐式的定义了一个接口,该接口包含了该类所有的实例成员(实例变量 + 实例函数)及其实现(implements)的接口【这体现了继承关系】。
接口的目的是定义,而实现交给实现接口的类,一个类可以实现1个或多个接口,如下:
// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
// 包含在接口里,但只在当前库中可见。
final _name;
// 不包含在接口里,因为这是一个构造函数。
Person(this._name);
// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}
// person 接口的实现。
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
实现多个接口
class Point implements Comparable, Location {...}
继承
继承没有什么特别需要说的,跟很多语言一样
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写运算符
C++运算符重载?
class Vector {
final int x, y;
Vector(this.x, this.y);
//重写+运算
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
//重写-运算
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}
枚举类
enum Color { red, green, blue }
每个枚举值都有一个获取索引的方法.
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
限制
- 枚举不能被子类化,混合或实现。
- 枚举不能被显式实例化。
为类添加功能 (有点意思)
类想要复用代码,可选择继承,当然,如果不想继承,可以选择 Mixin。
将一个功能定义成 Mixin
那么这个功能可以被多个任意类添加使用。
//走能力
mixin walkAbility {
//走
void walk () {
}
}
//飞能力
mixin flyAbility {
//飞
void fly () {
}
}
//跑能力
mixin runAbility {
//跑
void run () {
}
}
//鸟,有走和飞的能力
class Bird with walkAbility, flyAbility{
}
//人 有走和跑的能力
class People with walkAbility, runAbility{
}
库
代码组织形式 (库)
在讲库之前,先了解一下代码的组织形式:
写入一个dart文件
最基本的,在dart中,我们写的代码都会保存到一个个"xxx.dart" 文件中,如main.dart 文件。当然,如果你自己愿意,它是支持你将所有的代码(类、函数、变量等)全部写入一个dart 文件,如果这是一个复杂项目,而你又这样做了,除了被骂,我想不出别的评价,你要问为什么? 1. 可读性差 2. 不方便复用将代码拆分到多个 dart 文件
我们把功能独立,逻辑独立的代码单独放入一个 dart 文件,这样,逻辑终于清晰了,各个功能代码也都独立到自己的dart 文件中了。
通常:对于这种逻辑清晰,功能独立的代码单元,便可以作为公共的代码供他人复用,这种代码的组织和封装,在一些语言中被称为库,在 dart 中,使用了 Library 的概念,但此“库”非彼库。Dart 中的“Library”
搜索了很多文章,都没有一个很明确对Library的定义, 按照自己的理解解释下(有错欢迎纠正),在dart中,一个 Library 可以是:
(1) . 将逻辑和功能封装到一起的 “一个dart 文件”, 使用Library命令,也可以不使用(默认一个Dart文件就是一个Library)
(2) . 将逻辑和功能封装到一起的,但是太多了,一个dart文件装不下,将这些功能逻辑拆分成多块,分散到 “多个part文件中”,在dart中,Library 拆分到多个Dart文件的方法使用 “ part”、 “** part of 命令**”,举个例子:一个Library 名叫A ,将它拆分成三部分A,B, C 三部分(分散到3个Dart文件中)
A.dart文件如下
// 这里是A.dart 文件
part "B.dart";
part "C.dart";
B.part 文件中如下:
// 这里是B.dart 文件
part of "A.dart";
C.part 文件中如下:
// 这里是C.dart 文件
part of "A.dart";
OK, 想必看完以上内容会对“库”有个基本认识。对库是什么、如果写成一个“库”都应该有所了解。 既然库是用来包装一些功能的,那么如何使用库呢?
管理库
在Dart SDK中定义了内置的一些公共库,供我们使用。但我们总是欲求不满,总想要的更多,于是大家纷纷都自己“造”了一些库,为了避免重复造轮子,必须有个地方让自己造的轮子“公共”,并且与自己的工程解耦开,简单的被他人引入(安装)和使用,即使库不在本地,而在网络上。大家迫切需要一个库的管理工具。
幸运的是,现在 dart 就有这样的一个管理工具,一个 “包” 管理,它用来管理库,它叫Pub (Publish吗?)。它不仅管理代码,还管理 “版本”、“仓库地址”。这里强调了它是包管理的工具,包和库什么关系?在回答这个问题前,先来看看“包”是什么
-
包 (package)
先来看看dart中文网对一个基础包结构的定义:
可以看到,包的根路径下,必须包含一个 lib 目录 & 一个pubspec.yaml 文件。
Lib 目录下包含了若干个“库”。
pubspec.yaml 则定义了库的版本(映射到其git仓库的某一个版本),和来源以及库依赖的其它库等重要信息。
至此,包和库的差别一目了然。按照我个人的理解:包是对库的封装,它封装的信息不仅仅是简单的库代码,而是库对应的Git仓库信息,使用Git仓库的好处是可以进行版本管理和网络加载。所以当包封装了库代码和Git信息之后,需要管理就不是库,而是包了。
总结: Pub管理的是包而不是简单的库代码,这样功能更强大。如很多人所知,Pub就像 NPM,cocoaPods。maven。
引述看到的一段不错的描述:
来自:https://stackoverflow.com/questions/31736890/dart-library-package-keyword-meaning
A package is a set of libraries which can for example be deployed to pub.dartlang.org. I guess this is similar to a jar file.
A library is one Dart script file with or without a name (or a set of Dart script files with part
/part of
) and is the boundary for privacy. Private members are only visible or accessible from within the same library.
-
公共的(官方)包管理网站 (www.Pub.dev)
默认情况下,包管理工具,拉取和安装工程依赖的库,拉取的来源是就是公共库托管平台 :www.Pub.dev,它管理者大家自己造的无数轮子,而这无数轮子里面,有一些可能是你需要的。你可以自己上去搜索,查找想要的,然后在你自己的工程中配置依赖它。通过 pubspec.yaml 来配置,通过pub来安装。没错,还是pubspec.yaml,因为通常你的工程,也是一个 “包”。
-
将自己的包发布到 (www.Pub.dev)
可以参照 https://dart.cn/guides/libraries/create-library-packages 来执行,没有什么特别需要说的,发布之后,别人可以通过Pub管理工具下载和安装你的包。
使用库
如果我们想在自己代码(dart文件)中引入别的人库,那么必然先要清楚,这些库的来源可能是哪些呢?我们简单梳理一下,在dart中,这些库可能来自:
- 内置在dart SDK 的内置库 (本地)
- 自己写的或引入的一些本地库 (本地)
- Pub工具管理的的“包” (来自网络或本地)
导入库代码方式如下:
- 内置库导入:import 'dart:math';
- 本地文件库导入:import 'lib/xxx.dart'; (本地文件相对路径)
- Pub包管理系统中的库 (这个稍微复杂一些)
1、自己项目根目录下配置 pubspec.yaml 的包依赖
2、运行 pub get 获取包下载安装到本地
3、项目中引入库 import 'package:flutter/material.dart';
都是通过import导入,只是格式(前缀)不同。
一些思考
这里库和包管理的概念,直觉上就是 npm 的翻版。dart 的库和node.js 模块的概念很像,dart 包管理工具 Pub 和 node.js包管理工具npm很像,难道是失散多年的孪生兄弟?