Dart基础知识点

178 阅读5分钟

语言特点

  • 在Dart中,一切都是对象,一切对象都是class的实例,哪怕是数字类型、方法甚至null都是对象,所有的对象都是继承自Object
  • 虽然Dart是强类型语言,但变量类型是可选的因为Dart可以自动推断变量类型
  • Dart支持范型,List表示一个整型的数据列表,List则是一个对象的列表,其中可以装任意对象
  • Dart支持顶层方法(如main方法),也支持类方法或对象方法,同时你也可以在方法内部创建方法
  • Dart支持顶层变量,也支持类变量或对象变量
  • 跟Java不同的是,Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的,具体可以看这里
  • Dart中变量可以以字母或下划线开头,后面跟着任意组合的字符或数字 变量

变量定义

以下代码是Dart中定义变量的方法:

main() {
    var a = 1;
    int b = 10;
    String s = "hello";
    dynamic c = 0.5;
}

你可以明确指定某个变量的类型,如int bool String,也可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型。 final和const的区别:

区别一:final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而 const 要求在声明时初始化,并且赋值必需为编译时常量。

区别二:final 是惰性初始化,即在运行时第一次使用前才初始化。而 const 是在编译时就确定值了。 内建数据类型:

  • numbers
  • strings
  • booleans
  • lists(或者是arrays)
  • maps(或者是dictionary)
  • runes(UTF-32字符集的字符)
  • symbols
main() {
   // numbers
   var a = 0;
   int b = 1;
   double c = 0.1;
   // strings
   var s1 = 'hello';
   String s2 = "world";
   // booleans
   var real = true;
   bool isReal = false;
   // lists
   var arr = [1, 2, 3, 4, 5];
   List<String> arr2 = ['hello', 'world', "123", "456"];
   List<dynamic> arr3 = [1, true, 'haha', 1.0];
   // maps
   var map = new Map();
   map['name'] = 'zhangsan';
   map['age'] = 10;
   Map m = new Map();
   m['a'] = 'a';
   // runes,Dart 中 使用runes 来获取UTF-32字符集的字符。String的 codeUnitAt and codeUnit属性可以获取UTF-16字符集的字符
   var clapping = '\u{1f44f}';
   print(clapping); // 打印的是拍手emoji的表情
   // symbols
   print(#s == new Symbol("s")); // true
}

函数

函数的返回值

Dart是一个面向对象的编程语言,所以即使是函数也是一个对象,也有一种类型Function,这就意味着函数可以赋值给某个变量或者作为参数传给另外的函数。虽然Dart推荐你给函数加上返回值,但是不加返回值的函数同样可以正常工作,另外你还可以用=>代替return语句,比如下面的代码:

// 声明返回值int 
add(int a, int b) {
 return a + b;
}

// 不声明返回值
add2(int a, int b) {
 return a + b;
}

// =>是return语句的简写
add3(a, b) => a + b;

main() {
 print(add(1, 2)); // 3
 print(add2(2, 3)); // 5
 print(add3(1, 2)); // 3
}

可选参数(分为命名参数、位置参数两种)参数默认值

命名参数
sayHello({String name}) {
 print("hello, my name is $name");
}

sayHello2({name: String}) {
 print("hello, my name is $name");
}

main() {
 // 打印 hello, my name is zhangsan
 sayHello(name: 'zhangsan');

 // 打印 hello, my name is wangwu
 sayHello2(name: 'wangwu');
}

可以看到,定义命名参数时,你可以以 {type paramName} 或者 {paramName: type} 两种方式声明参数,而调用命名参数时,需要以 funcName(paramName: paramValue) 的形式调用。

命名参数的参数并不是必须的,所以上面的代码中,如果调用sayHello()不带任何参数,也是可以的,只不过最后打印出来的结果是:hello, my name is null,在Flutter开发中,你可以使用@required注解来标识一个命名参数,这代表该参数是必须的,你不传则会报错,比如下面的代码: const Scrollbar({Key key, @required Widget child})

位置参数

使用中括号[]括起来的参数是函数的位置参数,代表该参数可传可不传,位置参数只能放在函数的参数列表的最后面,如下代码所示:

sayHello(String name, int age, [String hobby]) { 
// 位置参数可以有多个,比如[String a, int b]
   StringBuffer sb = new StringBuffer();
   sb.write("hello, this is $name and I am $age years old");
 if (hobby != null) {
   sb.write(", my hobby is $hobby");
 }
 print(sb.toString());
}

main() {
// hello, this is zhangsan and I am 20 years old
 sayHello("zhangsan", 20);
// hello, this is zhangsan and I am 20 years old, my hobby is play football
 sayHello("zhangsan", 20, "play football");
}

注: 可选位置参数是位置,如果想指定某个位置上的参数值,则必须前面位置的已经有值,即使前面的值存在默认值。这里特意使用两个不同类型的可选参数作为示例,如果前后可选参数为相同类型,则会出现异常结果,并且只有在发生后才会注意到。所以这一点要特别注意。比如一下示例,假如本意是想赋值给age,但结果将会差强人意

void _buildHouse(int num, [String where, int range]) {}
void _buildHouseAndDefaultValue(int num, [String where = 'Shanghai', int range]) {}
   
_buildHouse(10,10); //不可行的
   
_buildHouse(10,'shenzhen',10); //可行的
   
_buildHouseAndDefaultValue(10,10); //不可行的
   
_buildHouseAndDefaultValue(10,'shenzhen',10); //可行的

运算符

main() {
 // 与Java相同的运算符操作
 int a = 1;
 ++a;
 a++;
 var b = 1;
 print(a == b);  // false
 real ? print('real') : print('not real'); // not real
 print(real && a == b); // false
 print(real || a == 3); // true
 print(a != 2); // true
 print(a <= b); // false
 var c = 9;
 c += 10;
 print("c = $c"); // c = 19
 print(1<<2); // 4
 // 与Java不太一样的运算符操作

 // is运算符用于判断一个变量是不是某个类型的数据
 // is!则是判断变量不是某个类型的数据
 var s = "hello";
 print(s is String); // true
 var num = 6;
 print(num is! String); // true
 // ~/才是取整运算符,如果使用/则是除法运算,不取整
 int k = 1;
 int j = 2;
 print(k / j); // 0.5
 print(k ~/ j); // 0
  // as运算符类似于Java中的cast操作,将一个对象强制类型转换
 (emp as Person).teach();

 // ??=运算符 如果 ??= 运算符前面的变量为null,则赋值,否则不赋值
 var param1 = "hello", param2 = null;
  param1 ??= "world";
 param2 ??= "world";
 print("param1 = $param1"); // param1 = hello
 print("param2 = $param2"); // param2 = world
 
 // ?.运算符 校验非空避免崩溃
 var str1 = "hello world";
 var str2 = null;
 print(str1?.length); // 11
 print(str2?.length); // null
 print(str2.length); // 报错
..运算符(级联操作)

class Person {
 eat() {
   print("I am eating");
 }

 sleep() {
   print("I am sleeping");
 }

 study() {
   print("I am studying");
 }
}

main() {
 // 依次打印
 //  I am eating
 //  I am sleeping
 //  I am studying
 new Person()..eat()..sleep()..study();
}

类(Class)

类的定义与构造方法

class Person {
 String name;
 int age;
 String gender;
 Person(this.name, this.age, this.gender);
sayHello() {
   print("hello, this is $name, I am $age years old, I am a $gender");
 }
}

上面的Person类中有3个成员变量,一个构造方法和一个成员方法,看起来比较奇怪的是Person的构造方法,里面传入的3个参数都是this.xxx,而且没有大括号{}包裹的方法体,这种语法是Dart比较独特而简洁的构造方法声明方式,它等同于下面的代码:

Person(String name, int age, String gender) {
   this.name = name;
   this.age = age;
   this.gender = gender;
}

要调用Person类的成员变量或成员方法,可以用下面的代码:

var p = new Person("zhangsan", 20, "male");
p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male
p.age = 50;
p.gender = "female";
p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female

类除了有跟类名相同的构造方法外,还可以添加命名的构造方法,如下代码所示:

class Point {
 num x, y;
 Point(this.x, this.y);
 // 类的命名构造方法
 Point.origin() {
   x = 0;
   y = 0;
 }
}

main() {
 // 调用Point类的命名构造方法origin()
 var p = new Point.origin();
 var p2 = new Point(1, 2);
}

Dart中使用extends关键字做类的继承,如果一个类只有命名的构造方法,在继承时需要注意,如下代码:

class Human {
 String name;
 Human.fromJson(Map data) {
   print("Human's fromJson constructor");
 }
}

class Man extends Human {
 Man.fromJson(Map data) : super.fromJson(data) {
   print("Man's fromJson constructor");
 }
}

有时候你仅仅只是在某个类的构造方法中,调用这个类的另一个构造方法,你可以这么写:

class Point {
 num x, y;
 Point(this.x, this.y);
 // 命名构造方法调用了默认的构造方法
 Point.alongXAxis(num x) : this(x, 0);
}

类的成员方法

一个类的成员方法是一个函数,为这个类提供某些行为。上面的代码中已经有了一些类的成员方法的定义,这些定义方式跟Java很类似,你可以为某个类的成员变量提供getter/setter方法,如下代码:

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

 // 构造方法传入left, top, width, height几个参数
 Rectangle(this.left, this.top, this.width, this.height);

 // right, bottom两个成员变量提供getter/setter方法
 num get right => left + width;
 set right(num value) => left = value - width;
 num get bottom => top + height;
 set bottom(num value) => top = value - height;
}

抽象类和抽象方法

使用abstract修饰一个类,则这个类是抽象类,抽象类中可以有抽象方法和非抽象方法,抽象方法没有方法体,需要子类去实现,如下代码:

abstract class Doer {
 // 抽象方法,没有方法体,需要子类去实现
 void doSomething();
 // 普通的方法
 void greet() {
   print("hello world!");
 }
}

class EffectiveDoer extends Doer {
 // 实现了父类的抽象方法
 void doSomething() {
   print("I'm doing something...");
 }
}

运算符重载

Dart中有类似于C++中的运算符重载语法,比如下面的代码定义了一个向量类,重载了向量的+ -运算:

class Vector {
 num x, y;
 Vector(this.x, this.y);
 Vector operator +(Vector v) => new Vector(x + v.x, y + v.y);
 Vector operator -(Vector v) => new Vector(x - v.x, y - v.y);
 printVec() {
   print("x: $x, y: $y");
 }
}

main() {
 Vector v1 = new Vector(1, 2);
 Vector v2 = new Vector(3, 4);
 (v1 - v2).printVec(); // -2, -2
 (v1 + v2).printVec(); // 4, 6
}

枚举类

使用enum关键字定义一个枚举类,这个语法跟Java类似,如下代码: enum Color { red, green, blue }

异步

Dart提供了类似ES7中的async await等异步操作,这种异步操作在Flutter开发中会经常遇到,比如网络或其他IO操作,文件选择等都需要用到异步的知识。 asyncawait往往是成对出现的,如果一个方法中有耗时的操作,你需要将这个方法设置成async,并给其中的耗时操作加上await关键字,如果这个方法有返回值,你需要将返回值塞到Future中并返回,如下代码所示:

Future checkVersion() async {
 var version = await lookUpVersion();
 // Do something with version
}
下面的代码使用Dart从网络获取数据并打印出来:
import 'dart:async';
import 'package:http/http.dart' as http;

Future<String> getNetData() async{
 http.Response res = await http.get("http://www.baidu.com");
 return res.body;
}

main() {
 getNetData().then((str) {
   print(str);
 });
}