Dart基本语法
Dart的设计目标应该是同时借鉴了Java和JavaScript。
Dart在静态语法方面和Java非常相似,如类型定义、函数声明、泛型等,
而在动态特性方面又和JavaScript很像,如函数式特性、异步支持等。
除了融合Java和JavaScript语言之所长之外,Dart也具有一些其它具有表现力的语法,如可选命名参数、..(级联运算符)和?.(条件成员访问运算符)以及??(判空赋值运算符)。
其实,对编程语言了解比较多的读者会发现,在Dart中其实看到的不仅有Java和JavaScript的影子,它还具有其它编程语言中的身影,如命名参数在Objective-C和Swift中早就很普遍
Dart和Java及JavaScript对比
之所以将Dart与Java和JavaScript对比,是因为,这两者分别是强类型语言和弱类型语言的典型代表,并且Dart 语法中很多地方也都借鉴了Java和JavaScript。
Dart vs Java
客观的来讲,Dart在语法层面确实比Java更有表现力;
- 在VM层面,Dart VM在内存回收和吞吐量都进行了反复的优化,但具体的性能对比,笔者没有找到相关测试数据,但在笔者看来,只要Dart语言能流行,VM的性能就不用担心,毕竟Google在Go(没用VM但有GC)、JavaScript(v8)、Dalvik(Android上的Java VM)上已经有了很多技术积淀。
- 值得注意的是Dart在Flutter中已经可以将GC做到10ms以内,所以Dart和Java相比,决胜因素并不会是在性能方面。
- 而在语法层面,Dart要比Java更有表现力,最重要的是Dart对函数式编程支持要远强于Java(目前只停留在Lambda表达式),
- Dart目前真正的不足是生态,但笔者相信,随着Flutter的逐渐火热,会回过头来反推Dart生态加速发展,对于Dart来说,现在需要的是时间。
Dart vs JavaScript
JavaScript的弱类型一直被抓短,所以TypeScript、CoffeeScript甚至是Facebook的flow(虽然并不能算JavaScript的一个超集,但也通过标注和打包工具提供了静态类型检查)才有市场。
就笔者使用过的脚本语言中(笔者曾使用过Python、PHP),JavaScript无疑是动态化支持最好的脚本语言,比如在JavaScript中,可以给任何对象在任何时候动态扩展属性,对于精通JavaScript的高手来说,这无疑是一把利剑。但是,任何事物都有两面性,JavaScript的强大的动态化特性也是把双刃剑,你可经常听到另一个声音,认为JavaScript的这种动态性糟糕透了,太过灵活反而导致代码很难预期,无法限制不被期望的修改。毕竟有些人总是对自己或别人写的代码不放心,他们希望能够让代码变得可控,并期望有一套静态类型检查系统来帮助自己减少错误。
正因如此,在Flutter中,Dart几乎放弃了脚本语言动态化的特性,如不支持反射、也不支持动态创建函数等。并且Dart在2.0强制开启了类型检查(Strong Mode),原先的检查模式(checked mode)和可选类型(optional type)将淡出,所以在类型安全这个层面来说,Dart和TypeScript、CoffeeScript是差不多的,所以单从这一点来看,Dart并不具备什么明显优势,但综合起来看,Dart既能进行服务端脚本、APP开发、web开发,这就有优势了!
Dart基本语法
1、常量
final和const都用来命名常量
- 被
final
或者const
修饰的变量,变量类型可以省略 - 两者区别在于:
const
变量是一个编译时常量-const 在编译时直接转为常量,final
变量在第一次使用时被初始化。
1 | //可以省略String类型声明 |
2、变量
2.1 var可接受任意类型,但是一旦赋值其数据类型不能改变了
1 | var t; |
2.2 dynamic和Object
Object
是Dart所有对象的根基类,也就是说所有类型都是Object
的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object
声明的对象.dynamic
与var
一样都是关键词,声明的变量可以赋值任意对象。dynamic
与Object
相同之处在于,他们声明的变量可以在后期改变数据类型。1
2
3
4
5
6
7dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代码没有问题
t = 1000;
x = 1000;dynamic
与Object
不同的是,dynamic
声明的对象编译器会提供所有可能的组合, 而Object
声明的对象只能使用Object的属性与方法, 否则编译器会报错。如下:变量a不会报错, 变量b编译器会报错1
2
3
4
5
6
7
8
9
10
11
12
13
14
15dynamic a;
Object b;
main() {
a = "";
b = "";
printLengths();
}
printLengths() {
// no warning
print(a.length);
// warning:
// The getter 'length' is not defined for the class 'Object'
print(b.length);
}
空安全(null-safety)
Dart 中一切都是对象,这意味着如果我们定义一个数字,在初始化它之前如果我们使用了它,假如没有某种检查机制,则不会报错,比如:
1 | test() { |
在 Dart 引入空安全之前,上面代码在执行前不会报错,但会触发一个运行时错误,原因是 i 的值为 null 。但现在有了空安全,则定义变量时我们可以指定变量是可空还是不可空。
1 | int i = 8; //默认为不可空,必须在定义时初始化。 |
如果一个变量我们定义为可空类型,在某些情况下即使我们给它赋值过了,但是预处理器仍然有可能识别不出,这时我们就要显式(通过在变量后面加一个”!“符号)告诉预处理器它已经不是null了,比如:
1 | class Test{ |
上面中如果函数变量可空时,调用的时候可以用语法糖:
1 | fun?.call() // fun 不为空时则会被调用 |
String 基础用法
字符串截取
var string = ‘dartlang’;
string.substring(1); // ‘artlang’
string.substring(1, 4); // ‘art’
double基本用法
1 | // 转成字符串 |
类型转换
String to int
1 | var n1 = int.parse('-42'); |
String to double
1 | var scoreDouble = double.parse(item.areaPerformanceScore); |
double保留两位小数
1 | final scoreWithTwoDecimal = scoreDouble.toStringAsFixed(2); |
List 基本用法
Creates a list of the given length.
NOTICE: This constructor cannot be used in null-safe code. Use [List.filled] to create a non-empty list. This requires a fill value to initialize the list elements with. To create an empty list, use
[]
for a growable list orList.empty
for a fixed length list (or where growability is determined at run-time).The created list is fixed-length if [length] is provided.
var fixedLengthList = List(3);
fixedLengthList.length; // 3
fixedLengthList.length = 1; // ErrorThe list has length 0 and is growable if [length] is omitted.
var growableList = List();
growableList.length; // 0;
growableList.length = 3;To create a growable list with a given length, for a nullable element type, just assign the length right after creation:
List
growableList = []..length = 500; For a non-nullable element type, an alternative is the following:
List
growableList = List .filled(500, 0, growable: true); The [length] must not be negative or null, if it is provided.
If the element type is not nullable, [length] must not be greater than zero.
‘List’ is deprecated and shouldn’t be used. Use a list literal, [], or the List.filled constructor instead.
Try replacing the use of the deprecated member with the replacement.dart(deprecated_member_use)The default ‘List’ constructor isn’t available when null safety is enabled.
Try using a list literal, 'List.filled' or 'List.generate'.
3、函数
Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。
3.1函数声明
1 | bool isNoble(int atomicNumber) { |
3.2 简写一个表达式的函数
1 | bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ; |
3.3函数作为变量
1 | var say = (str){ |
3.4函数作为参数传递
1 | void execute(var callback) { |
3.5可选位置参数[param1, param2]
- 包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:
1 | String say(String from, String msg, [String device]) { |
3.6可选命名参数{param1, param2}
- 定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。例如:
1 | //设置[bold]和[hidden]标志 |
注意,不能同时使用可选的位置参数和可选的命名参数
异步函数
Dart类库有非常多的返回
Future
或者Stream
对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作/网络请求。而不是等到这个操作完成。
Future
与JavaScript中的Promise
非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。请记住,
Future
的所有API的返回值仍然是一个Future
对象,所以可以很方便的进行链式调用。一个Future只会对应一个结果,要么成功,要么失败。
Future.then
1 | Future.delayed(new Duration(seconds: 2),(){ |
Future.catchError/Future.onError
1 | Future.delayed(new Duration(seconds: 2),(){ |
Future.whenComplete
有些时候,我们会遇到无论异步任务执行成功或失败都需要做一些事的场景,比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。这种场景,有两种方法,第一种是分别在then
或catch
中关闭一下对话框,第二种就是使用Future
的whenComplete回调:
1 | Future.delayed(new Duration(seconds: 2),(){ |
Future.wait
有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作.
比如我们有一个界面,需要先分别从两个网络接口获取数据,获取成功后,我们需要将两个接口数据进行特定的处理后再显示到UI界面上,应该怎么做?
答案是Future.wait
,它接受一个Future
数组参数,只有数组中所有Future
都执行成功后,才会触发then
的成功回调,只要有一个Future
执行失败,就会触发错误回调。下面,我们通过模拟Future.delayed
来模拟两个数据获取的异步任务,等两个异步任务都执行成功时,将两个异步任务的结果拼接打印出来,代码如下:
1 | Future.wait([ |
执行上面代码,4秒后你会在控制台中看到“hello world”。
回调地狱(Callback Hell)
如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then
回调中套回调情况
1 | login("alice","******").then((id){ |
如果业务逻辑中有大量异步依赖的情况,将会出现上面这种在回调里面套回调的情况,过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,这个问题被形象的称为回调地狱(Callback Hell)。回调地狱问题在之前JavaScript中非常突出,也是JavaScript被吐槽最多的点,但随着ECMAScript6和ECMAScript7标准发布后,这个问题得到了非常好的解决,
而解决回调地狱的两大神器正是ECMAScript6引入了Promise
,
以及ECMAScript7中引入的async/await
。
而在Dart中几乎是完全平移了JavaScript中的这两者:Future
相当于Promise
,而async/await
连名字都没改。接下来我们看看通过Future
和async/await
如何消除上面示例中的嵌套问题。
使用Future消除Callback Hell
1 | login("alice","******").then((id){ |
正如上文所述, “Future
的所有API的返回值仍然是一个Future
对象,所以可以很方便的进行链式调用” ,如果在then中返回的是一个Future
的话,该future
会执行,执行结束后会触发后面的then
回调,这样依次向下,就避免了层层嵌套。
使用async/await消除callback hell
通过Future
回调中再返回Future
的方式虽然能避免层层嵌套,但是还是有一层回调,有没有一种方式能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式?答案是肯定的,这就要使用async/await
了,下面我们先直接看代码,然后再解释,代码如下:
1 | task() async { |
async
用来表示函数是异步的,定义的函数会返回一个Future
对象,可以使用then方法添加回调函数。await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走;await
必须出现在async
函数内部。
可以看到,我们通过async/await
将一个异步流用同步的代码表示出来了。
其实,无论是在JavaScript还是Dart中,
async/await
都只是一个语法糖,编译器或解释器最终都会将其转化为一个Promise(Future)的调用链。
Stream
Stream
也是用于接收异步事件数据,和Future
不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。
Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。
举个例子:
1 | Stream.fromFutures([ |
上面的代码依次会输出:
1 | I/flutter (17666): hello 1 |
代码很简单,就不赘述了。
思考题:既然Stream可以接收多次事件,那能不能用Stream来实现一个订阅者模式的事件总线?
List
‘List’ is deprecated and shouldn’t be used. Use a list literal, [], or the List.filled constructor instead.
默认的 List 构造有什么改动?
你可能会遇到这样的错误:
1 | The default 'List' constructor isn't available when null safety is enabled. #default_list_constructor |
默认的列表构造会将列表用 null
填充,会造成问题。
将它变为 List.filled(length, default)
即可。
(deprecated) List
List([int? length]) dart:core
Creates a list of the given length.
NOTICE: This constructor cannot be used in null-safe code. Use [List.filled] to create a non-empty list. This requires a fill value to initialize the list elements with. To create an empty list, use
[]
for a growable list orList.empty
for a fixed length list (or where growability is determined at run-time).The created list is fixed-length if [length] is provided.
var fixedLengthList = List(3);
fixedLengthList.length; // 3
fixedLengthList.length = 1; // ErrorThe list has length 0 and is growable if [length] is omitted.
var growableList = List();
growableList.length; // 0;
growableList.length = 3;To create a growable list with a given length, for a nullable element type, just assign the length right after creation:
List
growableList = []..length = 500; For a non-nullable element type, an alternative is the following:
List
growableList = List .filled(500, 0, growable: true); The [length] must not be negative or null, if it is provided.
If the element type is not nullable, [length] must not be greater than zero.
‘List’ is deprecated and shouldn’t be used. Use a list literal, [], or the List.filled constructor instead.
Try replacing the use of the deprecated member with the replacement.dart(deprecated_member_use)The default ‘List’ constructor isn’t available when null safety is enabled.
Try using a list literal, ‘List.filled’ or ‘List.generate’.
- List 中元素重新赋值
1 | list1[0] = '第一个元素替换'; |
- setRange() 范围内修改List
1 | //在range 范围内修改List 超出范围报错 |
- replaceRange() 范围内替换List
1 | //在range 范围内替换List 超出范围报错 包含头不包含尾 |
- fillRange(start,end,value) 在start end 每个元素用value替换
1 | List<int> list11 = List(); |
- retainWhere(()=>(bool)) 根据条件筛选元素
1 | List<int> list12 = List(); |
- setAll(index,list) 从index开始,使用list内的元素逐个替换本list中的元素
1 | List<int> list13 = List(); |
- indexOf(element,[start]) 查找指定元素在list中的索引
1 | List<int> list14 = List(); |
- lastIndexOf(element,[start]) 从后往前查找指定元素在list中的索引
1 | List<int> list15 = List(); |
- elementAt(index) 根据索引值获取元素
1 | List<int> list16 = List(); |
- any((element) => (bool)) 判断List中是否任意一个元素符合条件赛选
1 | List<int> list17 = List(); |
- firstWhere((element) =>(bool)) 返回第一个满足条件的元素
1 | List<int> list18 = List(); |
- int lastIndexWhere(bool test(E element), [int start]); 返回第一个满足条件的元素下标,start表示从第几个下标开始查找,不存在则返回-1
1 | List<int> list19 = List(); |
- lastWhere(bool test(E element), {E orElse()}) 从后往前查找,返回第一个满足条件的元素
1 | List<int> list20 = List(); |
- forEach 遍历List中的每个元素 进入文档内查看,forEach方法内部其实也是forIn方法进行遍历
1 | List list21 = List(); |
- fold(T initialValue, T combine(T previousValue, E element)) 根据现有的List和给定的initialValue,指定一个参数函数规则,对List每个元素做操作,并将结果返回
1 | List<int> list22 = [1,2,3,4,5,6]; |
- reduce(E combine(E value, E element)) 用指定的函数对元素做连续操作,将结果返回
1 | List<int> list23 = [1,2,3,4,5,6]; |
- skipWhile(bool test(E value)) 根据参数函数,找到第一个不符合条件的元素,然后将其及以后的元素返回,如果都符合就返回一个空List,如果都不符合则全部返回
1 | List list25 = [3,1,2,4,5,6]; |
- take(int count) 从0 - count 获取元素 并返回
- takeWhile(bool test(E value)) 从0 开始获取,直至第一个不符合函数的元素,将其前面的元素都返回
1 | List list26 = [2,4,5,6,1]; |
- where(bool test(E element)) => WhereIterable(this, test) 根据指定的函数筛选,符合条件的元素组成新的Iterable
1 | List list27 = [3,1,0,5,7]; |
- singleWhere(bool test(E element), {E orElse()}) 根据函数找出唯一的元素,如果不唯一,则报错:Too many elements
1 | List list28 = [1,3,2,5,8]; |
- whereType() 从无指定类型的List中,筛选出指定类型的数据
1 | List list29 = [1,9,8,2,7,1.11,'abc']; |
- cast() 将List的泛型提升到期祖父类
1 | List<String> list30 = ['aa','bb','cc','dd']; |
- expand() 根据现有的List,指定一个规则,生成新的List
1 | List list32 = [1,4,3,6]; |
- toSet() 将List转化为Set 并去除后面重复的元素
1 | List list33 = [1,3,2,7,6,1,2]; |
- asMap() 将List转化为Map 索引为key,元素为value
1 | List list34 = [1,2,3,6,7,9]; |
- shuffle() 将List内元素重新随机排列,并且该函数没有返回值,直接修改该List
1 | List list35 = [9,1,2,5,3]; |
--
- sort() List自身排序
1 | List list36 = [1,5,6,7,8,8,9,3,8,9,8]; |
- sublist(int start, [int end]) 截取List 返回截取后的List
1 | List list37 = [2,5,1,7,9,10]; |
- getRange(int start, int end) 在List 中截取 start-end 之间
1 | List list38 = [8,9,1,0,3,5]; |
- join()拼接字符,返回String类型
1 | List list39 = ['a','cc','fc','xx']; |
- 清除所有元素 clear();
1 | //清除所有元素 clear() |
**
**