Dart 基本语法
date
Feb 6, 2022
slug
dart
status
Published
tags
理解笔记
summary
任何 保存在变量中的都是一个 对象, 并且所有对象都是 对应类 的实例...
type
Post
Dart 概念变量内建类型可选参数命名位置运算符相对JS没有的运算符级联运算符流程控制语句泛型使用集合字面量使用泛型类型的构造函数运行时的泛型集合限制泛型类型使用泛型函数库在import语句后面输入库文件的URI设置库前缀只导入库的一部分懒加载库实现一个库part&part ofexportTypedefsDart 的构造类构造函数命名式构造函数常量构造函数调用父类非默认构造函数初始化列表重定向构造函数工厂构造函数实例方法Getter 和 Setter抽象类/方法隐式接口拓展一个类重写父类成员重载运算符noSuchMethod使用 Mixin 为类添加功能类变量和方法
Dart 概念
- 任何 保存在变量中的都是一个 对象, 并且所有对象都是 对应类 的实例.
- 尽管
Dart
是强类型的, 但是 Dart 可以 推断类型(就是可以根据 value 推断类型), 所以类型注释可可选的.
- 如果明确说明 不需要任何类型, 需要使用特殊类型声明
dynamic
.
- 支持泛型.
如: List <dynamic>
.
- 支持顶级函数.
如: main()
- 同样函数绑定在类或对象上(静态/实例)函数.
- 支持(嵌套/局部)函数.
- 顶级函数就是在最外层定义的函数.
- 支持顶级变量
- 同样变量绑定在类或对象上(静态/实例)变量.
- 实例变量有时成为字段或属性.
- 如果标识符以下划线(_)开头, 则是私有库/变量/方法等, 外部不可访问.
- 风格指南中 var 变量仅存储对象引用.
==
运算符用来测试两个对象是否相等.
..
语法为 级联调用 (cascade)。 使用级联调用, 可以简化在一个对象上执行的多个操作。
变量
- var: 变量
- dynamic: 动态类型
- final: 常量
- final 只能被赋值一次, 顶层的final变量或者类的final变量在其第一次使用的时候被初始化
- 实例变量可以是
final
类型但不能是const
类型 - final 实例变量必须在构造器开始前被初始化, 或者作为构造器参数, 或者将其置于构造器的初始化列表中.
- const: 编译时常量
- null: 未初始化的变量默认值
内建类型
- Number(int/double)
- int: 整数值不大于 64 位
- double: 64(双精度)浮点数
- String(string)
- 'hello \$\{表达式\} world \$标识符'
- 连续三个单引号可实现多行字符串的创建, 需
toString()
转换 - 使用
r
前缀, 可以创建 原始 raw 字符串
- Boolean(bool)
- List(List)
- Map
- Map 是用来关联 keys 和 values 的对象
- Set
- 是一个元素唯一且无需的集合
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
- Rune(用于在字符串中标识 Unicode 字符)
- Symbol
- 表示 Dart 程序中声明的运算符或者标识符
可选参数
- {}内的参数为 命名可选参数
- []内的参数为 位置可选参数
- 参数的用
=
来赋值默认值
- 一个参数只能选择其中一种方式修饰
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
doStuff(list: [123, 344], gifts: {
'first': 'zhangsan'
})
命名
printPerson(String name,{int age = 14,String gender}){
print("name=$name,age=$age,gender=$gender");
}
printPerson("李四");
printPerson("李四",age: 20);
printPerson("李四",age: 20,gender: "Male");
printPerson("李四",gender: "Male");
位置
printPerson2(String name,[int age = 15,String gender]){
print("name=$name,age=$age,gender=$gender");
}
printPerson2("张三");
printPerson2("张三",18);
printPerson2("张三",18,"Female");
运算符
相对JS没有的运算符
// 除后向下取整
10 ~/ 2.1 = 2;
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
// 使用 as 运算符进行缩写:
(emp as Person).firstName = 'Bob';
// 当 b 为 null 时, 才会赋值给 b
b ??= value;
// 如果实例类 p 成员变量 y 为非 null,则设置它变量 y 的值为 4
p?.y = 4;
级联运算符
示例:
querySelector('#confirm') // 获取对象
..text = 'Confirm' // 调用成员变量
..classes.add('important') // 添加类名
..onClick.listen((e) => window.alert('Confirmed!')); // 注册事件
嵌套:
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
流程控制语句
if-else
判断条件必须是 布尔值
assert
语句中的布尔条件为false
, 会中断程序(只在开发环境有效).
泛型
在类型安全上通常需要泛型支持, 它的好处不仅仅是保证代码的正常运行:
- 正确指定泛型类型可以 提高代码质量.
- 使用泛型可以 减少重复的代码.
// T 是一个备用类型, 属于类型占位符, 在开发者调用该接口的时候会指定具体类型.
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
使用集合字面量
var names = <String>['Seth', 'Kathy', 'Lars'];
var ages = <num>[1,21,2,3];
var aaa = <dynamic>[1,21,2,3, 'sad', {'name': 'zhang'}, [12,21,3]];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
使用泛型类型的构造函数
// 定义 Set 数据接口中类型为 string
var nameSet = Set<String>.from(names);
// 创建了一个 key 为 integer, value 为 View 的 map 对象:
var views = Map<int, View>();
运行时的泛型集合
Dart 中泛型类型是 固化的, 也就是说在 运行时是携带者类型信息的.
限制泛型类型
使用泛型类型的时候, 可以使用 extends 实现参数类型的限制.
使用泛型函数
T first<T>(List<T> ts) {
// 做一些初始化工作, 然后...
T tmp = ts[0];
// 做一些额外的处理和检查, 然后...
return tmp;
}
- 函数的返回值类型(
T
)
- 参数的类型(
List<T>
)
- 局部变量的类型(T tep)
库
在import语句后面输入库文件的URI
// 引入第三方库文件
import 'package:flutter/material.dart';
// 引入项目中的文件
import './animated_cross_fade_demo.dart';
// 引入 dart 提供的库文件
import 'dart:html
设置库前缀
// 当不同库出现同样标识符, 可以用库前缀解决解决命名冲突
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
只导入库的一部分
import 'package:lib1/lib1.dart' show foo; // 仅导入 foo
import 'package:lib2/lib2.dart' hide foo; // 除了 foo 外都导出
懒加载库
// 当使用的时候在加载
import 'package:deferred/hello.dart' deferred as hello;
// 使用的时候
hello.loadLibrary();
实现一个库
使用 `library` 加上一个标识符 `point` 定义当前库的名字
library point;
part&part of
如果一个库的所有代码都卸载一个文件中, 会导致文件太大不好维护, 可以用
part
& part of
关键字拆分文件.library
语句所在的主文件中可以使用 import
和 part
语句, 但是 part of
所在的实现文件中不能使用任何 import/library/part语句. 库使用的所有 import 和 part 语句都必须放在主文件中声明.// 主文件 定义一个 math 库。它由 base,random 两部分组成
library math;
part 'base.dart';
part 'random.dart';
// 在 base.dart 文件的开头
part of math
// 在 random.dart 文件的开头
part of math;
export
可以使用 export 语句重新导出库.
比如:
把多个较小的库组合为一个较大的库或者重新导出库的一部分作为一个新的库.
既可以导出库的全部, 也可以导出库的一部分(使用 show 和 hide).
Typedefs
// 定义函数参数类型与返回值
typedef int CalFunc(int num1, int num2);
main() {
int num1 = 1;
int num2 = 2;
// 参数调用与传递
int calculate(CalFunc func) {
return func(num1, num2);
};
int result = calculate((int num1, int num2) {
return num1 - num2;
});
print(result);
}
Dart 的构造类
构造函数
- 默认构造函数
- 如果没有声明构造函数, 那么 Dart 会自动生成一个
无参数的构造函数
并且回调用父类的无参数构造方法
- 构造函数不会被继承
命名式构造函数
构造函数可以一俩种方式命名
类名
或 类名.标识符
的形式class Person {
final int age;
final String name;
Person(this.age, this.name);
}
class Person1 {
final int age;
final String name;
Person1.aaa(this.age, this.name);
}
main() {
// Person 和 Person1 的构造函数是等价的
var zhang = Person(12, 'zhang');
var li = Person1.aaa(43, 'li');
print(zhang.age);
print(li.age);
}
常量构造函数
如果类生成的对象都是不会变的, 那么可以在生成这些对象时声明为编译时的常量
使用常量构造函数, 需要在构造函数之前加 const 关键字, 来创建编译时的常量
void main() {
var p = const ImmutablePoint(2, 2);
var c = const ImmutablePoint(2, 2);
// 如果上面俩个初始化没有加 const, 下面的结果为 false
print(identical(p,c)); // true
}
class ImmutablePoint {
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
根据上下文, 保留第一个
const
关键字即可const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
调用父类非默认构造函数
如果子类构造函数还有一个
初始化列表
, 调用顺序如下:- 初始化列表
- 父类的无参数构造函数
- 当前类的构造函数
如果父类没有匿名无参数构造函数, 那么子类必须调用父类的其中一个构造函数
为子类的构造函数指定一个父类构造函数只需在构造函数体前使用
:
指定void main() {
Employee.fromJson({});
}
class Person {
String fristName;
Person.fromJson(Map data) {
print('in Person'); // 1
}
}
class Employee extends Person {
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee'); // 2
}
}
参数会在子类构造函数被执行前传递给父类的构造函数, 因此该参数也可以是一个表达式, 比如一个函数
// ...
class Employee extends Person {
Employee(data) : super.fromJson(data);
// ···
}
main() {
var emp = new Employee({});
}
注意:
传递给父类构造函数不能使用
this
关键字, 因为父类构造函数执行时, 子类的实例还未实例化,
所以所有的实例成员都不能访问, 但是类成员可以.初始化列表
命名式构造函数初始化列表
class Point {
double x, y;
Point.fromJson(Map<String, double> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
main() {
Map<String, double> aaa = <String, double>{
'x': 12.34,
'y': 123.5,
};
var s = Point.fromJson(aaa);
print(s.x);
}
默认构造函数初始化列表
import 'dart:math';
class Point {
// 声明 final 常量, 实例化对象之前, final 必须被赋值
final num x;
final num y;
final num distanceFromOrigin;
// 构造函数初始化为三个 final 常量赋值
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}
注意: 初始化列表表达式 = 右边的语句不能使用 this 关键字.
重定向构造函数
class Point {
double x, y;
// 类的主构造函数
Point(this.x, this.y);
// 委托给主构造函数
Point.aloneXAxis(double x) : this(x, 0);
}
main () {
var s = Point.aloneXAxis(312);
print(s.x);
print(s.y);
}
工厂构造函数
void main() {
var logger = Logger('UI');
logger.log('Button clicked');
var logger1 = Logger('asdasd');
logger.log('as大萨达');
}
class Logger {
final String name;
bool mute = false;
static final Map<String, Logger> _cache = <String, Logger>{};
// 工厂构造函数, 自执行
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
// 命名形式的构造函数, 调用才会执行
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
实例方法
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
main() {
var s = Point(213, 324);
print(s.y);
print(s.distanceTo(Point(2,40)));
}
Getter 和 Setter
void main() {
var rect = Rectangle(3, 4, 20, 15);
print(rect.left);
assert(rect.left == 3);
rect.right = 12;
print(rect.left);
assert(rect.left == -8);
}
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
final
属性没有Setter
方法.
抽象类/方法
实例方法的 Getter 和 Setter 方法都是可以抽象的, 定义一个接口方法而不去做具体实现, 让实现它的类去实现该方法, 抽象方法只能存在于
抽象类
中.抽象类将无法被实例化, 抽象类常用语声明接口方法, 有时也会有具体的方法实现.
如果想让抽象类同时可被实例化, 可以为其定义为工厂构造函数.
abstract class Doer {
void doSomething();
}
class EffectiveDoer extends Doer {
void doSomething() {
print('asdsad');
}
}
main() {
var p = EffectiveDoer();
p.doSomething();
}
隐式接口
每一个类都隐式的定义了一个接口并实现了该接口, 这个接口包含所有这个类的实例成员以及这个类所实现的其它接口.
如果想要创建一个A类支持调用 B 类的 API 且又不想继承 B 类, 则可以实现 B 类的接口.
一个类可以通过关键字
implements
来实现一个或多个接口并实现每个接口定义的API:class Person {
final _name;
Person(this._name);
String greet(String who) => '你好, $who, 我是$_name';
}
class Impostor implements Person {
// 通过 implements 继承了 Person 就必须将里面的实例成员及接口全部重写一遍, 否则编译会报错.
get _name => '';
String greet(String who) => '你好$who, 你知道我是who吗';
}
String greetBob(Person person) => person.greet('小芳');
main() {
print(greetBob(Person('小云')));
print(greetBob(Impostor()));
}
拓展一个类
class a {
void turrOn() {
print('1');
print('2');
}
}
class ab extends a {
void turrOn() {
super.turrOn();
print(3);
print(4);
}
}
void main() {
var testAb = ab();
var testA = a();
testAb.turrOn();
testA.turrOn();
}
重写父类成员
子类可以重写父类的实例方法/Getter/Setter方法, 可以使用
@override
注解标识你重写了一个成员:class ab extends a {
@override
void turrOn() {
print(3);
print(4);
}
}
重载运算符
void main() {
final v = new Vector(2, 3);
final w = new Vector(2, 2);
// v == (2, 3)
assert(v.x == 2 && v.y == 3);
// v + w == (4, 5)
assert((v + w).x == 4 && (v + w).y == 5);
// v - w == (0, 1)
assert((v - w).x == 0 && (v - w).y == 1);
assert((v - w).x == 0);
}
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
/// 覆写 + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
/// 覆写 - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
noSuchMethod
class A {
// 除非你重写 noSuchMethod,否则调用一个不存在的成员会导致 NoSuchMethodError。
@override
void noSuchMethod(Invocation invocation) {
print('你尝试使用一个不存在的成员:' +
'${invocation.memberName}');
}
}
void main() {
// 1. 当接收方是 dynamic 类型的话, 才可以调用不存在的方法和属性
// 2. ...
dynamic a = A();
a.ccc();
}
使用 Mixin 为类添加功能
Mixin 是一种多重继承中复用某个类中代码的方法模式
使用 on 关键字 可以指定那些类可以使用改 Mixin.
// 只有 BaseClass 类可以使用 TestMixin 这个 mixin.
mixin TestMixin on BaseClass {
void init() {
// 9
print('TestMixin init start'); // 10
super.init(); // 11
print('TestMixin init end'); // 14
}
}
mixin TestMixin2 on BaseClass {
void init() {
// 6
print('TestMixin2 init start'); // 7
super.init(); // 8
print('TestMixin2 init end'); // 15
}
}
class BaseClass {
void init() {
// 12
print('Base init'); // 13
}
BaseClass() {
// 1
init(); // 2
}
}
class TestClass extends BaseClass with TestMixin, TestMixin2 {
@override
void init() {
// 3
print('TestClass init start'); // 4
super.init(); // 5
print('TestClass init end'); // 16
}
}
void main() {
TestClass();
/// TestClass init start
/// TestMixin2 init start
/// TestMixin init start
/// Base init
/// TestMixin init end
/// TestMixin2 init end
/// TestClass init end
}
类变量和方法
使用关键字 static 可以声明类变量或方法
class Queue {
// 静态变量在其首次被使用的时候才会初始化
static const initialCapacity = 16;
// 静态方法内不能访问 this, 类的实例也不能使用 static 方法
static aaa () {
print('====')
}
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
Queue.aaa()
}