网站地图    收藏   

主页 > 前端 > dart入门教程 >

Dart语言难点详解

来源:未知    时间:2023-05-09 09:26 作者:小飞侠 阅读:

[导读] Dart语言难点详解 重要概念 当你学习这门语言时,记住这些事实和概念: 你可以放在变量中的所有东西都是一个对象,每个对象都是一个类的实例。偶数,函数和null都是对象。所有对...

Dart语言难点详解


重要概念

当你学习这门语言时,记住这些事实和概念:

  • 你可以放在变量中的所有东西都是一个对象,每个对象都是一个类的实例。偶数,函数和null都是对象。所有对象都从Object类继承。

  • Dart支持顶层函数如main(),以及链接到类或对象(分别为静态方法和实例方法)的函数。你也可以在函数内部创建函数(嵌套或局部函数)。

  • 同样,Dart支持顶级变量,以及一个类或对象的变量(静态变量和实例变量)。实例变量被称为字段或属性。

  • 与Java不同,Dart没有关键字public,protected和private。如果一个标识符以下划线(_)开头,则它的库是私有的。

  • 标识符可以以字母或**_开头,后面是字符和数字**的任意组合

  • 有时候区分表达式和声明是很重要的,必须搞明白两者的含义。

  • Dart工具可以报告两种类型的问题:警告和错误。 警告只是表明您的代码可能无法正常工作,但它们并不妨碍您的程序执行。 错误可以是编译时或运行时。 编译时错误会阻止代码执行; 运行时错误导致代码执行时引发异常。


默认值

未初始化的变量的初始值为null. 即使数值类型的变量最初也为空,因为数字是对象.


int lineCount;assert(lineCount == null);// Variables (even if they will be numbers) are initially null.


可选类型

您可以选择在变量声明中添加静态类型:


String name = 'Bob';

添加类型是清楚表达您的意图的一种方式。 诸如编译器和编辑器之类的工具可以通过提供代码完成和对错误和代码完成的早期警告来使用这些类型来帮助您.

Note: 建议, 对于局部变量使用var, 而不是类型注释.


Final 和 const

如果变量的值不发生变化, 那么就可以使用final or const, 而不是var 或其它修饰符. final 修饰的变量只能设置一次值; const修饰的变量应当在声明处赋值,它是一个编译时的常数. (const变量是一种隐式的final变量.) 全局final变量或类变量在第一次使用时初始化.

Note: 实例变量可以是 final 而不能是const.


// name = 'Alice';  // Uncommenting this causes an error

使用const修饰的变量作为编译时的常量。如果const变量处于类级别使用static const修饰. 在声明处为const修饰的变量赋值:


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

const 关键字不仅仅是声明常量变量. 您还可以使用它来create常量值,以及声明创建常量值的构造函数,任何变量都可以有一个常量值.


// Note: [] 创建一个空列表.// const [] creates an empty, immutable list (EIA).var foo = const [];   // foo is currently an EIA.final bar = const []; // bar will always be an EIA.const baz = const []; // baz is a compile-time constant EIA.// You can change the value of a non-final, non-const variable,// even if it used to have a const value.foo = [];// You can't change the value of a final or const variable.// bar = []; // Unhandled exception.// baz = []; // Unhandled exception.


字符串

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}将表达式的值放在字符串中。 如果表达式是标识符,则可以直接使用$标识符。 要得到一个对象的字符串,可以使用Dart调用该对象的 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!');

注意:== 操作符测试两个对象是否相等。 如果两个字符串包含相同的代码单元序列,则它们是相同的.

您可以使用相邻字符串文字或+ 运算符连接字符串运算符连接字符串:


var s1 = 'String ' 'concatenation'
         " works even over line breaks.";assert(s1 == 'String concatenation works even over '
             'line breaks.');var s2 = 'The + operator '
         + 'works, as well.';assert(s2 == 'The + operator works, as well.');

另一种创建多行字符串的方式:使用单引号或双引号的三重引号:


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, even \n isn't special.";

文字字符串是编译时常量,任何内插的表达式都是一个编译时常数,可以计算为null或 numeric, string, 或 boolean 值.


// 以下变量可以插入到一个const修饰的字符串中。因为他们在编译时是可计算的.const aConstNum = 0;const aConstBool = true;const aConstString = 'a constant string';// 以下变量不能被插入到一个const修饰的字符串中。因为他们在编译时是不可计算的.var aNum = 0;var aBool = true;var aString = 'a string';const aConstList = const [1, 2, 3];const validConstString = '$aConstNum $aConstBool $aConstString';// const invalidConstString = '$aNum $aBool $aString $aConstList';//出错

以下是将字符串转换成数字的方法:


// String -> intvar one = int.parse('1');assert(one == 1);// String -> doublevar onePointOne = double.parse('1.1');assert(onePointOne == 1.1);// int -> StringString oneAsString = 1.toString();assert(oneAsString == '1');// double -> StringString piAsString = 3.14159.toStringAsFixed(2);assert(piAsString == '3.14');


Runes

在Dart中,符号是字符串的UTF-32编码.

Unicode为所有世界写作系统中使用的每个字母,数字和符号定义唯一的数值,因为Dart字符串是UTF-16代码单元的序列,因此在字符串中表达32位Unicode值需要特殊语法.

表达Unicode代码点的通常方式是 \uXXXX, 其中XXXX是一个4位数的十六进制值. 例如,心脏字符 (♥) 是 \u2665. 要指定多于或少于4个十六进制数字,请将该值放在大括号中. 例如,笑声表情符号 () 是 \u{1f600}.

String 类有几个属性可用于提取符文信息. codeUnitAt 和 codeUnit 属性返回16位代码单元. 使用 runes 属性来获取字符串的符文.

以下示例说明了符文,16位代码单元和32位代码点之间的关系单击运行按钮( ) 以查看运行中的符文


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));}

Note: 使用列表操作操纵符文时要注意。 根据具体的语言,字符集和操作,这种方法很容易分解。更多信息查看如何反转Dart中的字符串?在 Stack Overflow 上.


Symbols

Symbol对象表示在Dart程序中声明的操作符或标识。你可能不会需要使用这些符号,但他们对于由名字指向的API是很有用的,因为时常要改变的是标识符的名字,而不是标识符的符号.

为了得到符号的标识,使用符号的文本,只是 # 后跟标识符:


#radix
#bar

Symbol 是编译时常量.

关于symbols的更多信息, 查看 dart:mirrors - reflection.


布尔值

为了表示布尔值,Dart有一个名为 bool的类型. 只有两个对象是bool类型:布尔文字true和false,Only two objects have type bool: the boolean literals true and false, 它们都是编译时常量。

当Dart需要一个布尔值时,只有值 true 被视为true. 所有其他值被视为假。 与JavaScript不同,诸如 1, "aString", 和 someObject 之类的值都被视为false.

例如,请思考以下代码,该代码既有JavaScript也可以是Dart代码:


var name = 'Bob';if (name) {
  // Prints in JavaScript, not in Dart.
  print('You have a name!');}

如果您将此代码作为JavaScript运行,则会打印“You have a name!”,因为 name 是一个非空对象. 在Dart中的production mode下, 由于name 转换为 false (因为name != true)所以上述代码根本不打印. 在Dart的checked mode模式下, 前面的代码抛出一个异常,因为name 变量不是一个bool .

Dart中对Booleans的设计主要是为了避免许多可以被看作是true的值,对于我们来说,不能使用if (*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);


函数

Dart是一个真正的面向对象语言,所以即使函数也是对象,也有一个类型 Function. 这意味着可以将函数分配给变量或作为参数传递给其他函数.

如果Funtion只有一个表达式,你可以使用简洁语法:


bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> *expr* 是 { return *expr*; }的简写形式. => 符号有时又被叫做 胖箭头表达式.

Note: 仅仅一个 表达式—不是一个语句块—能够出现在箭头 (=>) 和分号 (;)之间.比如, 你不能使用 if 语句块, 但你可以使用 条件表达式 .

一个function可以拥有两种类型的参数: 必选参数 和 可选参数. 必选参数先列出来, 紧接着是可选参数.


可选参数(Optional parameters)

可选参数分为 命名参数 和 位置参数 ,一个函数中只能使用其中一中,即不能同时存在于一个函数中。


可选命名参数(Optional named parameters)

当调用一个函数时, 你可以使用 参数名: 值的形式指定命名参数. 比如:


enableFlags(bold: true, hidden: false);

当定义函数时, 使用 {param1, param2, …} 来指定命名参数:


/// Sets the [bold] and [hidden] flags to the values/// you specify.enableFlags({bool bold, bool hidden}) {
  // ...}


可选位置参数(Optional positional parameters)

使用[] 包裹函数的可选位置参数列表:


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


参数默认值

函数可以使用=来为可选命名和可选位置参数制定默认值. 默认值必须是编译时常量. 如果没有提供默认值,默认值是 null.

下边为可选命名参数指定默认值:


/// Sets the [bold] and [hidden] flags to the values you/// specify, defaulting to false.void enableFlags({bool bold = false, bool hidden = false}) {
  // ...}// bold will be true; hidden will be false.enableFlags(bold: true);

版本要记*** 旧代码可能使用冒号 (:)代替 = 来设置命名参数的默认值. 原因是在 SDK 1.21版本以前, 只有: 被命名参数支持. 那种支持接近弃用, 我们推荐 使用 = 指定默认值, 并且 specify an SDK version of 1.21 or higher.

下面的例子是为位置参数指定默认值:


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

也可以为 lists 或 maps使用默认值. 下例中定义了函数, doStuff(), 为 list 参数指定了默认数组, 为gifts 参数指定默认Map集合.


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


main()函数

每一个应用必须有一个顶级 main() 函数, 作为应用执行的起点.main() 函数返回 空 和一个 可选的List<String> 参数.

web应用中的main() 函数:


void main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);}

Note: .. 语句在代码前被叫做 级联. 使用级联, 你可以对单个对象执行多重操作.

命令行应用中main() 函数采用参数:


// Run the app like this: dart args.dart 1 testvoid main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');}

你可以使用 args library 定义和解析命令行参数.


语法作用域(Lexical scope)

Dart是一种具备词法作用域的语言, 意味着变量的作用范围是固定不变的, 仅仅通过布局代码. 你能“向大括号之外追溯”来看一个变量的作用域.

下面是一个嵌套函数和变量在每一级作用域的例子:


var topLevel = true;main() {
  var insideMain = true;

  myFunction() {
    var insideFunction = true;

    nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }}

注意 为什么nestedFunction() 能使用每一级的变量, 始终能访问第一级的变量.


语法闭包(Lexical closures)

一个闭包是一个能够访问其词法作用域内变量的函数对象,甚至当这个函数在其原有作用域之外被调用时.

函数内部会包含在临近作用域内所定义的变量. 在下例中, makeAdder() 捕获变量 addBy. 无论函数返回到哪里,它都存储着addBy.


/// Returns a function that adds [addBy] to the/// function's argument.Function makeAdder(num addBy) {
  return (num i) => addBy + i;}main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);}


返回值

所有函数都有返回值. 如果没有指定返回值, 语句将返回null; 依赖于函数体.


级联运算符 (..)

级联(..)允许您对同一对象进行一系列操作。 除了函数调用之外,还可以访问该对象上的字段。 这通常可以节省您创建临时变量的步骤,并允许您编写更流畅的代码。.

思考下列代码:


querySelector('#button') // 得到一个对象.
  ..text = 'Confirm'   // 使用它的成员.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一个方法叫, querySelector(), 返回选择器对象. 级联符号之后的代码在此选择器对象上运行,忽略可能返回的任何后续值.

上一个例子等价于:


var button = querySelector('#button');button.text = 'Confirm';button.classes.add('important');button.onClick.listen((e) => window.alert('Confirmed!'));

级联可以嵌套. 比如:


final addressBook = (new AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (new PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

Note: 严格来说,级联的".."符号不是运算符。 它只是Dart语法的一部分。


Switch 和 case

Switch语句在Dart中使用==比较整型, 字符串, 或编译时常量. 比较的对象必须都是同一个类的实例 (而不是其任何子类型), 而且类没有复写==. 枚举类型 非常适用于switch 语句.

Note: Dart中的Switch语句是在选择有限的情况下使用, 比如翻译器或者扫描器.

规定:每一个非空 case 分句以 break 语句结束,其它被允许可作为非空 case 分句结束的还有continue, throw, 或 return 语句.

当与case 分句未形成匹配到时,使用default分句执行代码 :


var command = 'OPEN';switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();}

下列例子中在一个case 分句中省略了一个break 语句 , 从而造成了错误:


var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break causes an exception!!

  case 'CLOSED':
    executeClosed();
    break;
}

然而, Dart支持 case 分句内容为空, 被允许的一种形式的贯穿:


var command = 'CLOSED';switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;}

如果你想实现贯穿, 可以使用 continue 语句和一个标记:


var command = 'CLOSED';switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
    // Continues executing at the nowClosed label.nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;}

case 分句可以拥有局部变量, 并且只可以在该分句的作用域里使用.


Assert

如果布尔条件为假,使用assert语句来中断正常执行,您可以在整个指南中找到assert语句的例子。 这里还有一些:


// Make sure the variable has a non-null value.assert(text != null);// Make sure the value is less than 100.assert(number < 100);// Make sure this is an https URL.assert(urlString.startsWith('https'));

Note: Assert 语句只在检查模式下执行. 在生产模式下不起作用.

将消息附加到断言, 添加一个字符串作为第二个参数.


assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

版本要点: 第二个参数在SDK 1.22开始引入.

assert的第一个参数可以是解析为布尔值或函数的任何表达式。 如果表达式的值或函数的返回值为真,则断言成功并继续执行。 如果它为false,则断言失败,并抛出异常 AssertionError) .


异常Exceptions

Dart代码可以抛出和捕获异常. 异常表示发生了某些意外的错误. 如果异常未被捕获, 引起异常的巢室将被挂起,并且巢室有 和其程序被销毁。.

与Java不同, Dart中的所有异常都属于未检查异常.方法也不声明抛出什么异常,你也没有必要捕获异常.

Dart提供 Exception 和 Error 类型,以及许多预定义的子类型. 你也可以定义自己的异常. 但是,Dart程序可以抛出任何非空对象 - 而不仅仅是Exception和Error对象 - 作为异常。.


Throw

这是一个抛出或 唤起异常的例子:


throw new FormatException('Expected at least 1 section');

你也可以随便抛出一个对象:


throw 'Out of llamas!';

因为抛出异常是一个表达式,你可以在=> 语句中以及允许表达式的任何地方抛出异常:


distanceTo(Point other) =>
    throw new UnimplementedError();


Catch

捕获异常会阻止异常传播(除非您重新抛出异常)。 捕获一个异常让你能够处理它:


try {
  breedMoreLlamas();} on OutOfLlamasException {
  buyMoreLlamas();}

要处理可能引发多种类型异常的代码,可以指定多个catch子句。 匹配抛出的对象的类型的第一个catch子句处理异常。 如果catch子句未指定类型,则该子句可以处理任何类型的抛出对象:


try {
  breedMoreLlamas();} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');}

如上述代码所示,您可以使用 on 或 catch 或两者。 当您需要指定异常类型时使用 on 。 当异常处理程序需要异常对象时使用 catch.

你可以为catch()指定两个参数. 第一个是抛出的异常, 第二个是 栈轨迹 ( StackTrace 对象).


  ...} on Exception catch (e) {
  print('Exception details:\n $e');} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');}

如果处理异常的一部分,同时允许它传播,请使用rethrow 关键字.


final foo = '';void misbehave() {
  try {
    foo = "You can't change a final variable's value.";
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }}void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }}


Finally

为确保某些代码无论有无异常都执行,请使用finally 子句. 如果catch 子句没有相匹配的异常, 则在finally 子句执行后,异常继续传播:


try {
  breedMoreLlamas();} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();}

finally 子句应该放在所有 catch 子句之后:


try {
  breedMoreLlamas();} catch(e) {
  print('Error: $e');  // Handle the exception first.} finally {
  cleanLlamaStalls();  // Then clean up.}


类 Classes

Dart是一种面向对象的语言 包含类和基于 mixin 的继承两部分。每个对象是一个类的实例, 并且 Object.是所有类的父类。 基于 mixin 的继承指的是每个类(除了 Object )都只有一个父类,类体还可以在多个类继承中被重用。

要创建一个对象,你可以使用 new 关键词并在其后跟上一个构造函数 .构造函数可以写成 *ClassName*或者*ClassName*.*identifier*. 比如:


var jsonData = JSON.decode('{"x":1, "y":2}');// Create a Point using Point().var p1 = new Point(2, 2);// Create a Point using Point.fromJson().var p2 = new Point.fromJson(jsonData);

对象拥有由函数和数据组成的成员(方法 和 实例变量).当你调用一个方法时, 是基于一个对象调用它的: 这个方法能够访问该对象的所有方法和数据.

使用一个点(.) 引用实例变量或方法:


var p = new Point(2, 2);// Set the value of the instance variable y.p.y = 3;// Get the value of y.assert(p.y == 3);// Invoke distanceTo() on p.num distance = p.distanceTo(new Point(4, 4));

使用 ?. 代替 . 来避免当最左操作数为null时产生的异常:


// If p is non-null, set its y value to 4.p?.y = 4;

一些类提供常量构造函数。 要使用常量构造函数创建编译时常数,请使用const 代替 new:


var p = const ImmutablePoint(2, 2);

构造两个相同的编译时常量导致一个单一的规范的实例:


var a = const ImmutablePoint(1, 1);var b = const ImmutablePoint(1, 1);assert(identical(a, b)); // They are the same instance!

在运行时获取一个对象的类型, 你可以使用Object类的 runtimeType 属性, 该属性返回一个 Type 对象.


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

以下部分讨论如何实现类.


变量实例

声明实例变量:


class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.}

所有为初始化的实例变量值为 null.

所有实例变量都生成一个隐式的 getter 方法. 非最终实例变量也生成一个隐式 setter 方法. 更多查看 Getters 和 setters.


class Point {
  num x;
  num y;}main() {
  var point = new Point();
  point.x = 4;          // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.}

如果你要在实例变量声明的时候为其初始化值(而不是通过构造函数或方法),那么当创建实例时就该为其设值,该值在构造函数和其初始化程序列表之前执行.


构造函数

通过创建一个与其类名相同的函数来声明构造函数 (plus, optionally, an additional identifier as described inNamed constructors). 构造函数的最常见形式是生成构造函数,创建一个类的新实例:


class Point {
  num x;
  num y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }}

this 关键字引用当前实例.

**Note:**只有当命名出现冲突时使用 this. 否则,Dart风格指南建议不要用 this.

向实例变量分配构造函数的参数是很常见的一种模式,Dart语法糖让其变得容易:


class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);}

默认构造函数

如果您没有声明构造函数,则会为您提供默认构造函数。 默认构造函数没有参数,并调用父类中的无参数构造函数。.

构造函数不能继承

子类不会从他们的超类继承构造函数.声明没有构造函数的子类只有默认(无参数,无名称)构造函数.

命名构造函数

使用命名构造函数为类实现多个构造函数或提供额外的声明:


class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }}

请记住,构造函数不是继承的,这意味着父类的命名构造函数不会被子类继承。 如果要使用父类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数.

调用父类的非默认构造函数

默认情况下,子类中的构造函数调用超类的未命名的无参数构造函数。 超类的构造函数在构造函数体的起始处被调用。 如果一个 初始化器列表 也被使用,它将在超类被调用之前执行。 总而言之,执行顺序如下:

  1. 初始化程序列表

  2. 父类的无参构造

  3. 主类的无参构造

如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。 在冒号 (:)之后,在构造函数体(如果有的话)之前指定超类构造函数.

下面的例子中,Employee类的构造函数调用了其父类Person的命名构造函数.


class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }}class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }}main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';}

因为在调用构造函数之前对父类构造函数的参数进行求值,所以参数可以是一个表达式,例如函数调用:


class Employee extends Person {
  // ...
  Employee() : super.fromJson(findDefaultData());}

Note: 在构造函数初始化列表中使用super() 时,将其放在最后. 更多信息查看 Dart usage guide.

Warning: 父类的构造函数的参数无权访问 this. 比如, 参数能访问静态方法不能访问实例方法.

Initializer list

除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量,用逗号分隔初始化器.


class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Initializer list sets instance variables before
  // the constructor body runs.
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }}

Warning: 初始化程序的右侧无法访问 this.

初始化器列表在设置final字段时很方便。 以下示例在初始化程序列表中初始化三个final字段。


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);}

重定向构造函数

有时,构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的正文是空的,构造函数调用出现在冒号(:)之后.


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);}

常量构造函数

如果您的类生成永远不会更改的对象,则可以使这些对象的编译时常量。 为此,定义一个 const 构造函数,并确保所有实例变量都是 final.


class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);}

Factory 构造函数

当不需要总是创建该类的新实例时,使用 factory 关键字. 例如, 一个工厂构造函数可能从缓存中返回实例或返回一个子类的实例.

下面的例子演示工厂构造函数如何从缓存中返回对象:


class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to the _ in front
  // of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }}

Note: 工厂构造函数不能访问 this.

调用工厂构造函数, 使用 new 关键字:


var logger = new Logger('UI');logger.log('Button clicked');


方法

方法是为对象提供行为的函数.

实例方法

对象上的实例方法可以访问实例变量和 this. 下例中 distanceTo() 方法是一个实例方法的示例:


import 'dart:math';class Point {
  num x;
  num y;
  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }}

Getters and setters

Getters和setter是为对象的属性提供读写访问权限的特殊方法。 回想一下,每个实例变量都有一个隐式的getter,如果合适的话加一个setter。 您可以使用 get 和 set 关键字来实现getter和setter来创建其他属性


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

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

  // Define two calculated properties: right and 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;}main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);}

使用getter和setter,您可以从实例变量开始,稍后用方法包装它们,而不用改变客户端代码.

Note: 如运算符(++)以预期的方式工作,无论是否明确定义了getter. 为了避免任何意外的发生,操作符只调用一次getter,将其值保存在临时变量中.

抽象方法

实例,getter和setter方法可以是抽象的,定义一个接口,但将其实现交给其他类。 要使方法变成抽象方法,请使用分号 ( 而不是方法体:


abstract class Doer {
  // ...Define instance variables and methods...

  void doSomething(); // Define an abstract method.}class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }}

调用抽象方法会导致运行时错误。.

查看 Abstract classes.

可覆盖的操作符

您可以覆盖下表中显示的运算符。 例如,如果定义Vector(向量)类,则可以定义一个+ 方法来添加两个向量.

下面是覆盖 + 和 - 操作符的实例:


class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  /// Overrides + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  /// Overrides - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }}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);}

如果你覆盖 ==, 你也应该覆盖Object的 hashCode的 getter方法. 覆盖 == 和 hashCode 的例子,


抽象类

使用 abstract 修饰符来定义一个 抽象类— 一个不能实例化的类. 抽象类对于定义接口很有用,通常有一些实现. 如果想让抽象类变得可实例化 请使用 factory constructor .

抽象类通常具有 抽象方法. 这是一个声明一个抽象类的例子,它有一个抽象方法:


// This class is declared abstract and thus// can't be instantiated.abstract class AbstractContainer {
  // ...Define constructors, fields, methods...

  void updateChildren(); // Abstract method.}

以下类不是抽象的,因此即使它定义了一个抽象方法也可以实例化:


class SpecializedContainer extends AbstractContainer {
  // ...Define more constructors, fields, methods...

  void updateChildren() {
    // ...Implement updateChildren()...
  }

  // Abstract method causes a warning but
  // doesn't prevent instantiation.
  void doSomething();}


隐式接口

每个类隐式定义一个包含类的所有实例成员以及它实现的任何接口的接口。 如果要创建一个支持B类API而不继承B实现的A类,则A类应实现B接口.

一个类实现一个或多个接口通过在 implements 子句中声明它们,然后提供接口所需的API来实现。 例如:


// A person. The implicit interface contains greet().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(who) => 'Hello, $who. I am $_name.';}// An implementation of the Person interface.class Imposter implements Person {
  // We have to define this, but we don't use it.
  final _name = "";

  String greet(who) => 'Hi $who. Do you know who I am?';}greetBob(Person person) => person.greet('bob');main() {
  print(greetBob(new Person('kathy')));
  print(greetBob(new Imposter()));}

下面是一个指定一个类实现多个接口的例子:


class Point implements Comparable, Location {
  // ...}


扩展一个类

使用 extends 关键字创建子类, super 引用父类:


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

覆盖成员

子类可以覆盖实例方法,getter和setter, 您可以使用@override 注解来表示您有意覆盖成员:


class SmartTelevision extends Television {
  @override
  void turnOn() {
    // ...
  }
  // ...}

有时缩小方法参数的类型或保证代码中实例变量是 type safe, 可以使用 covariant 关键字.

noSuchMethod()

为了检测或应对尝试使用不存在的方法或实例变量, 你可以复写 noSuchMethod():


class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member:' +
          '${mirror.memberName}');
  }}

如果你使用 noSuchMethod() 为一个或多个类型实现可能的getter、setter、方法, 你可以使用 @proxy 注解来避免警告:


@proxyclass A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }}

一种替代 @proxy的方式是, 如果在编译时你知道有那些类型, 就是类声明实现了的类.


class A implements SomeClass, SomeOtherClass {
  void noSuchMethod(Invocation mirror) {
    // ...
  }}

了解更多注解信息, 查看 Metadata.


向类中添加功能:mixins

Mixins是在多个类层次结构中重用类的代码的一种方式.

要使用mixin,请使用with 关键字后跟一个或多个mixin名称。 以下示例显示使用mixins的两个类:


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

要实现一个mixin,创建一个扩展Object的类,没有声明构造函数,没有调用super. 例如:


abstract 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');
    }
  }}

Note: 从1.13起,Dart VM已经取消了对mixin的两个限制::

  • Mixins允许从Object以外的类扩展.

  • Mixins可以调用super().

dart2js 中还不支持这些 “super mixins” , 并且在Dart分析器中需要 --supermixin 标志.

更多信息查看 文章 Mixins in Dart.


类变量和方法

使用 static 关键字来实现类范围的变量和方法.

静态变量

静态变量(类变量)对于类范围的状态和常量很有用:


class Color {
  static const red =
      const Color('red'); // A constant static variable.
  final String name;      // An instance variable.
  const Color(this.name); // A constant constructor.}main() {
  assert(Color.red.name == 'red');}

直到使用时静态变量才被初始化.

Note: 此页面遵循 风格指南建议 ,优选使用lowerCamelCase 作为常量名称.

静态方法

静态方法(类方法)不对一个实例进行操作,因此无法访问this. 例如:


import 'dart:math';class Point {
  num x;
  num 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);
  }}main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(distance < 2.9 && distance > 2.8);}

Note: 考虑使用顶级函数,而不是静态方法,用于常用或广泛使用的实用程序和功能.

您可以使用静态方法作为编译时常量。 例如,您可以将静态方法作为参数传递给常量构造函数.


泛型

如果您查看基本数组类型List的API文档, List, 您将看到类型实际上是List<E>. <…> 符号将List标记为 通用 (或参数化)类型—一个有正规类型参数的类型. 按照惯例,类型变量具有单字母名称,例如: E, T, S, K, 和 V.


为什么要用泛型?

因为Dart中的类型是可选的,所以您不必使用泛型 . 但是,您可能希望,由于同样的原因,您可能希望在代码中使用其他类型:types(通用或不通用)可让您记录和注释代码,从而使您的意图更清晰.

例如,如果您打算列出只包含字符串,则可以将其声明为 List<String> (将其作为“List of string). 这样你,你的同行程序员和你的工具(如IDE和Dart VM在检查模式下)可以检测到将非字符串分配给列表可能是一个错误。 以下是一个例子:


var names = new List<String>();names.addAll(['Seth', 'Kathy', 'Lars']);// ...names.add(42); // Fails in checked mode (succeeds in production mode).

使用泛型的另一个原因是减少代码重复.泛型让您在多个类型之间共享一个接口和实现, 同时仍然利用检查模式和静态分析预警。 例如,假设您创建一个用于缓存对象的接口:


abstract class ObjectCache {
  Object getByKey(String key);
  setByKey(String key, Object value);}

你发现你想要这个接口的字符串特定版本,所以你创建另一个接口:


abstract class StringCache {
  String getByKey(String key);
  setByKey(String key, String value);}

接着你想要这个接口的数字特别版本… 于是你有了个主意.

通用类型可以节省您创建所有这些接口的麻烦。 相反,您可以创建一个接受类型参数的单一接口:


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

在此代码中,T是占位类型。 这是一个占位符,您可以将其视为开发人员稍后将定义的类型.


使用集合字面量

List 和 map 字面量能被参数化.参数化字面量就像你之前见过的字面量一样,除了你在括号之前使用的 <*type*> (对于list集合) 或 <*keyType*, *valueType*> (对于map集合) . 当您想要在检查模式下输入警告时,可以使用参数化字面量。 以下是使用类型字面量的示例:


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'};


构造函数上使用参数化类型

要在使用构造函数时指定一个或多个类型,请将类型放在类名后面的尖括号(<...>) . 例如:


var names = new List<String>();names.addAll(['Seth', 'Kathy', 'Lars']);var nameSet = new Set<String>.from(names);

下例中创建了一个键为Integer类型,值为View类型的map集合:


var views = new Map<int, View>();


泛型集合及其包含的类型

Dart泛型类型被 修改, 意味着会附带类型信息. 例如,您可以测试集合的类型,即使在生产模式下:


var names = new List<String>();names.addAll(['Seth', 'Kathy', 'Lars']);print(names is List<String>); // true

但是,is 表达式仅检查集合的类型 —而不是里面的对象. 在生产模式下, List<String> 里面可能含有非String类型的项. 解决方案是检查每一项的类型或使用异常处理程序包裹项操作代码 (查看 Exceptions).

Note: 相比之下,Java中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。 在Java中,您可以测试对象是否为List,但是不能测试它是否为 List<String>.


限制参数化类型

实现泛型类型时,可能需要限制其参数的类型。 你可以使用extends来实现 .


// T must be SomeBaseClass or one of its descendants.class Foo<T extends SomeBaseClass> {...}class Extender extends SomeBaseClass {...}void main() {
  // It's OK to use SomeBaseClass or any of its subclasses inside <>.
  var someBaseClassFoo = new Foo<SomeBaseClass>();
  var extenderFoo = new Foo<Extender>();

  // It's also OK to use no <> at all.
  var foo = new Foo();

  // Specifying any non-SomeBaseClass type results in a warning and, in
  // checked mode, a runtime error.
  // var objectFoo = new Foo<Object>();}


使用泛型方法

最初,Dart的通用支持仅限于类。 一种较新的语法(称为泛型方法)允许在方法和函数上使用类型参数:


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;}

在 first (<T>) 中的泛型类型参数 允许在多个地方使用参数T :

  • 函数返回类型 (T).

  • 参数类型 (List<T>).

  • 本地变量 (T tmp).

版本要点*** SDK 1.21. 中介绍了泛型方法的新语法。 如果使用泛型方法,请选用 SDK版本为1.21或更高版本.

关于泛型的更多信息, 参阅 Dart中的可选类型 和 使用通用方法.


库及可见性

import 和 library 指令能让你创建模块化和可共享的代码库. 库不仅提供API,而且是一个隐私单位: 以下划线(_)开头的标识符只能在库内部显示。 每个Dart应用程序都是一个库,

最新评论

添加评论

自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论