Flutter使用Dart开发,具有良好的性能和优秀的跨平台性,codepen.io目前已经支持Flutter。
Flutter vs React Native vs NativeScript vs Ionic vs PWAs | Maximilian Schwarzmüller, Udemy 干货|Flutter 原理与闲鱼深度实践
Dart是谷歌Fuchsia OS钦定的UI语言,专门针对UI开发进行过优化,Flutter很好地为Dart进行了背书。
Dart versus Node js fastest programs
Dart的一些特性:
- 跨平台且支持多端(dart VM、dart2native、dart2js)
- 支持AoT(Ahead of Time)编译
- 支持垃圾回收
- 支持热重载(JIT编译)
- 单线程(event-loop)
- 面向对象
- 强类型(strong-typed)语言,带类型推断功能
- 支持泛型
http://dartpad.dev/ 可以在线体验dart。不过更推荐使用 repl.it,功能更强大,比如支持引入第三方类库。
基础
变量与常量
用 var 关键字声明变量
var greeting = 'hello, dart'; // type inference
String hi = '你好'; // use specific type
int awesome = 666;
dynamic typeVariableVar = 'variable';
typeVariableVar = 233; // assign int to typeVariableVar is OK, cause it's type is dynamic
awesome = '666'; // Error: A value of type 'String' can't be assigned to a variable of type 'int'
用 const 关键字声明编译时常量
const HTTP_OK = 200;
const int errorCode = -1;
用 final 关键字声明不可重新赋值变量
final now = DateTime.now();
final numbers = <int>[1, 2, 3];
numbers.add(4); // cannot ressign numbers to other value, but can add value to it
numbers[0] = 0; // this is OK too, cause value in final collection is not final
numbers = [5, 6, 7]; // Error: Can't assign to the final variable 'numbers'
// value in const collection is also const
const otherNumbers = <dynamic>[233, 666, 'awsl'];
otherNumbers[0] = 886; // Uncaught Error: Unsupported operation
- 对dart而言一切类型皆对象,所以变量保存的是对象的引用。
- 未初始化的变量默认值为null
变量作用域
和其他绝大部分语言一样 dart 变量的作用域是静态作用域。关于全局作用域、动态作用域和静态作用域推荐阅读 《代码之髓》第七章进一步了解。
闭包(Closure)
函数内部可以包含函数所在作用域内的变量,即使函数在其作用域以外被调用。
基础数据类型
- num
- int
- double (IEEE 754)
var a = 1;
var b = 0xD800;
var c = 1.0;
var d = 1.0e10;
double e = 1; // Dart 2.1版本起 int类型字面量自动转换为double,所以flutter内很多组件属性类型是double但使用时可以自己传int
var one = int.parse('1');
int.parse('666'); // 666
int.parse('36', radix: 16); // 54 parse with radix
int.parse('0x29A'); // 666
double.parse('0.9'); // 0.9
123.456.toStringAsFixed(2) == '123.46');
123.456.toStringAsPrecision(2); // '1.2e+2'
123.456.toStringAsPrecision(4); // '123.5'
double.parse('1.23e+2'); // 123.0
- string
var str1 = 'hello';
var str2 = "hi";
var name = 'world';
var strWithVar = 'hello, $name';
var strWithExpr = 'hello, ${name.toUpperCase()}';
var strConcat = 'String '
'concatenation'
" works even over line breaks.";
var strConcat = 'The + operator ' + 'works, as well.';
var multilineStr = """a string break into
multi-line like this.""";
var rawStr = r'In a raw string, not even \n gets special treatment.';
var emojiStr = '😁';
assert(emojiStr.length == 2); // true
var unicodeStr = '\u{1f601}'; // 😁
var surrogateUnicodeStr = '\ud83d\ude00'; // 😁
for (var char in emojiStr.runes) {
print(char.toRadixString(16)); // 1f600
}
为了兼容 JavaScript, Dart字符串没有采用UTF-8而采用了UTF-16编码。
https://github.com/dart-lang/sdk/issues/36316
String 由 Runes(Unicode码点值)构成,要遍历字符串内的字符需要对runes进行遍历。
不能通过String构成函数创建字符串,只能通过字面量创建字符串
var str1 = 's';
var str2 = 's';
assert(str1 == str2); // true
- bool
只有true和false是bool类型,空字符串、null都不是bool
'' == false // false
// Uncaught Error: Assertion failed: "boolean expression must not be null"
var nullVar;
if (!nullVar) {
print();
}
''.isEmpty == true // true
nullVar == null // true
- List 有序数组
var arr = [1, 2, 3];
var constList = const [1, 2, 3];
var newList = [0, ...arr];
var nullArr;
var newArr = [0, ...?nullArr];
// control flow collection
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
- Set 无序集合
Set<String> countries = {};
var names = <String>{};
var languages = { 'chinese', 'english', 'japanese', 'german' };
Dart 2.3 开始,set支持展开运算符(…)和空感知展开运算符(…?)
- Map 映射
var chineseNumbers = {
0: '零',
1:'一',
2: '二'
};
var animals = Map(); // Dart 2开始创建对象时 new 操作符可选
animals['lion'] = '狮子';
animals['tiger'] = '老虎';
键和值都可以是任意类型 {} 字面量的类型默认是Map,所以用 {} 字面量初始化Set类型变量时需要注意 Dart 2.3 开始,set支持展开运算符(…)和空感知展开运算符(…?)
- Symbol
用于代表操作符或者标识符
You might never need to use symbols, but they’re invaluable for APIs that refer to identifiers by name, because minification changes identifier names but not identifier symbols
https://github.com/dart-lang/sdk/blob/9a618e5661665b8d687a28e6b1ec25e9177ec2d7/tests/corelib_2/symbol_operator_test.dart
https://dart.dev/guides/language/sound-dart
枚举(Enum)
和typescript的enum比起来,功能比较简单
enum Color { red, green, blue }
List<Color> colors = Color.values;
assert(colors[0] == Color.red);
assert(Color.red.index == 0);
print(Color.red.toString().split('.').last); // red
函数
first-class Object. 函数是一个对象,所以可以当作普通变量使用。
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; // => expr 等效于 { return expr; } 只有当函数体只包含一条表达式时才能使用
函数参数分为必选和可选两种类型,参数可以是具名参数也可以是不具名参数
void add(int op1, int op2) => op1 + op2;
// parameters wrapped by [] is optional positional parameters
String greeting(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
void sayHi({ String name }) {}
void sayHello({ @required String name }) {}
void enableFlags({bool bold = false, bool hidden = false}) {...}
// cascade, perform multiple operations on the members of a single object
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
// anonymous function
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List parameter for arguments.
运算符(operator)
dart支持运算符重载(override),如Duration类就重写了加减运算符。
Description | Operator |
---|---|
unary postfix | expr++ expr– () [] . ?. |
unary prefix | -expr !expr ~expr ++expr –expr await expr |
multiplicative | * / % ~/ |
additive | + - |
shift | « » »> |
bitwise AND | & |
bitwise XOR | ^ |
bitwise OR | |
relational and type test | >= > <= < as is is! |
equality | == != |
logical AND | && |
logical OR | |
if null | ?? |
conditional | expr1 ? expr2 : expr3 |
cascade | .. |
assignment | = *= /= += -= &= ^= ??= etc. |
可以看到除了常规的一些运算符外,dart还支持
// ?.(conditional member access)
// ??(if null)
// ??=(if null assignment)
// ..(cascade)
// ...(spread operator)
// ...?(null-aware spread operator)
// as(typecast)
true is bool // is(positive typecheck)
3 is! bool // is!(negative typecheck)
可以被重载的运算符
< | + | ||
> | / | ^ | []= |
<= | ~/ | & | ~ |
>= | * | « | == |
– | % | » |
!= 运算符是语法糖,e1 != e2 实际上等于 !(e1 == e2),所以不可重载。
== 运算符用于判断对象的相等性,可以通过覆盖 == 运算符的实现来修改 == 的行为。但是对 == 运算符的覆盖必须遵循等价关系(equivalence relation):
Total: It must return a boolean for all arguments. It should never throw or return
null
.
Reflexive: For all objects
o
,o == o
must be true.
Symmetric: For all objects
o1
ando2
,o1 == o2
ando2 == o1
must either both be true, or both be false.
Transitive: For all objects
o1
,o2
, ando3
, ifo1 == o2
ando2 == o3
are true, theno1 == o3
must be true.
覆盖 == 运算符时需要同时覆盖 hashCode 属性,相等的对象必须就有相同的 hashCode,但具有相同hashCode的对象不一定相等。
A hash code is a single integer which represents the state of the object that affects [operator ==] comparisons.
All objects have hash codes. Hash codes must be the same for objects that are equal to each other according to [operator ==].
The hash code of an object should only change if the object changes in a way that affects equality.
Objects that are not equal are allowed to have the same hash code.
class Duration implements Comparable<Duration> {
...
/**
* Adds this Duration and [other] and
* returns the sum as a new Duration object.
*/
Duration operator +(Duration other) {
return Duration._microseconds(_duration + other._duration);
}
/**
* Subtracts [other] from this Duration and
* returns the difference as a new Duration object.
*/
Duration operator -(Duration other) {
return Duration._microseconds(_duration - other._duration);
}
...
}
控制流(control flow)
和其他常见语言支持常规的控制流语句差不多,switch语句稍微有点区别。
// if..else
if (isChinese()) {
print('你好');
} else if (isJapanese()) {
print('こんにちは');
} else {
print('hello');
}
// for
var numbers = [0, 1, 2, 3];
for (var i = 0; i < numbers.length; i++) {
print(numbers[i]);
}
for (var number in numbers) {
print(number);
}
// while
var counter = 10;
while (counter > 0) {
counter--;
print(counter);
}
// do-while
var counter = 0;
do {
counter++;
print(counter);
} while (counter < 10);
// break
var counter = 10;
for (var i = 0; i < counter; i++) {
if (i == 5) {
break;
}
print(i); // prints 0 - 4
}
// continue
var counter = 10;
for (var i = 0; i < counter; i++) {
if (i == 5) {
continue;
}
print(i); // prints 0 - 4 6 - 9
}
// switch..case
var command = 'OPEN';
switch (command) {
case 'CLOSED':
handleClose();
break;
case 'OPEN':
handleOpen();
break;
case 'UNKOWN': // empty case clause is permitted
case 'PENDING':
handlePending();
continue done; // continue with labeled case clause
done:
case 'DONE':
handleDone();
break;
default:
executeUnknown();
}
// assert
assert(text != null);
assert(text != null, 'text should be initialized');
异常处理
- throw
throw SocketException("Socket is closed for reading");
throw ArgumentError('Index inside surrogate pair: $index');
throw UnsupportedError("Directory_SetCurrent");
- try..on
- try..catch
- try..on..catch
- finally
需要注意使用 on 语句捕获异常需要指明具体的异常类型(Exception是接口)
// handle one exception
try {
handleOrder();
} on FormatException {
handleException();
}
// handle multiple exceptions
try {
handleOrder();
} on Exception catch(e) {
print('unknown exception: $e');
} catch (e, s) {
print('yet another unknown exception: $e');
print('Stack trace:\n $s'); // s stands for stacktrace
} finally {
cleanUp();
}
类(Class)
支持基于mixin模式的类继承 支持通过extension方法在不修改原类的情况下对类进行扩展 支持常量构造函数
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b))
构造函数
构造函数分两类:
- 默认构造函数(和类同名的无参数函数,不声明构造函数时创建的构造函数)
- 具名构造函数
// 声明了构造函数 Point 类就不存在默认构造函数
class Point {
double x, y;
Point(this.x, this.y);
// Named constructor
Point.fromMap(Map data) {
x = data['x'];
y = data['y'];
}
// Redirect constructor, which just delegate to anthor constructor
Point.alongXAxis(double x) : this(x, 0);
}
// const objects that never change
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
// all instance variables must set to final
final double x, y;
// const constructor
const ImmutablePoint(this.x, this.y);
}
use factory constructor when:
- the constructor is expensive, so you want to return an existing instance - if possible - instead of creating a new one;
- you only ever want to create one instance of a class (the singleton pattern);
- you want to return a subclass instance instead of the class itself.
Constant constructors don’t always create constants
https://stackoverflow.com/questions/53886304/understanding-factory-constructor-code-example-dart
当声明了带参数且与类同名的函数时类就不具备默认构造函数了。当父类不存在默认构造函数时,子类初始化时需要先显式调用父类构造函数。
If the superclass doesn’t have an unnamed, no-argument constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any).
在构造函数调用前和父类构造函数调用后可以进行一些初始化操作(Initializer list),每一步用逗号分隔。如flutter内常见的组件初始化参数校验和赋值:
// 调用AppBar构造函数前会先对参数进行校验、设置初始值、调用父类构造函数等一系列操作
AppBar({
Key key,
this.leading,
this.automaticallyImplyLeading = true,
this.title,
this.actions,
this.flexibleSpace,
this.bottom,
this.elevation,
this.shape,
this.backgroundColor,
this.brightness,
this.iconTheme,
this.actionsIconTheme,
this.textTheme,
this.primary = true,
this.centerTitle,
this.excludeHeaderSemantics = false,
this.titleSpacing = NavigationToolbar.kMiddleSpacing,
this.toolbarOpacity = 1.0,
this.bottomOpacity = 1.0,
}) : assert(automaticallyImplyLeading != null),
assert(elevation == null || elevation >= 0.0),
assert(primary != null),
assert(titleSpacing != null),
assert(toolbarOpacity != null),
assert(bottomOpacity != null),
preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
super(key: key);
this
this即代表当前对象实例
静态变量与静态方法
即类变量和类方法,属于类不属于任何类的实例
class Point {
static const size = 16;
double x, y;
Point(this.x, this.y);
static double distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
assert(Queue.size == 16);
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
print(distance);
Getter/Setter
实例变量默认包含对应的getter/setter方法
class People {
final String firstname;
final String lastname;
People({
@required this.firstname,
@required this.lastname,
});
String get fullname {
return '$firstname $lastname';
}
}
var noone = People('john', 'doe');
print(noone.fullname); // 'john doe'
Callable
if you wanna call object instance like a function, implements call() method.
class CallableClass {
String call(String name) => 'hello $name!';
}
var cc = CallableClass();
var greeting = cc('john');
print(greeting);
继承(extends)
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
}
class SmartTelevision extends Television {
@override
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
}
Mixin
https://en.wikipedia.org/wiki/Mixin
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
和Mixin模式类似的还有Trait模式(rust采用),二者的比较参见:
https://stackoverflow.com/questions/925609/mixins-vs-traits
扩展(Extension)
Dart 2.7 添加了扩展方法支持。通过扩展方法可以在不修改类库的情况下对其进行扩展。
int.parse('42');
// lib/string_apis.dart
// extend String class add toInt method
extension String2Number on String {
int toInt() {
return int.parse(this);
}
}
import 'string_apis.dart';
print('42'.toInt());
https://dart.dev/guides/language/extension-methods
抽象类(Abstract Class)
abstract class Human {
void read(); // Abstract method.
}
接口(interface)
dart没有单独的interface接口,类可以充当接口
class Person {
// In the interface, but visible only in this library.
final _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String name) => 'Hello, $name. I am $_name.';
}
// An implementation of the Person interface.
class Chinese implements Person, Comparable {
int age;
get _name => '';
String greet(String name) => '$name 你好';
static int compare(Comparable a, Comparable b) => a.age - b.age;
}
泛型(Generics)
// for type safty
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var moreNames = <String>['Tom', 'Joe'];
names.add(42); // Error
// reduce duplication
abstract class NumberCache {
num getByKey(String key);
void setByKey(String key, Object value);
}
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
// When T is String it works the same as StringCache
// When T is num it works the same as NumberCache
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
// key of map limited to int, value of map limited to Page object
var pages = Map<int, Page>();
// only object that implements Comparable interface allowed
var ranking = List<T implements Comparable>();
// only instances of Animal and it's sub class allowed
var ranking = List<T extends Animal>();
包(Package)
- 所有dart程序都是一个库
- 库以包(pacakge)的形式进行分发
- 私有变量和方法以下划线开头
- 通过import语句引用库
- 编译到js代码时支持懒加载(lazy loading)
// use dart sdk library
import 'dart:async';
// use third-party package
import 'package:flutter/material.dart';
// import same library code by relative path
import '../utils/helper.dart';
// partial import
import 'package:lib1/lib1.dart' show foo; // import foo only
import 'package:lib2/lib2.dart' hide foo; // import all others except foo
// use alias to resolve conflicts
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// lazy load for dart2js
import 'package:greetings/hello.dart' deferred as hello;
https://dart.dev/guides/libraries/create-library-packages
异步
Future
A Future is used to represent a potential value, or error, that will be available at some time in the future.
顾名思义,Future就是指未来某个时间点得到的结果。和JavaScript Promise一样,Future要么返回一个结果值,要么返回一个错误。
Future存在两种状态:
- uncompleted
- completed(with a value or error)
// onError is optional
Future<int> successor = future.then(
(int value) {
// Invoked when the future is completed with a value.
return 42; // The successor is completed with the value 42.
},
onError: (e) {
// Invoked when the future is completed with an error.
if (canHandle(e)) {
return 499; // The successor is completed with the value 499.
} else {
throw e; // The successor is completed with the error e.
}
}
);
import 'dart:async';
import 'dart:io';
void main() {
new File('file.txt').readAsString().then((String contents) {
print(contents);
});
}
Stream
可以类比工厂流水线,数据就好比原材料在流水线上流动,每个环节的工人对数据进行处理,最后得到完整的制品。
Dart里 Stream 是一个事件序列。通过对事件进行订阅(StreamSubscription)
A Stream provides a way to receive a sequence of events
https://en.wikipedia.org/wiki/Stream_(computing)
https://dart.dev/tutorials/language/streams
https://api.dart.dev/stable/2.8.3/dart-async/Stream-class.html
- Single-subscription
- Broadcast
按规定,Stream 在有订阅者时才开始运行
Generator
- sync*
- async*
Stream<int> timedCounter(Duration interval, [int maxCount]) async* {
int i = 0;
while (true) {
await Future.delayed(interval);
yield i++;
if (i == maxCount) break;
}
}
StreamController
take full control of stream by use StreamController
Stream<int> timedCounter(Duration interval, [int maxCount]) {
StreamController<int> controller;
Timer timer;
int counter = 0;
void tick(_) {
counter++;
controller.add(counter); // Ask stream to send counter values as event.
if (counter == maxCount) {
timer.cancel();
controller.close(); // Ask stream to shut down and tell listeners.
}
}
void startTimer() {
timer = Timer.periodic(interval, tick);
}
void stopTimer() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
controller = StreamController<int>(
onListen: startTimer,
onPause: stopTimer,
onResume: startTimer,
onCancel: stopTimer);
return controller.stream;
}
await/async
Future<void> printOrderMessage() async {
print('Awaiting user order...');
var order = await fetchUserOrder();
print('Your order is: $order');
}
Future<String> fetchUserOrder() {
return Future.delayed(Duration(seconds: 2), () => 'Large Latte');
}
Future<void> main() async {
countSeconds(4);
await printOrderMessage();
}
void countSeconds(int s) {
for (var i = 1; i <= s; i++) {
Future.delayed(Duration(seconds: i), () => print(i));
}
}
// output
Awaiting user order...
1
2
Your order is: Large Latte
3
4
Isolate
源码注释对Isolate的说明如下:
independent workers that are similar to threads but don’t share memory, communicating only via messages.
Each isolate has its own memory heap and event-loop, ensuring that no isolate’s state is accessible from any other isolate
dart codes runs in isolate
Meta programming
Annotation
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
// @override to mark subclass override parent's method
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
@Deprecated(
'Use CalendarDatePicker instead. '
'This feature was deprecated after v1.15.3.'
)
class YearPicker extends StatefulWidget {
...
}
class Foo extends StatelessWidget {
const Foo({
Key key,
@required Widget child,
}) : super(key: key, child: child);
}