一、前言
Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,Flutter 开源、免费,拥有宽松的开源协议,支持移动、Web、桌面和嵌入式平台。
Flutter是使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行Android和IOS开发。Flutter采用Dart语言进行开发,而并非Java,Javascript这类热门语言,这是Flutter团队对当前热门的10多种语言慎重评估后的选择。因为Dart囊括了多数编程语言的优点,它更符合Flutter构建界面的方式。
本文主要就是简单梳理一下Dart语言的一些基础知识和语法。关于编程语言的基本语法无外乎那么些内容,注释、变量、数据类型、运算符、流程控制、函数、类、异常、文件、异步、常用库等内容,相信大部分读者都是有一定编程基础的,所以本文就简单地进行一个梳理,不做详细的讲解。大家也可以参考 Dart编程语言中文网。
上一篇文章主要是写了Dart语言的一些基本语法,本文将接着上一篇文章继续往后写。
二、Dart中的流程控制
流程控制涉及到的基本语法其实很简单,但是这一块也是编程语言基础中最难的一部分,主要难点在于解决问题的逻辑思路,流程控制知识实现我们解决问题的逻辑思路的一种表达形式。所以,大家在学习编程语言的过程中,学习基本语法是一部分,更重要的部分其实是锻炼自己解决问题的逻辑能力,而这一块的加强,必须加以大量的练习才能熟练掌握。本文主要是给大家罗列一下Dart中的流程控制相关的基本语法。
流程控制主要涉及到的内容无外乎条件分支结构、switch分支结构和循环结构,此外,还有一些特殊的语法break、continue等。对于有过编程经验的同学而言,这些内容都是so easy。下面就简单给大家罗列一下。
2.1 条件分支结构
Dart 中的条件分支结构就是 if - else
语句,其中 else
是可选的,Dart 的if判断条件必须是布尔值,不能是其他类型。比如下面的例子。
if (isRaining()) { you.bringRainCoat(); } else if (isSnowing()) { you.wearJacket(); } else { car.putTopDown(); }
2.2 switch分支结构
在 Dart 中 switch 语句使用 ==
比较整数,字符串,或者编译时常量。 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 ==
重写。 枚举类型 可以用于 switch
语句。在 case
语句中,每个非空的 case
语句结尾需要跟一个 break
语句。 除 break
以外,还有可以使用 continue
, throw
,者 return
。当没有 case
语句匹配时,执行 default
代码
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(); }
// case 程序示例中缺省了 break 语句,导致错误 switch (command) { case 'OPEN': print('open'); // ERROR: 丢失 break case 'CLOSED': print('close'); break; }
// Dart 支持空 case 语句, 允许程序以 fall-through 的形式执行 var command = 'CLOSED'; switch (command) { case 'CLOSED': // Empty case falls through. case 'NOW_CLOSED': // Runs for both CLOSED and NOW_CLOSED. executeNowClosed(); break; } // 在非空 case 中实现 fall-through 形式, 可以使用 continue 语句结合 lable 的方式实现 var command = 'CLOSED'; switch (command) { case 'CLOSED': executeClosed(); continue nowClosed; // Continues executing at the nowClosed label. nowClosed: case 'NOW_CLOSED': // Runs for both CLOSED and NOW_CLOSED. executeNowClosed(); break; }
2.3 循环结构
和其他编程语言中的循环结构一样,Dart中的循环结构也是有for、while、do...while三种,这三种循环结构可以相互转换,大家根据自己的编程习惯进行选择即可。
2.3.1 for循环
进行迭代操作,可以使用标准 for
语句。 例如:
var message = StringBuffer('Dart is fun'); for (var i = 0; i < 5; i++) { message.write('!'); }
闭包在 Dart 的 for
循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。 请思考示例代码:
var callbacks = []; for (var i = 0; i < 2; i++) { callbacks.add(() => print(i)); } callbacks.forEach((c) => c());
和期望一样,输出的是 0
和 1
。
// 如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法, 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择 candidates.forEach((candidate) => candidate.interview()); //实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration var collection = [0, 1, 2]; for (var x in collection) { print(x); // 0 1 2 }
2.3.2 while和do...while循环
// while 循环在执行前判断执行条件: while (!isDone()) { doSomething(); } // do-while 循环在执行后判断执行条件: do { printLine(); } while (!atEndOfPage());
2.4 break和continue语句
使用 break
停止程序循环:
while (true) { if (shutDownRequested()) break; processIncomingRequests(); }
使用 continue
跳转到下一次迭代:
for (int i = 0; i < candidates.length; i++) { var candidate = candidates[i]; if (candidate.yearsExperience < 5) { continue; } candidate.interview(); }
如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么上面示例完全可以用另一种方式来实现:
candidates .where((c) => c.yearsExperience >= 5) .forEach((c) => c.interview());
2.5 assert语句
如果 assert
语句中的布尔条件为 false , 那么正常的程序执行流程会被中断。 下面是一些示例:
// 确认变量值不为空。 assert(text != null); // 确认变量值小于100。 assert(number < 100); // 确认 URL 是否是 https 类型。 assert(urlString.startsWith('https'));
- assert 的第一个参数可以是解析为布尔值的任何表达式。 如果表达式结果为 true , 则断言成功,并继续执行。 如果表达式结果为 false , 则断言失败,并抛出异常 (AssertionError) 。
- assert 的第二个参数可以为其添加一个字符串消息。
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
三、Dart中的函数
Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function 。 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。 也可以把 Dart 类的实例当做方法来调用。
3.1 函数的定义
下面是函数实现的示例:
// 模板 returnType funcName(paramsList) { // function code // return statement } bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] != null; }
3.1.1 可选参数
函数有两种参数类型: required(必需参数,函数调用时不传就会报错) 和 optional(可选参数,函数调用时可以不传)。 required 类型参数在参数最前面, 随后是 optional 类型参数。 命名的可选参数也可以标记为 “@required” 。
可选参数可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰。
- 命名可选参数:定义函数时,使用
{param1, param2, …}
来指定命名参数,并且可以使用 @required 注释表示参数是 required 性质的命名参数。调用函数时,可以使用指定命名参数paramName: value
。// 定义函数是,使用 {param1, param2, …} 来指定命名参数: void enableFlags({bool bold, bool hidden}) {...} // 调用函数时,可以使用指定命名参数 paramName: value。 例如: enableFlags(bold: true, hidden: false); // 使用 @required 注释表示参数是 required 性质的命名参数, 该方式可以在任何 Dart 代码中使用(不仅仅是Flutter)。 // 此时 Scrollbar 是一个构造函数, 当 child 参数缺少时,分析器会提示错误。 const Scrollbar({Key key, @required Widget child})
位置可选参数:将参数放到
[]
中来标记参数是可选的,调用函数时,按位置顺序传递参数。// 将参数放到 [] 中来标记参数是可选的: String say(String from, String msg, [String device]) { var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } return result; } // 下面是不使用可选参数调用上面方法 的示例: assert(say('Bob', 'Howdy') == 'Bob says Howdy'); // 下面是使用可选参数调用上面方法的示例: assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');
3.1.2 默认参数
在定义方法的时候,可以使用 =
来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。
注意:旧版本代码中可能使用的是冒号 (:
) 而不是 =
来设置参数默认值。 原因是起初命名参数只支持 :
。 这种支持可能会被弃用。 建议 使用 =
指定默认值。
下面是设置可选参数默认值示例:
/// 设置 [bold] 和 [hidden] 标志 ... void enableFlags({bool bold = false, bool hidden = false}) {...} // bold 值为 true; hidden 值为 false. enableFlags(bold: true);
下面示例演示了如何为位置参数设置默认值:
String say(String from, String msg, [String device = 'carrier pigeon', String mood]) { var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } if (mood != null) { result = '$result (in a $mood mood)'; } return result; } assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
3.1.3 返回值
所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null;
语句。
foo() {} assert(foo() == null);
3.2 main()函数
任何应用都必须有一个顶级 main()
函数,作为应用服务的入口。 main()
函数返回值为空,参数为一个可选的 List<String>
。
下面是 web 应用的 main()
函数:
void main() { querySelector('#sample_text_id') ..text = 'Click me!' ..onClick.listen(reverseText); }
下面是一个命令行应用的 main()
方法,并且使用了输入参数:
// 这样运行应用: dart args.dart 1 test void main(List<String> arguments) { print(arguments); assert(arguments.length == 2); assert(int.parse(arguments[0]) == 1); assert(arguments[1] == 'test'); }
3.3 匿名函数
多数函数是有名字的, 比如 main()
和 printElement()
。 也可以创建没有名字的函数,这种函数被称为 匿名函数。 匿名函数可以赋值到一个变量中, 举个例子,在一个集合中可以添加或者删除一个匿名函数。
匿名函数和命名函数看起来类似— 在括号之间可以定义一些参数或可选参数,参数使用逗号分割。后面大括号中的代码为函数体:
([[Type] param1[, …]]) { codeBlock; }; // 下面例子中定义了一个包含一个无类型参数 item 的匿名函数。 list 中的每个元素都会调用这个函数,打印元素位置和值的字符串。 var list = ['apples', 'bananas', 'oranges']; list.forEach((item) { print('${list.indexOf(item)}: $item'); });
3.4 箭头函数
不管是匿名函数还是命名函数,如果函数中只有一句表达式,可以使用箭头语法,简写如下:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
提示: 在箭头 (=>) 和分号 (;) 之间只能使用一个 表达式 ,不能是 语句 。 例如:不能使用 if 语句 ,但是可以是用 条件表达式.
3.5 函数是一等对象
一个函数可以作为另一个函数的参数。 例如:
void printElement(int element) { print(element); } var list = [1, 2, 3]; // 将 printElement 函数作为参数传递。 list.forEach(printElement);
同样可以将一个函数赋值给一个变量,例如:
// 使用匿名函数 var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; assert(loudify('hello') == '!!! HELLO !!!');
3.6 变量的作用域
Dart 是一门词法作用域的编程语言,就意味着变量的作用域是固定的, 简单说变量的作用域在编写代码的时候就已经确定了。 花括号内的是变量可见的作用域。下面示例关于多个嵌套函数的变量作用域:
bool topLevel = true; void main() { var insideMain = true; void myFunction() { var insideFunction = true; // 注意 nestedFunction() 可以访问所有的变量, 一直到顶级作用域变量。 void nestedFunction() { var insideNestedFunction = true; assert(topLevel); assert(insideMain); assert(insideFunction); assert(insideNestedFunction); } } }
3.7 闭包
3.7.1 闭包的概念
闭包这个概念好难理解,身边朋友们好多都稀里糊涂的,我也是学习了很久才理解这个概念。下面请大家跟我一起理解一下,如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。
函数可以封闭定义到它作用域内的变量。 接下来的示例中, makeAdder()
捕获了变量 addBy
。 无论在什么时候执行返回函数,函数都会使用捕获的 addBy
变量。
/// 返回一个函数,返回的函数参数与 [addBy] 相加 Function makeAdder(num addBy) { // //返回的函数就是一个闭包,封闭了局部变量 addBy return (num i) => addBy + i; } void main() { // 创建一个加 2 的函数。 var add2 = makeAdder(2); // 创建一个加 4 的函数。 var add4 = makeAdder(4); assert(add2(3) == 5); assert(add4(3) == 7); }
3.7.2 闭包的特点
由于变量的作用域的限制,全局变量可以在整个代码范围内使用,但是带来的问题就是任何地方都可以修改该全局变量,函数内局部变量又只能在函数内部使用。所以闭包就让外部访问函数内部变量成为可能,同时也让局部变量可以常驻在内存中。
- 让外部访问函数内部变量成为可能;
- 局部变量会常驻在内存中;
- 可以避免使用全局变量,防止全局变量污染;
- 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中; 局部变量闭包升级版(中间引用的变量) => 自由变量;
四、异常
Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行。和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
Dart 提供了 Exception 和 Error 类型, 以及一些子类型。 当然也可以定义自己的异常类型。 但是,此外 Dart 程序可以抛出任何非 null 对象, 不仅限 Exception 和 Error 对象。
4.1 抛出异常 throw
下面是关于抛出或者 引发 异常的示例:
throw FormatException('Expected at least 1 section');
也可以抛出任意的对象:
throw 'Out of llamas!';
因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
4.2 异常处理 try...catch...finally
Dart中的异常处理和Java中的比较类似,也是使用try...catch...finally的语句进行处理,不同的是,Dart中海油一个特殊的关键字on。使用 on 来指定异常类型, 使用 catch 来 捕获异常对象,捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。
捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。 可以通过捕获异常的机会来处理该异常:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。 与抛出异常类型匹配的第一个 catch 语句处理异常。 如果 catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象:
// 捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。 try { breedMoreLlamas(); } on OutOfLlamasException { // 一个特殊的异常 buyMoreLlamas(); } on Exception catch (e) { // 其他任何异常 print('Unknown exception: $e'); } catch (e) { // 没有指定的类型,处理所有异常 print('Something really unknown: $e'); }
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'); }
如果仅需要部分处理异常, 那么可以使用关键字 rethrow
将异常重新抛出。
void misbehave() { try { dynamic foo = true; print(foo++); // Runtime error } catch (e) { print('misbehave() partially handled ${e.runtimeType}.'); rethrow; // Allow callers to see the exception. } } void main() { try { misbehave(); } catch (e) { print('main() finished handling ${e.runtimeType}.'); } }
不管是否抛出异常, finally
中的代码都会被执行。 如果 catch
没有匹配到异常, 异常会在 finally
执行完成后,再次被抛出。如果catch捕获到异常,那么先执行catch中的处理代码,然后再执行finally中的代码。总而言之,finally语句块中的代码一定会被执行,并且是在最后被执行。
try { breedMoreLlamas(); } finally { // Always clean up, even if an exception is thrown. cleanLlamaStalls(); } // 任何匹配的 catch 执行完成后,再执行 finally try { breedMoreLlamas(); } catch (e) { print('Error: $e'); // Handle the exception first. } finally { cleanLlamaStalls(); // Then clean up. }