初识 Dart 语言

(原创)

很多人都玩过“找别扭”这款游戏,下面大家一起来看看 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";
  • 捕获异常
  1. try - catch - finally, 当 try 块中发生了异常,跳转到catch中执行,不管是否有异常,最终都会执行 finally。
  2. 捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。
  3. 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很像,难道是失散多年的孪生兄弟?

你可能感兴趣的:(初识 Dart 语言)