Flutter跨平台开发-语法基础

Dart For Objcer

本文档是一个面向Objcer 的更加精炼的 Dart 语言概览,删除了相同的语法概念,仅剩差异部分,或 dart 独有的部分。未提及的,均可以沿用 objc 经验。

数据类型

所有数据类型都继承自 Object类。(包括int、String、函数以及 null 等等)。All is Object.

常用内置数据类型:

  • num
    • int
    • double
  • String
  • bool (true | false)
  • List ,数组
  • Set,集合
  • Map,字典

变量 & 常量

变量

dart 默认声明的为变量,变量声明主要有以下几种形式:

  • 类型约定:

    String name; // 默认值为 null:未初始化的所有变量拥有一个默认的初始化值:null,包括 num。
    name = 'Bob';
    
    int age = 18;
    
  • 类型推断,通过 关键词 var 来声明

    var name = 'Bob'; //name is String 
    name = 123; //编译报错
    
  • 使用动态类型,通过 关键词 dynamic 来声明, 类似 OC 中的 id

    dynamic name = 'Bob'; //name is String 
    name = 123; //name is int
    

一般情况下,我们使用 var 来声明变量,让编译器帮我们推断其类型;

常量

常量声明涉及到两个关键字 finalconst。使用示例如下:

final String name; // 编译报错:未初始化
final String name = 'dann'; //✅
name = 'dann2'; //编译报错,提示仅能赋值一次

const String name; // 编译报错:未初始化
const String name = 'dann'; //✅
name = 'dann2'; //编译报错,常量变量不能被赋值
  • final 用于声明==运行时==常量;
  • const 用于声明==编译时==常量;

示例说明如下:

final abc = 1 + 2;  //✅
const bb = 1 + 2;       //✅

final x = DateTime.now(); // ✅
const x = DateTime.now(); // ❌

函数

一般函数形式:

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

//函数体只包含单个表达式的简写,同上
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; 

函数参数分为两种:

  • 必要参数

    bool isNoble(int atomicNumber/* 必要参数 */){
      ...
    }
    
  • 可选参数:一个函数==仅可==包含一种可选参数

    • 命名参数:{type param1, type param2, ...}

      bool isNoble(int atomicNumber/* 必要参数 */, {bool bold = false, @required Widget child}){
      ...
      }
      
    • 位置参数:[type param1, type param2, ...],

      bool isNoble(int atomicNumber/* 必要参数 */, [bool bold]){
      ...
      }
      

函数注意点:

  • 必要在前,可选在后;

  • 可对参数设置默认值 isNoble(int atomicNumber = 110)

  • 即使是可选参数,也可通过关键字 @required 将其约束为 必要的(调用函数时,必须对该参数赋值)

  • 函数也是对象,可作为参数传递

    void printElement(int element) {
      print(element);
    }
    
    var list = [1, 2, 3];
    
    // 将 printElement 函数作为参数传递。
    list.forEach(printElement);
    

匿名函数

类似 OC 中的 Block

void main() {
var list = ['apples', 'bananas', 'oranges'];
// forEach(void Function(String) f) -> void
list.forEach((item){
  print(item);
 });
}

表达式

表达式基本继承自类 C 语言,OC中没有的表达式,有如下几个:

  • ~/ 除并取整

    print(5/2 == 2.5);  //true, 此处不同于 OC ,返回整数
    print(5 ~/ 2 == 2); //true
    
  • as 类型转换 | is is! 类型判断

    // 类型检查
    if (emp is Person) {
     emp.firstName = 'Bob';
    }
    
    // 类型检查
    if (emp is! Person) {
     //TODO: 
    }
    
    //此时要确保 emp 是 Person 类型,否则会抛出异常
    (emp as Person).firstName = 'Bob'; 
    
  • .. 级联 在 同一个对象 上连续 调用 多个对象的变量或方法。

    querySelector('#confirm') // 获取对象 (Get an object).
      ..text = 'Confirm' // 使用对象的成员 (Use its members).
      ..classes.add('important')
      ..onClick.listen((e) => window.alert('Confirmed!'));
    
    //equal to
    var button = querySelector('#confirm');
    button.text = 'Confirm';
    button.classes.add('important');
    button.onClick.listen((e) => window.alert('Confirmed!'));
    
    //返回值为 void 的方法则不能使用级联运算符
    var sb = StringBuffer();
    sb.write('foo')
      ..write('bar'); // 出错:void 对象中没有方法 write
    
  • ?? 空判断 expr1 ?? expr2 expr1 非空,则返回其值,否则执行 expr2 并返回值

      var num;
      print(num??123); // 123
      num = 456;
      print(num??123); // 456
    
  • ??= 空则赋值

      var a = 123;
      a ??= 456;
      print(a); //123
      
      var b;
      b ??= 456;
      print(b); //456
    
  • ?. 条件访问成员

      List numbs;
      print(numbs?.length); // null
      numbs = [1, 2, 3];
      print(numbs?.length); // 3
    

流程控制

流程控制语句也基本继承自 C 语言;按已有 OC 习惯使用即可;

异常

关键字:

  • throw
  • try
  • catch
  • finally

流程及使用习惯,类 Java,如下:

void breedMoreLlamas(){
  if(1){
    //TODO: 
  }else{
    throw 'Out of llamas!';
  }
}

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // 先处理异常。
} finally {
  cleanLlamaStalls(); // 然后清理。
}

  • 一个类只有一个父类;
  • 类可扩展,同OC;

成员使用

var p = Point(2, 2);

// 为实例变量 y 赋值。
p.y = 3;

// 获取 y 的值。
assert(p.y == 3);

// 调用变量 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));

构造函数

class Point {
  num x, y;

  Point(num x, num y) { 
    this.x = x; //使用 this 关键字引用当前实例; OC 中的self
    this.y = y;
  }
  
  // 语法糖: 在构造函数体执行前用于设置 x 和 y 的, equal to above
  Point(this.x, this.y);
}

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

// new 关键字可选
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

常量构造函数

两个使用相同构造函数相同参数值构造的编译时常量是同一个对象:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 同一个实例!

命名式构造函数

  • 声明一个类多个命名式构造函数来表达更明确的意图;
  • 不能被继承;
class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名式构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

初始化列表

除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。

// Initializer list sets instance variables before
// the constructor body runs.
// 使用初始化列表在构造函数体执行前设置实例变量。
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

其他构造函数

详情参见Dart API 文档:

  • 重定向构造函数 :类似OC 中的 非指定构造函数
  • 工厂构造函数:关键字 factory, 使用该构造函数构造类的实例时==并非==总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。==工厂中不可以访问 this==

获取对象类型

关键字 runtimeType

print('The type of a is ${a.runtimeType}');

方法 & 属性

使用同OC, 但使用 . 语法

每个属性都有 Getter 方法, 对于 非 final 属性,还有 Setter 方法。 可以通过 关键字 getset 为额外的属性(如计算属性),添加 Getter 和 Setter 方法:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算产生的属性:right 和 bottom。
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

抽象类

使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。如果想让抽象类同时可被实例化,可以为其定义工厂构造函数

abstract class Doer {
  // 定义实例变量和方法等等……

  void doSomething(); // 定义一个抽象方法。
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供一个实现,所以在这里该方法不再是抽象的……
  }
}

隐式接口

骚操作

接口 即 OC 中的协议。

==每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。==

一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:

// Person 类的隐式接口中包含 greet() 方法。
class Person {
  // _name 变量同样包含在接口中,但它只是库内可见的。
  final _name;

  // 构造函数不在接口中。
  Person(this._name);

  // greet() 方法在接口中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一个实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => '你好$who。你知道我是谁吗?';
}

String greetBob(Person person) => person.greet('小芳');

void main() {
  print(greetBob(Person('小芸')));
  print(greetBob(Impostor()));
}

实现多个接口,使用逗号分隔:

class Point implements Comparable, Location {...}

扩展

子类化

使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类:

class TV{
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTV extends TV {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
重写

子类可以重写父类的实例方法、Getter 以及 Setter 方法。使用 @override 来标示:

class SmartTV extends TV {
  @override
  void turnOn() {...}
  // ···
}

扩展方法

一种向现有库添加功能的方式。extension extensionName on ClassName{...}

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

print('42'.padLeft(5)); // 使用 原始类 自带方法
print('42'.parseInt()); // 使用 扩展方法

Mixin

一种在多重继承中复用某个类中代码的方法模式。

定义一个类继承自 Object 并且不为该类定义构造函数,这个类就是 Mixin 类,除非你想让该类与普通的类一样可以被正常地使用,否则可以使用关键字 mixin 替代 class 让其成为一个单纯的 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');
    }
  }
}

使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

使用关键字 on 来指定哪些类可以使用该 Mixin 类,比如有 Mixin 类 A,但是 A 只能被 B 类使用,则可以这样定义 A:

mixin MusicalPerformer on Musician {
  // ···
}

枚举

枚举具有较大的局限性,其值不可自定义。

每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。

enum Color { red, green, blue }

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

类变量及类方法

使用 static 修饰

类成员
class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

静态变量在其首次被使用的时候才被初始化。

类方法
import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

泛型

如今的每个现代编程语言都支持泛型。日常使用中,最常见于集合。常用于需要要求类型安全的情况,好处还有:

  • 适当地指定泛型可以更好地帮助代码生成。
  • 使用泛型可以减少代码重复。
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

在上述代码中,为集合指定类型为 String,方便编译器检查,避免插入错误类型数据;

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。

泛型约束

可能会想限制泛型的类型范围,这时候可以使用 extends 关键字:

class Foo<T extends SomeBaseClass> {
  // 具体实现……
  String toString() => "'Foo<$T>' 的实例";
}

class Extender extends SomeBaseClass {...}

//这时候就可以使用 SomeBaseClass 或者它的子类来作为泛型参数:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

泛型方法

在方法中,使用泛型

T first<T>(List<T> ts) {
  // 处理一些初始化工作或错误检测……
  T tmp = ts[0];
  // 处理一些额外的检查……
  return tmp;
}

异步

不同于 OC 中 通过 GCD 和 NSOperation 来支持异步编程。

Dart 中 使用 Futureawait/async 来支持异步编程。

Future

Future 基本使用上类似于 GCD,如下:

void main() { 
Future(() => print('立刻在Event queue中运行的Future'));
  
Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中运行的Future'));
  
Future.microtask(() => print('在Microtask queue里运行的Future'));
  
Future.sync(() => print('同步运行的Future'));
  
Future(()=> print('task'))
  .then((_)=> print('callback1'))
  .then((_)=> print('callback2'))
  .catchError((error)=>print('$error'))
  .whenComplete(()=> print('whenComplete'));
}

//print as follow:
/*
同步运行的Future
在Microtask queue里运行的Future
立刻在Event queue中运行的Future
task
callback1
callback2
whenComplete
1秒后在Event queue中运行的Future
*/

其中Dart的事件循环,类似 OC 中的 Runloop, 大概如下:

1、Dart的入口是main函数,所以main函数中的代码会优先执行;

2、main函数执行完后,会启动一个事件循环(Event Loop)就会启动,启动后开始执行队列中的任务;

3、首先,会按照先进先出的顺序,执行 微任务队列(Microtask Queue)中的所有任务;

4、其次,会按照先进先出的顺序,执行 事件队列(Event Queue)中的所有任务;

为了避免 地狱回调 的问题,dart 又通过引入 await/async 来实现以同步的代码风格,实现异步调用。

await/async

Future<String> getNetworkData() async {
  var result = await Future.delayed(Duration(seconds: 3), () {
    return "network data";
  });

  return  "请求到的数据:" + result;
}

main() async {
  print('print network data...');
  print(await getNetworkData());
}

其他

  • isolates dart 中独有的概念,用于代替线程使用,详情参见官网;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容