Dart的重要知识点

1. const 和 final

  1. Dart中的const定义一个编译时常量。编译时常量是指,在编译时该const值必须确定。而final是指变量的值只能被设置一次。可以是在运行时确定。

    示例1:

    // 正确
    final time = new DateTime.now();
    // 错误。因为 DateTime.now()在运行时才能获知时间,在编译时无法获知。
    const time = new DateTime.now();
    
  2. const创建的常量是深度递归的,例如常量容器也内元素也是不能变的,但是final修饰的容器内元素可以改变。

    示例:

    const foo1 = [1,2];
    foo1[0]=3; // 错误
    foo1.add(3); // 错误
    
    const foo1 = [1,2];
    foo1[0]=3; // 正确
    foo1.add(3); // 正确
    
  3. 相同的const对象只会被创建一次,而相同的final对象被创建多个。

  4. const 可以用来申明常量变量,也可以创建常量值。

    示例:

    var foo = const[1,2,3]; // const修饰的是[1,2,3],所以[1,2,3] 不能更改,但是foo指向的对象可以更改。
    foo=[1,2]; //正确
    
    const foo = [1,2,3];
    // 这表示修饰的foo是一个常量变量,不可更改foo指代的对象。
    // 根据语法可推测此时[1,2,3] 中的内容是可以更改,但是实际上  
    foo[1] = 4; // 错误 
    //  const foo = [1,2,3]; 等于 const foo = const [1,2,3];
    // 可以认为const foo =xxx 时 const foo = const xxx 的简写形式
    
    // final 修饰的变量所指代的对象是可以更改对象的内容,但是不能更改变量指代的对象。 所以可以认为以下两句是等价的:
    final foo = const [1,2,3];
    const foo = [1,2,3];
    

2. Mixin

mixin是一个类,其他类可以访问mixin类中的方法、成员变量而不必继承该类。

mixin是一个普通的类(在Dart语言中该类没有构造方法),其他类可以继承该类中的方法和变量而不用继承整个类。

  1. 在java语言中,类的继承关系是单继承的,可以extends一个父类并可以implements多个接口。在Dart中也同样采用这种继承关系。只不过Dart中没有interface 关键字,只有普通类和抽象类。当一个类被作为接口使用,那么在子类中必须重写这个接口类中的所有方法,而不管该方法是否是抽象方法。

    所以当有如下继承关系时:

    继承关系
    我们在不用Mixin的情况下,java代码的写法和Dart写法基本一致。

    // 使用接口的语法
    
    // 动物
    abstract class Animal {
      void live();
    }
    
    // 水生动物
    class Aquatic extends Animal {
      @override
      void live() {
        print("我生活在水中!");
      }
    }
    
    // 陆生动物
    class Terrestrial extends Animal {
      @override
      void live() {
        print("我生活在陆地!");
      }
    }
    
    // 定义接口-----------------
    abstract class Flyable {
      void fly() {
        print("我可以飞行!");
      }
    }
    
    abstract class Swimmable {
      void swim() {
        print("我可以游泳!");
      }
    }
    
    abstract class Singable {
      void sing();
    }
    // ------------------------
    
    //鲨鱼
    class Shark extends Aquatic implements Swimmable {
      @override
      void swim() {
        print('我可以游泳');
      }
    }
    
    // 章鱼
    class Octopus extends Aquatic implements Swimmable {
      @override
      void swim() {
        print('我可以游泳');
      }
    }
    
    // 猫
    class Cat extends Terrestrial implements Singable {
      @override
      void sing() {
        print('喵喵叫!');
      }
    }
    
    // 鸟
    class Bird extends Terrestrial implements Singable, Flyable {
      @override
      void sing() {
        print("啾啾叫!");
      }
    
      @override
      void fly() {
        print("我可以飞行!");
      }
    }
    
    main(List<String> args) {
      Shark().swim();
      Octopus().swim();
      Cat().sing();
      Bird().sing();
      Bird().fly();
    }
    /*
    我可以游泳
    我可以游泳
    喵喵叫!
    啾啾叫!
    */
    

    上述代码中,虽然abstract类中已经对方法提供了实现,但是当该抽象类作为接口使用,子类必须重写该接口中的所有方法。观察上述代码Swimmable 接在,在实现该接口的SharkOctopus类中, 重新的swim方法完全一样,造成了代码的冗余, 此时可以使用Mixin来来解决该问题 , 将以上代码修改:

    // 使用mixin
    
    // 动物
    abstract class Animal {
      void live();
    }
    
    // 水生动物
    class Aquatic extends Animal {
      @override
      void live() {
        print("我生活在水中!");
      }
    }
    
    // 陆生动物
    class Terrestrial extends Animal {
      @override
      void live() {
        print("我生活在陆地!");
      }
    }
    
    // 定义接口-----------------
    mixin Flyable {
      void fly() {
        print("我可以飞行!");
      }
    }
    
    mixin  Swimmable {
      void swim() {
        print("我可以游泳!");
      }
    }
    
    abstract class Singable {
      void sing();
    }
    // ------------------------
    
    //鲨鱼
    class Shark extends Aquatic with Swimmable {
      
    }
    
    // 章鱼
    class Octopus extends Aquatic with Swimmable {
      
    }
    
    // 猫
    class Cat extends Terrestrial implements Singable {
      @override
      void sing() {
        print('喵喵叫!');
      }
    }
    
    // 鸟
    // with 关键字必须在implements前面
    class Bird extends Terrestrial  with Flyable implements Singable {
      @override
      void sing() {
        print("啾啾叫!");
      }
    }
    
    main(List<String> args) {
      Shark().swim();
      Octopus().swim();
      Cat().sing();
      Bird().sing();
      Bird().fly();
    }
    
    

    上述代码中我将Flyable, 和 Swimmable 改为了mixin . 此时(使用with关键字来继承mixin类) 子类中不必重写mixin中已经实现的方法.

    为啥不用extends方式继承? 因为extends是单继承的, 使用with可以加入多个混合类.

    上述示例中可以看出mixin的优点:

    • 解决了extends不能多继承问题
    • 解决了接口继承方式子类必须重写接口中所有方法(不管是否是抽象方法)的缺点.
  2. mixin的继承顺序, 当with关键字后继承多个mixin类且这些类有相同的方法时:

    • 子类中的重写版
    • with关键字后的多个mixin类的最后一个.

    其实mixin其继承原理,并不是只继了最后一个版本,而是按照顺序继承, 例如:
    class C with A, B{}
    其继承关系是 C->B->A, 即当A,B,C中有同名方法时, B中方法覆盖A中的,C中方法覆盖B中的.

    例如:

    mixin A {
      void test() {
        print("Im class A");
      }
    }
    
    mixin B {
      void test() {
        print("Im class B");
      }
    }
    
    class C with A, B {}
    
    main(List<String> args) {
      C().test();
    }
    
    // 输出:
    // Im class B
    

    示例:

    abstract class Base {
      void test() {
        print("Im class Base");
      }
    }
    
    mixin A on Base {
      void test() {
        print("Im class A");
        super.test();
      }
    }
    
    mixin B on Base {
      void test() {
        print("Im class B");
        super.test();
      }
    }
    
    class C extends Base with A, B {
      @override
      void test() {
        print("Im class C");
        super.test();
      }
    }
    
    main(List<String> args) {
      C().test();
    }
    
    // 输出:
    //Im class C
    //Im class B
    //Im class A
    //Im class Base
    
  3. on 关键字在 mixin中的使用, 声明mixin类时, 可以使用on 关键字来申明该mixin类可以在哪个类的子类中使用.

    abstract class Base {
      void say() {
        print("Im base class");
      }
    }
    
    mixin MixFun on Base {
      void test() {
        print('Im Mixin funcion');
      }
    }
    
    // 正确, 因为BaseExt是Base类的子类, MixFun可以混入
    class BaseExt extends Base with MixFun {
    
    }
    
    // 报错!!, 因为 ObjTest不是Base以及Base的子类.
    class ObjTest with MixFun {
    
    }
    

3. Future和Stream

Future表示不能立即完成的计算。普通函数返回结果,异步函数返回Future,后者最终将包含结果。当结果准备好时,未来会告诉你。
Stream是异步事件的序列。它就像一个异步迭代,当你请求它时,流不是得到下一个事件,而是告诉你当它准备好时有一个事件。

4. dynamic/var/object

  1. dynamic是所有dart 对象的基础类型,在大多数情况下,不直接使用它。
    通过它定义的变量会关闭类型检查,这意味着 dynamix x= 'hi'; x.foo();这段代码静态类型检查不会报错,但是运行时会crash,因为String并没有foo() 方法,所以建议大家在编程时不要直接使用dynamic;
  2. var是一个关键字,系统会自动判断类型 runtimeType;
  3. object:是Dart 对象的基类,定义object o =xxx;时系统会认为o 是个对象,你可以调用o的toString()hashCode()方法因为Object 提供了这些方法,但是如果你尝试调用o.foo()时,静态类型检查会运行报错。

总结:dynamic 与object 的最大的区别是在静态类型检查上。

5. Runes 和 Symbols

1.Runes

Runes是TUF-32字符集。在Dart语言中的String是使用UTF-16表示的。所以UTF-32需要使用专门的类来表达。比如\u{1f600}代表笑脸emoji。

main(List<String> args) {
  var s = '\u{1f600}';
  print(s.runtimeType); // String
  print(s.runes.runtimeType); // Runes
  print(s); //输出:😀。 说明String在输出时自动转码
  print(s.runes); //输出:(128512) .输出的是编码值

  // 创建一个Runes对象
  Runes runes = Runes('\u{2665} , \u{1f605}, \u{1f60e}');
  print(runes); // 输出:(9829, 32, 44, 32, 128517, 44, 32, 128526)

  // 输出字符
  print(String.fromCharCodes(runes)); // 输出:♥ , 😅, 😎
}

此外, Runes继承自Iterable , 所以Runes是课迭代对象。

2.Symbols
官网解释很模糊。只说用到的机会很少。然后就没了。。。

教程中有一个在反射机制上的应用。

反射机制加载类,需要使用Symbol类作为参数。以下为示例:
首先定义一个library:

library fool_lib;

class Foo {
  f1() {
    print("function 1");
  }

  f2() {
    print("function 2");
  }

  f3() {
    print("Function 3");
  }
}

使用反射机制加载该library

import 'dart:mirrors';
import 'fool_lib.dart';

main(List<String> args) {
  Symbol lib = Symbol("fool_lib");
  Symbol className = Symbol("Foo");
  if (checkIfClassAvailableInLibary(lib, className)) print("class fount");
}

bool checkIfClassAvailableInLibary(Symbol lib, Symbol className) {
  MirrorSystem mirrorSystem = currentMirrorSystem();
  LibraryMirror libMirror = mirrorSystem.findLibrary(lib);

  if (libMirror != null) {
    print("找到模块");
    print("${libMirror.declarations.length}");
    libMirror.declarations.forEach((key, value) {
      print(key);
    });
  }

  if (libMirror.declarations.containsKey(className)) {
    return true;
  }

  return false;
}

6. getter和setter

getset方法为类的成员属性提供了读和写的方法. 每个类的实例其实都包含了隐式的get和set. 同时我们也可以显示定义get和set方法. 使用get和set方法定义的成员变量无法在类的构造函数中赋值.

例如:

class A{
	set a(int v) =>  a=v;
	int get a => a;
	A(this.a); // 报错
}

get的使用示例:


class Vehicle {
  String make;
  String model;
  int manufactureYear;
  int vehicleAge;
  String color;

  Map<String,dynamic> get map {
    return {
      "make": make,
      "model": model,
      "manufactureYear":manufactureYear,
      "color": color,
    };
  }

  int get age {
    return DateTime.now().year - manufactureYear;
  }

  void set age(int currentYear) {
    vehicleAge = currentYear - manufactureYear;
  }

  Vehicle({this.make,this.model,this.manufactureYear,this.color,});
}

main(){
  Vehicle car = Vehicle(
    make:"Honda",
    model: "Civic",
    manufactureYear:2010,
    color: "red",
  );
  
  print(car.age);// 输出车龄
  print(car.map);// 将car的属性组装成map输出
}

7. Dart构造函数

Dart语言的构造函数有4种:

  • 普通构造函数
  • 命名构造函数
  • 常量构造函数
  • 工厂构造函数
  1. 默认构造函数

如果定义了类,而没有提供构造函数,那么它有一个默认无参数的构造函数.

2.普通构造函数

class A{
	num x,y;
	A(num x, num y){
		this.x=x;
		this.y=y;
	} 

	A(this.x, this.y); // 上述构造函数可以简写为此形式, 此为dart语言的语法糖
	
}

3.命名构造函数

Dart语言的普通构造函数不能重载,当需要有多个构造函数时,需要使用命名构造函数.
此外,命名构造函数不能被继承.

class A{
	num x,y;
	A(this,x,this,y);
	
	A.origin(){
		x=0;
		y=0;
	} // 匿名构造函数.

}

4.之类构造调用父类构造

之类的构造函数中必须调用父类的构造函数. 当我们没有显示调用父类的构造函数时, 系统默认调用父类的父类构造函数.

构造函数的执行顺序(和c++的顺序相同):
初始化列表->父类构造->子类构造;

当父类没有无参数的构造函数时,此时我们必须显示的调用父类的构造函数.

class A{
  
  num x,y;
  
  A(this.x,this.y);
}

class B extends A{
  num z;
  B(num x, num y, this.z):super(x,y);  // 调用父类构造
  B(num x, num y, num z): z=z, super(x,y) ; // 此构造方法等同与上一个, 
  //需要注意的是: 1. 初始化列表中属性初始化使用=,而父类构造使用(). 2.  父类的构函数必须放在初始化列表的最后
    B(num x, num y, num z):super(x,y) , z=z,; // 这样写将会报错
}

main(){
  var b = B(1,2,3);
  print(b.x);
  print(b.y);
  print(b.z);
}

5.构造函数传递
类中可以有一个普通构造函数和多个命名构造函数. 构造函数之间可以传递.

class Point {
  num x, y;

  // 普通构造函数
  Point(this.x, this.y);

  // 初始化列表中调用普通构造函数
  Point.alongXAxis(num x) : this(x, 0);
}

6.常量构造函数

  1. 常量构造函数中所有的成员变量都是final.
  2. 创建常量对象时需要加const修饰,否则创建的是非const对象.

7.工厂构造方法

工厂构造函数是一种构造函数,与普通构造函数不同,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象.

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) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值