Dart 简明教程 - 01 - Concepts & Variables

812 阅读6分钟

本系列教程大多翻译自 Dart 官网,因本人英语水平有限,可能有些地方翻译不准确,还请各位大佬斧正。如需转载请标明出处,谢绝 CSDN 爬虫党。

Dart 简介

Dart 是一种适用于万维网的开放源代码编程语言,由 Google 主导开发,于2011年10月公开。它的开发团队由 Google Chrome 浏览器 V8 引擎团队的领导者拉尔斯·巴克主持,目标在于成为下一代结构化Web开发语言。

类似于 JavaScript,Dart 也是一种面向对象语言,但是它采用基于类的编程。

Dart 语言之前一直很小众,社区也不是很活跃,但由于 Flutter 框架的热度逐渐上升,作为 Flutter 的标配语言,Dart 也逐渐被开发者所接受。

Dart 基本结构

大多数 dart 基本功能都长这样:

// Define a function.
printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// This is where the app starts executing.
main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}

一些重要的概念

  1. 所有定义的变量,都是对象(object),所有的对象都是类的实例(an instance of a class)。甚至 numbers, functions, 和 null 都是对象. 所有的对象都来自 Object 类;
  2. 因为 Dart 可以推断类型,所以是强类型语言。在编译推断里,number 将被视作 int 类型。如果你不确定变量的类型,可以使用特殊的类型dynamic;
  3. Dart 支持泛型,如List<int>(只支持整型的数组)或List<dynamic>(支持任何类型的数组);
  4. Dart 支持顶级函数(top-level functions, 比如 main()),函数也可以绑定到[类]或对象。你也可以在函数里创建函数(嵌套或局部函数)。
  5. 类似的,Dart 支持顶级变量(top-level variables),也支持变量绑定到[类]或对象。实例化的变量通常叫做字段或属性;
  6. 和 Java 不同,Dart 不支持 public, protected 和 private 关键字。但如果变量或函数以下划线(_)开头,会被当做私有变量处理;
  7. Dart 中的变量或函数名以字母或下划线开头,后接多种组合(字母、数字、下划线等),与大多数编程语言相似;
  8. 严格区分 表达式(expression)声明(statement),声明语句可以包含表达式,但表达式里不能包含声明语句;
  9. Dart 的工具可以检查出警告信息(warinings)和错误信息(error)。警告只是提示代码可能不能运行的迹象,但错误可能是编译错误或运行错误,编译会阻止程序运行,运行错误会抛出 exception;

关键字

具体可去 Dart 官网 查看

变量

默认值

如果只是声明变量,但是未初始化,该变量会被初始化为 null,即使数字也会如此,因为在 Dart 中,数字也是一个对象;

int lineCount;
assert(lineCount == null);

Note: 生产环境会忽略 assert() 调用。在开发环境中,如果 assert(condition) 中的 condition 是 false,会抛出 exception。

final & const

如果你永远不想改变一个变量,使用 final 或 const 去修饰声明,否则使用 var 或者添加声明类型。

final 只会被赋值一次;
const 是一个编译时的常量;(const 是隐式的 final)

一个 final 的顶级变量或类将在第一次调用的时候才初始化

Note: Instance variables can be final but not const. Final instance variables must be initialized before the constructor body starts — at the variable declaration, by a constructor parameter, or in the constructor’s initializer list.

Note: final 可以实例化,但 const 不行。 实例化 final 变量必须在构造体之前初始化。

如果想声明在编译时才赋值的常量,使用 const。如果 const 常量是 class 级别的,使用 static const

const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

const 关键字不只是用于声明常量,还可以用于创建常量值,就相当于声明一个创建了常量值的构造函数。任何变量都可以拥有常量值。

var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

本质上,如果你赋值的时候,没有使用 const 关键字,就像上面 baz 一样,Dart 也会自动认为是一个常量值,即 const baz = [] 等于 const baz = const []

你可以修改没有被 final 和 const 修饰的变量,即使已经赋了一个常量值

var foo = const [];
foo = [1, 2, 3]; // Was const []

但不能改变被 final 或 const 修饰的常量

const baz = [];
baz = [42]; // Error: Constant variables can't be assigned a value.

在 Dart 2.5+ 的版本,你可以使用类型检查(is)、描述符(as)、if 表达式、 for 表达式和扩展运算符(......?)

// Valid compile-time constants as of Dart 2.5.
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: "int"}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.

内置数据类型

Dart 支持下列数据类型:

  • numbers
  • strings
  • booleans
  • lists(也就是数组 Array)
  • sets
  • maps
  • runes(专门用来表示 Unicode 字符串)
  • symbols

你可以用上述类型的字面量表达式去初始化对象,例如,'this is a string'是字符串类型的字面量,true 是布尔类型的字面量。

因为在 Dart 中,所有的变量都是一个实例化的对象类,你通常会使用构造函数去初始化变量。一些内置数据类型有他们自己的构造函数,比如,你可以使用 Map() 去构造一个 map 对象;

Numbers

Dart 中 numbers 有如下两类:

int

整型,长度不超过 64 bits,取值范围为 -2^63 ~ 2^63 - 1,如果编译成 JavaScript ,范围为 -2^53 ~ 2^53 -1

不能包含小数点。

var x = 1;
var hex = 0xDEADBEEF;
double

浮点型,长度为 64-bit,符合 IEEE 754 标准。

var y = 1.1;
var exponents = 1.42e5;

在 Dart 2.1+ 整数字面量在特定环境会被自动转换成浮点型

double z = 1; // Equivalent to double z = 1.0

// Dart 2.1 以前,会直接报错

一些 String 和 Number 互相转换的例子:

// 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');

上述类型还支持位运算( << , >> ),与(&),或(|)运算符:

assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111

numbers 字面量是编译时的常量。许多通过编译常量运算出来的结果也是编译常量:

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

Strings

Dart 字符串是 UTF-16 编码的一串序列,你可以用单引号('')或双引号("")创建字符串字面量:

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

你可以通过使用${expression}在字符串里穿插变量(类似于 JavaScript 的字符串模板)

  • 如果表达式是标识符(变量名),可以直接省略{};
  • 如果传入的是对象,可以通过object.toString()方法转义;
  • 支持 + 运算符
var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, ' +
        'which is very handy.');
assert('That deserves all caps. ' +
        '${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. ' +
        'STRING INTERPOLATION is very handy!');

如果想简洁的编写换行的字符串,可以使用3个连续的单引号或双引号:

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

可以在字符串前面加一个r前缀,表示创建的是一行(即会忽略换行符)

var s = r'In a raw string, not even \n gets special treatment.';

如果想表示 Unicode 字符,可以使用 Runes 类型

因为字面量的字符串是编译常量,所以往里面插入的表达式也必须是编译常量。(和 JavaScript 不同)

const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These work in a const string.
const validConstString = '$aConstNum $aConstBool $aConstString';

var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

// These do NOT work in a const string.
const invalidConstString = '$aNum $aBool $aString $aConstList';

Booleans

和大多数语言一样, Dart 的布尔字面量只有 true 和 false 两种。

不过 Dart 是类型安全语言,所以像 if (nonbooleanValue)assert (nonbooleanValue)这样直接使用非布尔值来做为判断条件是不行的。

要像这样去判断

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

Lists

也许几乎所有的编程语言数组都用 Array,或称为序列化对象。在 Dart 里,List 就是数组,所以大多数开发者直接称为列表(lists)。

var list = [1, 2, 3];

Note: Dart 会将上面的 list 推断成 List,如果过你试图添加一个非整型的对象,代码检查器或编译器会直接报错。

和 JavaScript 一样,list 的下标从 0 开始,所以最后一个元素是 list.length - 1

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

如果要创建 list 的编译常量,添加 const 关键字:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.

Dart 2.3 引入了扩展运算符(...)和无感扩展运算符(null-aware spread operator ...?),提供了一种便捷的插入多元素到数组的方法。

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

如果表达式右边的扩展运算符可能是 null,你可以使用 ...?去避免错误

var list; // null
var list2 = [0, ...?list];
assert(list2.length == 1);

Dart 2.3 还可以在数组里使用 iffor

这是使用 if 的例子

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];
// nav.length maybe 3 or 4

这是使用 for 的例子

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

list 类型还有很多实用的方法,更多详情请移步 GenericsCollections

Sets

在 Dart 中 set 是一组无序的、成员必须是独一无二的集合,Dart 支持使用 set 字面量来赋值,如:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Note: 上述代码, halogens 的类型是 Set,如果你试图添加非 String 类型,编译器或者运行时会报错。

创建一个空集合,使用{}并声明类型:

var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.

到底是 set 还是 map 类型? map 和 set 的字面量语法很像。因为 map 字面量先于 set 被设计,所以{}的默认类型是 Map。因此,如果你忘了标识类型,Dart 会默认创建一个 Map<dynamic, dynamic>

对已有成员的集合添加成员,使用 add()addAll()

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

使用 .length 属性获取集合的成员数量

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

创建一个集合的编译常量,在字面量之前添加 const 关键字:

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.

Dart 2.3+ 的集合像数组一样,同样支持扩展运算符(......?),以及 iffor

更多详情请移步 GenericsSets

Maps

通常,map 是一个包含“键值对”的对象。键(key)和值(value)可以是任意类型的对象。每个键只能出现一次,但相同的值可以出现多次。Dart 提供了 map 字面量和 Map 类型。

使用字面量创建 Map:

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

Note: Dart 会推断 gifts 为 Map<String, String>类型,nobleGases 为 Map<Int, String>类型。如果你添加了不匹配的值,编译器和运行时会报错。

可以通过构造函数创建对象

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

Note: 你可能会好奇为啥只用 Map() 就相当于 new Map(),在 Dart 2+ ,关键字 new 是可选项。

添加新的键值对(和 JavaScript 一样):

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

取值(也和 JavaScript 一样):

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

如果试图取一个不存在的键,会返回 null

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

使用 .length 来获取 map 键值对的数量:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

创建一个 map 的编译常量,在字面量之前添加 const 关键字:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

Dart 2.3+中,和数组一样, map 同样支持扩展运算符(......?),以及 iffor

更多详情请移步 GenericsMaps

Runes

在 Dart 中,runes 是 UTF-32 字符集的 string 对象。

全世界的系统都使用 Unicode,它 为每一个字符、数字和符号定义了独一无二的值。

因为 Dart 的字符串是 UTF-16 字符集的,所以要表达 32-bit 的 Unicode 就需要特殊的语句了。

通常,用 \uXXXX(XXXX是四位16进制的值) 表达 Unicode 字符。比如,心形字符(♥)是 \u2665

如果超过4位,使用花括号包起来。比如,emoji 里的微笑(😆)是 \u{1f600}

String 类有一些属性,可以直接取出 rune 的信息,比如 codeUnitAtcodeUnit 取出 16-bit 的字符。

main() {
  var clapping = '\u{1f44f}';
  print(clapping);
  print(clapping.codeUnits);
  print(clapping.runes.toList());

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(input));
}

输出:

👏
[55357, 56399]
[128079]
♥  😅  😎  👻  🖖  👍

Note: 当对 runes 类型使用数组操作方法时,要细心,因为程序很容易崩溃,更多信息请移步 Stack Overflow 的 How do I reverse a String in Dart?

Symbols

Symbol 对象相当于在 Dart 程序中声明的一个操作符或标识符。你可能永远用不上,但对于 API 来说,是非常重要的名称标识符,因为只能稍微修改标识符名,而不是标识 symbols。

A Symbol object represents an operator or identifier declared in a Dart program. 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.

通过 symbol 字面量来获取标识符,只需在前面加#

#radix
#bar

symbol 字面量是编译常量。

系列文章:

Dart 简明教程 - 01 - Concepts & Variables
Dart 简明教程 - 02 - Functions
Dart 简明教程 - 03 - Operators
Dart 简明教程 - 04 - Control flow statements
Dart 简明教程 - 05 - Exceptions
Dart 简明教程 - 06 - Classes
Dart 简明教程 - 07 - Generics
Dart 简明教程 - 08 - Libraries and visibility
Dart 简明教程 - 09 - Asynchrony support
Dart 简明教程 - 10 - Generators & Isolates & Typedefs & Metadata...