Dart 基本语法

Dart 语言集合了 Java、JavaScript、C、TypeScript 等语言的优势,可以说是集百家之长的一门编程语言,可以用与 web 编程服务端编程以及 app 编程(Flutter),目前主要用于 Flutter 开发。以下是几点重要的概念:

  • Dart 是强类型语言,但是又兼有 JS 的动态性
  • 类型声明是可选的,可通过类型推导而来
  • 语句以分号结尾,不能省略

1、基本语法

1.1、变量

  • var 关键字
    可以用var 声明一个变量,一旦赋值,类型就不能再改变,但值可以变化
var str = 'abc';
str = 'def';  // ok
str = 123; // error
  • 变量声明时,若未指定初始值,则默认值都是null,而不是JS 中的 undefined
  • 类型可选,类型推断的存在使得声明变量时,变量类型时可选的
String str = 'abc' // 与 var str='abc' 等价
  • dynamic和Object
    1、Object 是dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象.
    2、dynamic与var一样都是关键词,声明的变量可以赋值任意对象. 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型.
dynamic t;
 Object x;
 t = "hi world";
 x = 'Hello Object';
 //下面代码没有问题
 t = 1000;
 x = 1000;

dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错.

  • final & const 关键字
    1、声明常量可用 final 或者 const , const 属于编译时常量,而 final 属于运行时常量;
    2、const 还可以用来创建不变的值
 var foo = const [1];  // 表示这个数组的值不能变化,而不是数组的地址
 foo[0]=1;  // error
  • num 数字类型
    包含两个子类型 int & double。以下是字符串类型和数字类型的互转
// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
  • String
    可用单双三引号来创建string,字符串中支持变量插值
String a='123';
var b='123'; // 等价于 var b='$a';
  • bool
    Dart 中只有 true 对象才被认为是 true。 所有其他的值都是 flase。这点和 JavaScript 不一样, 像 1、 "aString"、 以及 someObject 等值都被认为是 false。
var name = 'Bob';
if (name) {
  // Prints in JavaScript, not in Dart.
  print('You have a name!');
}
  • List
    就是JS中的 Array,在 list 字面量之前添加 const 关键字,可以 定义一个不变的 list 对象(编译时常量)
var list = [1, 2, 3];
assert(list.length == 3);

var constantList = const [1, 2, 3];
constantList[1] = 1; // causes an error.
  • Map
    键和值可以是任何类型的对象,比JS中的Object强大一点儿(key不能为对象),
    如果所查找的键不存在,则返回 null,而不是 undefined
var gifts = {
// Keys      Values
  'first' : 'partridge',
  'second': 'turtledoves',
  'fifth' : 'golden rings'
};

var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
  • Symbol & Runes 暂时略过
  • enum 枚举
enum Color {
  red,
  green,
  blue
}

枚举类型中的每个值都有一个 index getter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 0, 第二个为 1.

1.2、函数 Function

  • 声明
    函数如果没有返回值,则一律返回 null
bool isNoble(int atomicNumber) {
  ...
}
  • 箭头函数
    1、对于只有一个表达式的方法,你可以选择 使用缩写语法来定义。
    2、箭头函数即使只有一个参数,也不能省略括号
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
[1,2,3].forEach(a=>print(a)); // err
[1,2,3].forEach((a)=>print(a)); // ok
  • 可选参数(必须放在必须参数后面)
    1、可选命名参数
    定义方法的时候,使用 {param1, param2, …} 的形式来指定命名参数
enableFlags({bool bold, bool hidden}) {
  // ...
}

调用方法的时候,你可以使用这种形式 paramName: value 来指定命名参数

enableFlags(bold: true, hidden: false);

2、可选位置参数
把一些方法的参数放到 [] 中就变成可选 位置参数了:

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');
  • 默认参数
    函数可选参数可以指定默认值,必须参数不能指定
void enableFlags({bool bold = false, bool hidden = false}) {
  // ...
}
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');
  • main 入口函数
    每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void 并且有个可选的 List<String> 参数。
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}
  • 匿名函数
    就是无函数名的函数,一般用于迭代器
var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) {
  print(list.indexOf(i).toString() + ': ' + i);
});
  • 如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断

1.3、操作符

只记录与JS语言不一致的操作符

  • 算术操作符
    除法结果取整: ~/
assert(5 / 2 == 2.5);   // Result is a double
assert(5 ~/ 2 == 2); 
  • 相等操作符
    无三等号操作符,双等号操作符比较的是内容是否相同,而不是内存地址,如果要比较内存地址是否相同,请用 identical
void main() {
  const c = [1,2];
  var a = c;
  var b = c;
  print(a == b);
  print(identical(a,b));
}
  • 类型判定操作符
    as:类型转换;is 相当于 instanceof;is!
if (emp is Person) { // Type check
  emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
  • 赋值操作符
    使用 = 操作符来赋值。 但是还有一个 ??= 操作符用来指定 值为 null 的变量的值
a = value;   // 给 a 变量赋值
b ??= value; // 如果 b 是 null,则赋值给 b;
             // 如果不是 null,则 b 的值保持不变
  • 条件表达式
    除了? : ,多了一个双问号 ??,相当于JS里短路运算符 ||
expr1 ?? expr2 // expr1 为 null 时执行 expr2
  • 级联操作符
    级联操作符 (..) 可以在同一个对象上 连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建 临时变量, 并且写出来的代码看起来 更加流畅,相当于JS中的链式调用:
querySelector('#button') // Get an object.
  ..text = 'Confirm'   // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
  • 对象访问操作符
    新增了一个 .?
    左边的操作对象不能为 null,例如 foo?.bar 如果 foo 为 null 则返回 null,否则返回 bar 成员

1.4、类

  • 构造函数
    与JS的 constructor 不同,把类中同名函数作为构造函数
class Point {
  num x;
  num y;
  Point(this.x, this.y);
}
  • 命名构造函数
    使用命名构造函数可以为一个类实现多个构造函数
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor 只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this。
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

注意:构造函数不能继承,所以超类的命名构造函数 也不会被继承。如果你希望 子类也有超类一样的命名构造函数, 你必须在子类中自己实现该构造函数。

  • 调用超类构造函数
    1、默认情况下,子类的构造函数会自动调用超类的 无名无参数的默认构造函数,
    2、若提供了初始化参数列表,则初始化参数列表在超类构造函数执行之前执行。
    3、构造函数执行顺序:a、initializer list(初始化参数列表)b、superclass’s no-arg constructor(超类的无名构造函数)c、main class’s no-arg constructor(主类的无名构造函数)
    4、如果超类没有无命名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) 可以调用 超类构造函数。
import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
  • 重定向构造函数
    1、有时候一个构造函数会调动类中的其他构造函数。
    2、一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。
class Point {
  num x;
  num y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}
  • 类的继承(单继承)
    使用 extends 定义子类, supper 引用 超类
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ...
}

可以使用 @override 注解来 表明你的函数是想覆写超类的一个函数:

class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}
  • 扩展类
    可以通过 with 关键字来给类实现 mixin 功能,扩展类的特点:没有构造函数, 不能调用 super
class Musician extends Performer with Musical {
  // ...
}
class 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');
    }
  }
}
  • 类的实例变量、方法以及静态变量和方法与JS类一致

1.5、泛型

1、使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以继续使用检查模式和静态分析工具提供的代码分析功能。

var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};

2、当使用泛型类型的时候,你 可能想限制泛型的具体类型。 使用 extends 可以实现这个功能:

// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> {...}

3、泛型函数

T first<T>(List<T> ts) {
  // ...Do some initial work or error checking, then...
  T tmp ?= ts[0];
  // ...Do some additional checking or processing...
  return tmp;
}

1.6、其他

  • 模块系统
    1、引用库
    使用 import 来指定一个库如何使用另外 一个库。import 必须参数为库 的 URI。 对于内置的库,URI 使用特殊的 dart: scheme。 对于其他的库,你可以使用文件系统路径或者 package: scheme。 package: scheme 指定的库通过包管理器来提供, 例如 pub 工具。
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

2、如果你导入的两个库具有冲突的标识符, 则你可以使用库的前缀来区分。 例如,如果 library1 和 library2 都有一个名字为 Element 的类, 你可以这样使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib

3、导入库的一部分

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

4、库的延迟加载,使用 deferred as 来 导入,使用库标识符调用 loadLibrary() 函数来调用

import 'package:deferred/hello.dart' deferred as hello;
greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
  • 注解
    有三个注解所有的 Dart 代码都可以使用: @deprecated、 @override、 和 @proxy
  • 异步支持
    1、Future
Future.delayed(new Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");
}).then((data){
   //执行成功会走到这里 
   print(data);
}).catchError((e){
   //执行失败会走到这里   
   print(e);
}).whenComplete((){
   //无论成功或失败都会走到这里
});

有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作

Future.wait([
  // 2秒后返回结果  
  Future.delayed(new Duration(seconds: 2), () {
    return "hello";
  }),
  // 4秒后返回结果  
  Future.delayed(new Duration(seconds: 4), () {
    return " world";
  })
]).then((results){
  print(results[0]+results[1]);
}).catchError((e){
  print(e);
});

2、Stream
Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果

Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(new Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(new Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(new Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){

});
// 依次输出
hello 1
Error
hello 3

2、最佳实践

2.1、字符串

  • 当字符串较长,一行放不下时,不使用 + 来链接字符串:
raiseAlarm(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');
  • 使用插值的形式来组合字符串和值
'Hello, $name! You are ${year - birth} years old.';

2.2、集合

  • 尽可能的使用集合字面量来定义集合
var points = [];
var addresses = {};
// bad
var points = new List();
var addresses = new Map();
  • 不要使用 .length 来判断集合是否为空
    尽管对象和数组都有length属性
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
// bad
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
  • 使用高阶(higher-order)函数来转换集合数据
    比JS多了个 where 用于筛选
var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);

2.3、变量

  • 不用显示的初始化一个变量的值为 null
  • 避免保存可以计算的结果,而是用 getter
class Circle {
  num radius;

  num get area => math.PI * radius * radius;
  num get circumference => math.PI * 2.0 * radius;

  Circle(this.radius);
}
  • 考虑 省略局部变量的类型
Map<int, List<Person>> groupByZip(Iterable<Person> people) {
  var peopleByZip = <int, List<Person>>{};
  for (var person in people) {
    peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
    peopleByZip[person.zip].add(person);
  }
  return peopleByZip;
}
// bad
Map<int, List<Person>> groupByZip(Iterable<Person> people) {
  Map<int, List<Person>> peopleByZip = <int, List<Person>>{};
  for (Person person in people) {
    peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
    peopleByZip[person.zip].add(person);
  }
  return peopleByZip;
}

2.4、成员

  • 使用 final 关键字来限定只读属性
    只能读取, 而不能修改其值,最简单的做法就是使用 final 关键字来标记这个变量
class Box {
  final contents = [];
}
  • 用 => 来实现只有一个单一返回语句的函数
bool ready(num time) => minTime == null || minTime <= time;
  • 不要使用 this. ,除非遇到了变量冲突的情况
class Box {
  var value;

  void clear() {
    update(null);
  }

  void update(value) {
    this.value = value;
  }
}
// bad
class Box {
  var value;

  void clear() {
    this.update(null);
  }

  void update(value) {
    this.value = value;
  }
}
  • 要 尽可能的在定义变量的时候初始化其值

2.5、构造函数

  • 要尽可能的使用初始化形式
class Point {
  num x, y;
  Point(this.x, this.y);
}
// bad
class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}
  • 不要在初始化形式上定义类型
// bad
class Point {
  int x, y;
  Point(int this.x, int this.y);
}
  • 要用 ; 来替代空函数体的构造函数 {}
class Point {
  int x, y;
  Point(this.x, this.y);
}
// bad
class Point {
  int x, y;
  Point(this.x, this.y) {}
}
  • 要 把 super() 调用放到构造函数初始化列表之后调用
View(Style style, List children)
    : _children = children,
      super(style) {
// bad
View(Style style, List children)
    : super(style),
      _children = children {
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349