Dart基础语法
简介
Dart语言诞生于2011年10月10日,由谷歌主导开发的开源语言,Dart是一种“结构化的Web编程”语言,可以在所有现代浏览器和环境中提供高性能。Dart语言可用于Web、服务器、移动应用和物联网等领域的开发。
Dart语言目前最新版本是2.17.6,目前可用于全平台的开发,注意Dart也是一门面向对象的编程语言。
Flutter和Dart有什么关系呢?早期的Flutter团队在评估了十多种语言之后才选择了Dart,因为它符合构建用户界面的方式。
Flutter团队看重的Dart语言部分特性
(1)Dart是AOT(Ahead Of Time)编译的,编译成快速、可预测的本地代码,使得Flutter几乎都可以使用Dart编写。这不仅使得Flutter变得更快,而且几乎所有的组件(包括所有的小组件)都可以定制。
(2)Dart也可以JIT(Just In Time)编译,开发周期非常短,工作流颠覆常规(包括Flutter流行的亚秒级有状态热重载)。
(3)Dart可以更轻松的创建以60fps运行的流畅动画和转场。Dart可以在没有锁的情况下进行对象分配和垃圾回收。就像JavaScript一样,Dart避免了抢占式调度和共享内存(因此也不需要锁)。由于Flutter应用程序被编译为本地代码,因此不需要在领域之间建立缓慢的桥梁(如JavaScript到本地代码)。它的启动速度也快很多。
(4)Dart使得Flutter不需要单独的声明式布局语言(如JSX或者XML)或者单独的可视化界面构建器,因为Dart的声明式编程布局易于阅读和可视化。所有的布局使用一种语言,聚齐在一处,Flutter很容易提供高级工具,使得布局更加简单。
(5)开发人员发现Dart语言特别容易学习,因为它具有静态和动态语言用户都熟悉的特性。
(6)以上这些功能并非都是Dart语言独有的,但是Dart将这些功能组合的恰到好处,使得Dart在实现Flutter方面独一无二。因此,没有Dart语言,很难想象Flutter是否能像现在这样强大。
当开发者想创建移动APP、Web APP、Command-Line应用时,都可以使用Dart语言。
Dart语言重要概念
(1)所有的东西都是对象,无论是变量、数字、函数等都是对象。所有的对象都是类的实例。所有对象都继承内置的Object类。这一点有点类似于Java语言中的“一切皆对象”。
(2)在程序中指定数据类型使得程序能够合理地分配内存空间,并帮助编译器进行语法检查。但是,指定类型并不是必须的,Dart语言是弱数据类型。
(3)Dart代码可以在运行前解析,指定数据类型和编译时的常量可以提高运行速度。
(4)Dart程序有统一的程序入口:main(),这一点与Java、C/C++语言相似。
(5)Dart语言没有public、protected和private等概念,私有特性通过变量或者函数加上下划线来表示。
(6)Dart的工具可以检查出警告信息(warning)和错误信息(errors)。警告信息只是表明代码可能不工作,但是不会妨碍程序的运行。错误信息可能是编译时的错误,也可能是运行时的错误,编译时的错误将阻止程序运行,运行时的错误将会以异常(exception)的方式呈现。
(7)Dart支持anync/await异步处理。
Dart语言中的关键字
Dart语言中存在56个关键字,分别如下所示:abstract、do、import、super、as、dynamic、in、switch、assert、else、interface、sync*
、、enum、implements、is、this、async*
、export、library、throw、await、external、mixin、true、break、extends、new、try、case、factory、null、typedef、catch、false、operator、var、class、final、part、void、const、finally、rethrow、while、continue、for、return、with、covariant、get、set、yield*
、default、if、static、deferred。
Dart语言常用库
Dart语言常用库如下表示所示:
|包名|描述|
|:—-:|:—-:|
|dart:async
|异步编程支持,提供Future和Stream类|
|dart:collection
|对dart:core提供更多的集合支持|
|dart:convert
|不同类型(JSON,UTF-8)间的字符编码、解码支持|
|dart:core
|Dart语言内建的类型、对象以及Dart语言的核心功能|
|dart:html
|网页开发用到的库|
|dart:io
|文件读写I/O相关操作的库|
|dart:math
|数字常量以及函数,提供随机数算法|
|dart:svg
|事件和动画的矢量图像支持|
在上述常用的库中,以下三个开发库使用频率非常高:
(1)dart:core
,这是Dart语言的核心库,包括strings、numbers、collections、errors、dates、URIs等。
(2)dart:html
,网页开发中DOM相关的一些库。
(3)dart:io
,I/O命令行使用的I/O库。
请注意,dart:core
是Dart语言初始化已经包含的库,其他的任何库在使用前都需要通过import关键字来进行导入。举个例子,要使用dart:html
库,可以使用如下命令进行导入:
1 | import 'dart:html'; |
当然,使用官方提供的pub工具可以安装丰富的第三方库,第三方库地址为 https://pub.dev/ 。
Dart环境搭建
Dart SDK安装
在Windows系统上,可以使用choco install dart-sdk
命令来进行安装,具体的可以点击 这里 进行安装:
当然了,开发者还可以点击 这里 下载Windows系统的版本:
建议选择稳定版本进行下载,然后选择一个合适的安装目录进行傻瓜式的安装。如果出现网络安装失败的情况,可以选择retry或者更换下载地址为 这里 ,开发者自己手动来下载SDK:
点击进入后,选择合适的操作系统及Dart版本:
之后下载zip压缩包直接解压,接着开始设置环境变量。
(1)设置系统变量DART_SDK
,值就是刚才解压的zip的路径,注意只需填写到dart-sdk这一目录即可:
(2)设置系统变量PATH
,值就是刚才解压的zip的路径,注意需要填写到dart-sdk/bin
这一目录:
(3)检查Dart是否安装成功:
1 | C:\Users\Kenbings>dart --version |
Dart IDE安装
这里笔者选择了Android Studio为开发工具,当然IDEA之类的工具也是可以的。
Dart插件安装
这里笔者以Android Studio为例来介绍Dart插件的安装。在Settings–>Plugins里面选择Dart插件:
接着选择Flutter插件:
最后再来安装Material Theme UI插件:
Dart语言入门
依次按照如下图示创建一个Dart项目:
之后新建一个名为hello_world.dart
文件,里面的代码如下所示:
1 | /** |
之后直接运行该main方法,可以看到控制台输出“hello,world”字样。
Dart中的变量与常量
在Dart中声明变量使用var关键字,注意它可被赋予不同类型的值。举个例子,如下所示:
1 | var name = "张三"; |
在Dart语言中一切皆对象,所以如果没有将变量初始化,那么它的默认值就是null。举个例子,如下声明了变量a,但是没有给它赋值,那么默认值就是null:
1 | void main(){ |
此时我们可以给它赋值一个数字类型:
1 | void main(){ |
接着我们再将这个a变量赋值为一个字符串类型:
1 | void main(){ |
当然了,我们也可以在声明变量的时候立即给它初始化:
1 | void main(){ |
常量及固定值在开发中很常见,如星期一到星期日,一年的12个月,这些数据都可以定义为常量形式。如果定义的变量不会发生变化,可以使用final或者const来声明。注意const是一个编译时常量,而final的值只能被设定一次,即使用final声明一个只能赋值一次的变量。
举个例子,如下声明了一个final常量username,如果用户对此变量重新赋值,那么将会引发异常:
1 | fianl username = "张三"; |
同样使用const定义的常量也不能进行二次赋值,否则会引发错误。可以看到使用final和const定义的常量都是不能进行二次赋值的,那么这两个有什么区别呢?这个在后面就知道了。
通过对const类型做四则运算将自动得到一个const类型的值。举个例子,下面的代码会计算圆的面积,并得到一个常量:
1 | void main(){ |
当然了,也可以通过const来创建常量的值,也就是说const[]
本身就是构造函数。举个例子,如下所示:
1 | final stars = const []; |
Dart中的基本数据类型
Dart语言常用的基本数据类型有:Number(数值型)、String(字符串)、Boolean(布尔型)、List(数组)和Map(键值对)。
Number(数值型)
Number(数值型)包括以下两类:
(1)int(整型):取值范围:-2^53 ~ 2^53。
(2)double(浮点型):64位长度的浮点型数据,即双精度浮点型。
注意int和double类型都是num类型的子类。int类型不能包含小数点。
num类型包括的操作有:+
、-
、*
、/
、%
(取余)、~/
(取整)以及位移操作>>
。
num类型包括的常用方法有:abs(绝对值)、ceil(不小于它的最小整数)、round(四舍五入)和floor(不大于它的最大整数)、toInt(浮点型转整型)和toDouble(整型转浮点型)。
num类型包括的常用属性有:isNaN(是否为非数字)、isEven(是否为偶数)和isOdd(是否为奇数)。
来看几个实际的例子:
1 | void main(){ |
String(字符串)
String类型就是字符串类型,在开发中大量使用。(1)使用单引号或者双引号创建字符串。(2)使用三个引号或者双引号创建多行字符串。(3)使用r创建原始raw字符串。
举个例子,下面分别演示了上述三种创建字符串的方式:
1 | void main(){ |
【字符串操作】
(1)运算符:+
、*
、==
(判断字符串内容)、[]
(取字符串中的字符,下标从0开始)。下面的例子分别介绍了上述四种运算符的使用:
1 | void main(){ |
(2)插值表达式${expression}
。举个例子,如下所示:
1 | void main(){ |
(3)常用属性:length、isEmpty和isNotEmpty。举个例子,如下所示:
1 | void main(){ |
(4)常用方法:contains()、subString()、startsWith()、endsWith()、indexOf()、lastIndexOf()、toLowerCase()、toUpperCase()、trim()、trimLeft()、trimRight()、split()、replaceXXX()等。举个例子,如下所示:
1 | void main(){ |
Boolean(布尔型)
Dart是强Boolean类型检查,只有bool类型的值是true才会被认为是true。有的语言里0是false,大于0的是true,但是在Dart语言中不是,值必须为true或者false。举个例子,如下所示:
1 | void main(){ |
可以看到后面的代码无法通过编译,原因在于sex变量是一个字符串,不能使用条件判断语句,必须使用Boolean类型才可以。
List(数组)
Dart里的List对象类似于JavaScript中的数组Array对象。定义List的例子如下所示:
1 | void main(){ |
List对象的第一个元素的索引是0,最后一个元素的索引是list.length-1
。
创建不可变的list可使用如下方式,所谓的list指的是其内容无法进行修改:
1 | void main(){ |
当然了,也可以通过构造来创建,举个例子,如下所示:
1 | var list = new List(); //这种方式不建议使用,因为它可能产生不安全问题 |
【常用操作】
(1)[]
,可用于获取或者更改元素的值;
(2)length
,用于获取list的长度;
(3)add()
,用于往数组末尾添加元素;
(4)insert()
,用于往数组指定位置插入元素;
(5)remove()
,用于删除数组中的末尾元素;
(6)clear()
,用于清空数组中的所有元素;
(7)indexOf()
,用于查找某个元素第一次在数组中出现的位置;
(8)lastIndexOf()
,用于查找某个元素最后一次在数组中出现的位置;
(9)sort()
,对数组进行排序;
(10)sublist()
,获取数组的子数组;
(11)shuffle()
,随机打乱这个数组中的元素。;
(12)asMap()
,将数组转换为一个map;
(13)forEach()
,遍历数组的元素;
举个例子,下面对于上述list操作进行了演示:
1 | void main(){ |
Map(键值对)
Map类型将key和value关联在一起,也就是键值对,注意key的值必须是唯一的。
创建Map可使用如下方式:
1 | void main(){ |
也可以使用如下方式创建一个不可变的Map:
1 | void main(){ |
还可以通过对象构造来创建Map:
1 | void main(){ |
下面通过一个例子来演示Map的创建:
1 | void main(){ |
【常用操作】
(1)[]
,可用于获取或者更改元素的值;
(2)length
,用于获取map中键值对的个数;
(3)isEmpty()
,判断map是否为空;
(4)isNotEmpty()
,判断map是否不为空;
(5)keys
,用于获取map的所有key;
(6)values
,用于获取map的所有value;
(7)containsKey()
,用于判断map中是否包含某个key;
(8)containsValue()
,用于判断map中是否包含某个value;
(9)remove
,用于删除map的某个键值对;
(10)forEach
,用于遍历map中的键值对。
举个例子,下面对于上述map操作进行了演示:
1 | void main(){ |
dynamic
其实在前面我们使用var关键字声明变量的时候,这个变量的类型就是dynamic,因此我们完全可以使用dynamic来声明一个可变类型的变量。dynamic更多时候则是用在泛型上:
1 | void main(){ |
Dart中的运算符
运算符通常也被称为操作符,Dart支持各种类型的运算符,并且其中的一些操作符还能进行重载。完整的操作符如下表所示:
|描述 |运算符|
|:—-:|:—-:|
|一元后缀 | expr++
、expr--
、()
、[]
|
|一元前缀 | -expr
、!expr
、~expr
、++expr
、--expr
|
|乘法类型 | *
、/
、%
、~/
|
|加法类型 | +
、-
|
|移动位运算 | <<
、>>
|
|与位运算 | &
|
|异或位运算 | ^
|
|或位运算 |竖线
|
|关系和类型测试 | >=
、<=
、><
、as
、is
、is!
|
|等式 | ==
、!=
|
|逻辑与 | &&
|
|逻辑或 | 双竖线
|
|条件 | expr1? expr2:expr3
|
|级联 | ..
|
|赋值 |=*
、=/
、~/=
、%=
、+=
、-=
、<<=
、>>=
、&=
、^=
、竖线=
、??=
|
注意上表中操作符的优先级是由上到下逐个减小,上面行内的操作符优先级高于下面行内的操作符。举个例子,“乘法类型”中的操作符%
的优先级高于“等价“操作符==
,而==
的操作符的优先级又比“逻辑与”操作符&&
要高。注意使用运算符时的顺序,方法如下所示:
1 | //1、使用括号来提高可读性 |
对于二元运算符,其左边的操作数将会决定使用的操作符的种类。举个例子,当使用一个Vector对象以及一个Point对象时,aVector+aPoint使用的+是由Vector定义的。
算术运算符
Dart支持的常用算术运算符如下表所示:
|操作符 |含义|
|:—-:|:—-:|
|+ | 加|
|-| 减|
|-expr | 一元减号,即使后面表达式的值反过来|
- | 乘|
|/ | 除|
|~/ | 取整|
|%| 取余|
举个例子,如下所示:
1 | void main(){ |
Dart还支持前缀和后缀递增和递减运算符,如下表所示:
|操作符 |含义|
|:—-:|:—-:|
|++var| var = var+1表达式的值为var+1|
|var++| var = var+1表达式的值为var|
|–var| var = var-1表达式的值为var-1|
|var–| var = var-1表达式的值为var|
举个例子,如下所示:
1 | void main(){ |
关系运算符
等式和关系运算符的含义如下表所示:
|操作符 |含义|
|:—-:|:—-:|
|==|等于|
|!=|不等于|
|>|大于|
|<|小于|
|>=|大于等于|
|<=|小于等于|
请注意,如果需要判断两个对象的内容是否相等,请使用==
运算符。
举个例子,如下所示:
1 | void main(){ |
类型测试操作符
as
、is
和is!
操作符在运行时用于检查类型非常方便,含义如下所示:
|操作符 |含义|
|:—-:|:—-:|
|as|类型转换|
|is|当对象是相应类型时返回true|
|is!|当对象不是相应类型时返回true|
如果obj实现了T所定义的接口,那么obj is T
将返回true。使用as操作符可以将一个对象转换为指定类型,前提是能够转换。在转换之前,使用is判断更为稳妥。
举个例子,如下所示:
1 | if(user is User){ |
如果能确定user是User的实例,那么可以通过as操作符直接简化代码:
1 | (user as User).name = "flutter"; |
请注意,上面两段代码并不相等。如果user的值是null或者不是一个User对象,那么第一段代码不会做任何事情,而第二段代码会抛异常。
赋值操作符
可以使用=
运算符赋值。如果开发者想仅在变量为null时赋值,可以使用??=
运算符。举个例子,如下所示:
1 | //赋值给a |
诸如+=
之类的复合赋值运算符将操作与赋值相结合。以下是复合赋值运算符的工作方式:
|复合赋值|等式表达式|
|:—-:|:—-:|
|a op b|a = a op b|
|a += b|a = a +b|
|a -= b|a = a -b|
逻辑运算符
可以使用逻辑运算符反转或者组合布尔表达式,逻辑运算符如下所示:
|操作符|含义|
|:—-:|:—-:|
|!expr|反转以下表达式(将false更改为true,反之将true更改为false)|
|双竖线|逻辑或|
|&&|逻辑与|
举个例子,如下所示:
1 | if( !expr && (test == 2 || test==6)){ |
条件表达式
Dart中有两个运算符,可用来简明地评估可能需要if-else语句的表达式。下面所示的代码就是一种条件表达式,也可以称为三元表达式。如果条件为真,则返回expr1,否则返回expr2:
1 | condition? expr1:expr2; |
第二种方式则如下所示,如果expr1为非空,则返回其值;否则计算并返回expr2的值:
1 | expr1?? expr2 |
注意这个expr1必须是一个允许空的类型,如果使用字符串,而字符串本身就是非空类型,此时代码会抛出异常。
级联操作
级联操作使用两个点..
来表示,可对同一对象执行一系列操作。类似于JavaScript中的Promise的then处理。级联操作的主要目的是为了简化代码。举个例子,如下所示:
1 | querySelector('#btnOk') //获取一个id为btnOk的按钮对象 |
可以看到第一个方法调用querySelector,返回一个按钮对象,之后再设置它的文本为“确定”,接着再给这个按钮添加一个样式“ButtonOKStyle”,最后监听单机事件,事件弹出一个显示“确定”的警告。其实这个例子就相当于下面的操作:
1 | var button = querySelector('#btnOk'); |
严格意义来说,级联的双点符号并不是运算符,而是Dart语法的一部分。
Dart中的流程控制语句
Dart中的流程控制语句主要有如下7个:if和else、for(循环)、while和do-while(循环)、break和continue、switch和case、assert(断言)、try-catch和throw。
if和else
Dart支持if及else的多种组合,如下面的例子所示:
1 | void main(){ |
上面的代码将输出“优秀”,条件语句执行到第一条判断就停止了。
for(循环)
【普通的for(循环)】下面的例子说明for循环,首先定义了一个字符串“hello,dart”,然后使用for循环向message变量中写入5个同样的字符”!”,如下所示:
1 | void main(){ |
注意上面的值是在字符串尾部进行添加的。除了常规的for循环外,针对可以序列化的操作数,还可以使用for..in循环。
【for..in循环】如果开发者不关心操作数的当前下标时,此时使用for..in循环是非常方便的。举个例子,如下所示:
1 | void main(){ |
while和do-while(循环)
【while】下面的例子说明while循环,其中定义了一个变量temp。temp会在循环体内自动加1,当条件(temp <5)不满足时,就会退出循环,如下所示:
1 | void main(){ |
程序执行结果如下所示:
1 | 这是一个while循环: 0 |
【do-while】当然了,上述例子也可以使用do-while语句来进行改造,改造后的代码如下所示:
1 | void main(){ |
break和continue
【break】break用于跳出循环,下面的例子说明当变量的值为2时就跳出循环,如下所示:
1 | void main(){ |
程序执行结果如下所示:
1 | 1 |
也就是说当v等于2时循环就结束,所以程序就输出1。
【continue】现在我们尝试将其中的break替换为continue,此时代码如下:
1 | void main(){ |
程序执行结果如下所示:
1 | 1 |
可以看到此时当v等于2时,循环只是跳出本次循环,代码还会继续执行下去。
switch和case
Dart中的switch和case语句使用“==”操作符来比较整数、字符串或者其他编译过程中的常量(也包括对象和枚举),进而实现分支的作用。请注意,switch/case语句的前后操作数必须是同一类型的对象实例。每一个非空的case字句最后都必须跟上break语句,使用default处理默认情况,也可以使用continue来跳转标签。
举个例子,如下所示:
1 | void main(){ |
再来看一下使用continue来跳转标签的例子:
1 | void main(){ |
由于language的值为dart,因此首先会匹配case "dart"
的情况,然后输出dart,接着由于使用了continue C;
使得代码跳转到了C:
处,C处会找到离它最近的情况,然后进行输出。此处的C只是一个变量,它不需要值,名称可随意取。
assert(断言)
assert语句用于中断正常的执行流程,当assert判断条件为false时,程序将发生中断;注意assert判断的条件时任何可以转化为boolean类型的对象,也包括函数。
说白了,即assert判断为true则继续执行后续流程,如果为false则抛出一个断言错误AssertError。举个例子,如下所示:
1 | void main(){ |
显然后续内容是无法打印输出的,原因就在于assert断言判断值为false中断了后续流程执行。
异常处理
异常表示发生了意外的错误,如果没有捕获异常,那么引发异常的程序将会被挂起,且程序将被终止运行。注意Dart中所有的异常都是非检查异常。
Dart提供了异常和错误类型以及许多预定义的子类型,当然开发者也可以自定义自己的异常,注意Dart程序可以抛出任何非空对象。
抛出异常
下面是一个抛出或者引发异常的例子:
1 | throw FormatException("抛出一个异常"); |
也可以抛出任意对象:
1 | throw "数据非法"; |
捕获异常
开发者可以指定一个或者两个参数来捕获异常(catch),如果使用两个那么第一个catch用于捕获异常详细信息,第二个catch则是堆栈跟踪(StackTrace对象)。举个例子,如下所示:
1 | void main(){ |
finally
如果开发者要求某些代码,无论是否抛出异常都可以正常运行,此时就可以使用finally字句。如果没有catch字句匹配异常,那么异常将会在finally字句执行之后被抛出。
1 | void main(){ |
小结
本篇学习了Dart语言中的一些重要概念并在此基础上搭建了Dart开发环境,还学习了Dart语言中的一些基础语法,如变量和常量、基本数据类型、运算符和流程控制语句等。