Dart(二)类与函数,代码规范

本文详细介绍了Dart编程中的类与函数定义,包括默认值、const和final的区别,条件运算符、赋值运算符和级联运算符的用法,以及函数的正常、命名和匿名定义。此外,还涵盖了类的构造函数(如默认、命名、常量和工厂构造函数),初始化列表、单例模式、接口实现、扩展方法、操作符重载、可调用类和Mixin的概念。同时,文章强调了代码风格和规范的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Dart(二)类与函数,代码规范

语法前菜

变量
  • 默认值

  • late

    • Dart 2.12 added the late modifier, which has two use cases:
      • Declaring a non-nullable variable that’s initialized after its declaration.
      • Lazily initializing a variable.
  • finalconst

    一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。

    顶层的 final 变量或者类的 final 变量在其第一次使用的时候被初始化。

    使用关键字 const 修饰变量表示该变量为 编译时常量。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(译者注:顺序不能颠倒)。在声明 const 变量时可以直接为其赋值,也可以使用其它的 const 变量为其赋值。

    const 关键字不仅仅可以用来定义常量,还可以用来创建 常量值,该常量值可以赋予给任何变量。你也可以将构造函数声明为 const 的

    • const编译时需要编译时已经确定的值,final用来修饰变量,只能被赋值一次,在运行时赋值
    • 类级别的常量,通常用 static const,顺序不可变
    • const可以使用其他const常量的值来初始化其值,final不可以
    • const不可变性可传递,final不可以传递
    • final修饰的变量内存中重复创建,const相同
运算符
  • 条件运算符(? : ??

    Dart 有两个特殊的运算符可以用来替代 if-else 语句.

    条件 ? 表达式1 :表达式2

    如果条件为 true,执行表达式 1并返回执行结果,否则执行表达式 2 并返回执行结果。

    表达式1 ?? 表达式2

    如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。

  • 赋值运算符(??=

    为值为 null 的变量赋值

  • 级联运算符 (.. ?..)

    可以在同一个对象上连续调用多个对象的变量或方法。

问题:(与上面无关)

假设有一个Map<String, dynamic>类型的变量,里边有10个键值对;后端提有一个接口,需要把这个map传过去,还需要再增加5个新的键值对,问实现方案?


Map<String, dynamic> params = {
	... // 10个键值对
};

Map<String, dynamic> newParams = {
  ... // 5个键值对
}

///为params增加新的newParams键值对,有几种方案得到result?
Map<String, dynamic> result = ?;

var response = await NHRequest.post(Host.OtmsWebPlus, '/sku/page', params: result);

image-20210803175006439

  • 展开操作符(...),空感知操作符(...?

    Dart 在 2.3 引入了 扩展操作符...)和 空感知扩展操作符...?),它们提供了一种将多个元素插入集合的简洁方法。

    例如,你可以使用扩展操作符(...)将一个 List 中的所有元素插入到另一个 List 中:

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

    如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符(...?)来避免产生异常:

    var list;
    var list2 = [0, ...?list];
    assert(list2.length == 1);
    
    static Future queryGoodsListV2(int pageNum, int pageSize,
          {dynamic params}) async {
        var response = await NHRequest.post(Host.OtmsWebPlus, '/sku/page',
            params: {
              'status': Constant.STATUS_ENABLED,
              'customerTenantId': UserModel.shareInstance.customerCode,
              'deleteStatus': 0, // 0表示未删除,1表示已删除
              ...?params
            },
            queryParameters: {
              'page': pageNum + 1,
              'size': pageSize,
              'sort': 'create_time__DESC',
            });
        return response.data['data'];
      }
    

函数

正常函数
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
参数
  • 必要参数

    bool isNoble(int paramA) {}
    
  • 命名参数

    bool isNoble(int paramA, {int paramB}) {}
    
    • required

      Non-nullable版本支持,Dart 2.12

    • @required

      bool isNoble(int paramA, {required int paramB}) {}
      
  • 可选参数

    bool isNoble(int paramA, [int paramC, int paramD]) {}
    
匿名函数

创建一个没有名字的方法,称之为 匿名函数Lambda 表达式Closure 闭包。你可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。

([[类型] 参数[, …]]) {
  函数体;
}; 
(String name) {
	print(name);
}
词法作用域(Lexical scoping

注意变量及方法调用的作用域

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

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

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。

var n = 999;
f1() {
	print(n);
}
f1(); // 999

上面代码中,函数f1可以读取全局变量n。

f1() {
	var n = 999;
}
print(n);
// Uncaught ReferenceError: n is not definedCopy to clipboardErrorCopied

上面代码中,函数f1内部声明的变量n,函数外是无法读取的。

f1() {
  var n = 999;
  f2() {
    print(n); // 999
  }
}

在函数的内部,再定义一个函数。

思考: 上面的代码中,如何把n暴露出来呢?

  • 闭包是一个方法(对象)
  • 闭包定义在其他方法内部
  • 闭包能够访问外部方法内的局部变量,并持有其状态(这是闭包最大的作用,可以通过闭包的方式,将其暴露出去,提供给外部访问)

例子:

void main() {
  var func = abc();
  func();
  func();
  func();
}

abc () {
  int count = 0;
  return () {
    print(count++);
  };
}

普通类、抽象类、可调用的类、枚举类、mixin,Object

构造函数

声明一个与类名一样的函数即可声明一个构造函数

构造方法不允许重载?

实例方法不允许重载?

默认构造函数

如果你没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。

class Point {
}

主构造函数

class Point {
  final double x, y;

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

class Point {
	final double x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}
final double xOrigin = 10;
final double yOrigin = 20;

///类1
class Point1 {
  double x, y;

  Point1(x, y): x = x, y = y;

  Point1.origin()
      : x = xOrigin,
        y = yOrigin;

  Point1.alongXAxis(double x) : this(x, 0);
}

///类2
class Point2 {
  final double x, y;
  
  const Point(this.x, this.y);
}
命名构造函数

为一个类声明多个命名式构造函数来表达更明确的意图

final double xOrigin = 10;
final double yOrigin = 20;

class Point {
  final double x, y;

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}
常量构造函数

如果类生成的对象都是不变的,可以在生成这些对象时就将其变为编译时常量。你可以在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现该功能。

class Point {
  final double x, y;
  // int z;
  
  const Point(this.x, this.y);
}

image-20210804215231128

重定向构造函数

有时候类中的构造函数仅用于调用类中其它的构造函数,此时该构造函数没有函数体,只需在函数签名后使用(:)指定需要重定向到的其它构造函数 (使用 this 而非类名):

final double xOrigin = 10;
final double yOrigin = 20;

class Point {
  final double x, y;

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}
工厂构造函数

使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。

当执行构造函数并不总是创建这个类的一个新实例时,则使用factory关键字。

例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。

可以通过代码控制返回需要的实例,且不能访问this指针。

class Point {
  // factory constructor
  factory Point.of(a, b, c, d) {
    return Point(a + b, c + d);
  }
}
初始化列表

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

初始化列表表达式 = 右边的语句不能使用 this 关键字。

class First {
  final int param1;
  var param2;
  First()
      : param1 = 4,
        param2 = 'hello';
}
初始化顺序
  • 初始化列表

  • 父类的构造函数

  • 当前类的构造函数

单例实现
///
/// Singleton
class Singleton {
  ///定义一个私有的静态变量
  static Singleton _singleton;

  ///定义一个私有的命名构造函数
  Singleton._instance();

  ///定义一个工厂默认构造函数,虽然对外暴露,但不会生成多个实例化对象
  factory Singleton() => _getSingleton();

  ///实现一个私有的实例化判断方法,控制全局唯一
  static Singleton _getSingleton() {
    if (_singleton == null) {
      _singleton = Singleton._instance();
    }
    return _singleton;
  }

  ///对外暴露的静态对象访问点
  static Singleton get instance => _getSingleton();

  ///单例内部实现方法
  void display() {
    //TODO
  }
}

其他
接口(implements

每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。

如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。

扩展方法(extension...on...

Extension methods, introduced in Dart 2.7, are a way to add functionality to existing libraries. You might use extension methods without even knowing it. For example, when you use code completion in an IDE, it suggests extension methods alongside regular methods.

操作符重载(operator
class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}
可调用类(call

通过实现类的 call() 方法,允许使用类似函数调用的方式来使用该类的实例。

class WannabeFunction {
  String call(String a, String b, String c) => '$a $b $c!';
}

var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');

void main() => print(out);
Mixin(mixinon

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

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

  • 想要实现一个 Mixin,请创建一个继承自 Object 且未声明构造函数的类。除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 mixin 替代 class

    • 两种方式实现mixin
  • 可以使用关键字 on 来指定哪些类可以使用该 Mixin 类

    import 'first.dart';
    
    class Mixin {
      display() {
        print('in Mixin invoke display()');
      }
    }
    
    mixin Mixin2 on First { //这里指定Mixin2只能用在First及其子类上,所以下面引用Mixin2时会编译报错
      display2() {
        print('in MixinExt invode displayExt()');
      }
    }
    
    class ConCreator with Mixin, Mixin2 {} //这里Mixin2编译报错
    
    void main() {
      ConCreator()
        ..display()
        ..display2();
    }
    
    

自己思考

extends、implements、mixin区别在哪里?

代码格式规范

注释

如果第一个单词不是大小写相关的标识符,则首字母要大写。使用句号,叹号或者问号结尾。所有的注释都应该这样:文档注释,单行注释,甚至 TODO。即使它是一个句子的片段。

  • 文档注释

    由于历史原因,dartdoc 支持两种格式的文档注释:/// (“C# 格式”) 和 /** ... */ (“JavaDoc 格式”)。我们推荐使用 /// 是因为其更加简洁。 /***/ 在多行注释中间添加了开头和结尾的两行多余内容。 /// 在一些情况下也更加易于阅读,例如,当文档注释中包含有使用 * 标记的列表内容的时候。

    在文档注释中,除非用中括号括起来,否则分析器会忽略所有文本。使用中括号可以引用类、方法、字段、顶级变量、函数和参数。括号中的符号会在已记录的程序元素的词法域中进行解析。

    通常使用文档注释来为类、方法、变量进行说明

    把文档注释放到注解之前。
    [good]
    /// A button that can be flipped on and off.
    (selector: 'toggle')
    class ToggleComponent {}
    
    [bad]
    (selector: 'toggle')
    /// A button that can be flipped on and off.
    class ToggleComponent {}
    
  • 单行注释

    单行注释以 // 开始。所有在 // 和该行结尾之间的内容均被编译器忽略。

  • 多行注释

    多行注释以 /* 开始,以 */ 结尾。所有在 /**/ 之间的内容均被编译器忽略(不会忽略文档注释),多行注释可以嵌套。

    通常使用块注释 (/* ... */) 来临时的注释掉一段代码,但是其他的所有注释都应该使用 //

    • 不要 使用块注释作用作解释说明。
      [good]
      void greet(String name) {
        // Assume we have a valid name.
        print('Hi, $name!');
      }
      
      [bad]
      void greet(String name) {
        /* Assume we have a valid name. */
        print('Hi, $name!');
      }
      
代码风格

参考:Dart代码风格

命名规范

参考:DartAPI设计规范

格式化

和其他大部分语言一样, Dart 忽略空格。但是,不会。具有一致的空格风格有助于帮助我们能够用编译器相同的方式理解代码。

  • 空格(运算符之间、括号等)
  • 单行不超过80字符,as里默认设置80
  • as快捷键格式化dart
  • dart format命令
好处
  • 保持代码风格统一
  • 提高代码可读性
  • 提高开发效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值