Dart语言的诞生
2011年10月,Google 发布了一种新的编程语言 Dart,谷歌希望利用这款语言,帮助程序开发者克服JavaScript语言的缺点,并使Dart支持所有项目,从小型松散的项目到Gmail和谷歌文档这种大型复杂的项目。Dart 语言可以说是集百家之长,拥有其他优秀编程语言的诸多特性和影子,另外,最重要的是,Dart成为了Flutter的官方开发语言~
Dart的特性
编译模式:JIT和AOT
我们都知道,程序在运行之前通常都需要编译,JIT和AOT是最常用的两种编译方式
- JIT:即时编译,在开发时使用,可以动态下发和执行代码,开发测试效率高,但运行速度和性能会收到影响;
- AOT:预先编译(静态编译),可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低;
总结:JIT适合在开发测试时使用,AOT适合在发布时使用,而Dart是少数同时支持JIT和AOT的语言,Flutter非常受欢迎的功能热重载正是基于JIT,因此Dart 具有运行速度快、执行性能好的特点。
单线程模式
我们都知道,支持并发执行线程的高级语言(C++,Java等),都是以抢占式的方式切换线程,每个线程都会被分配一个固定的时间片来执行,这样当多线程操作更新共享资源时,就可能导致数据不同步的问题,虽然可以加锁来保证同步,但是锁本身会带来性能消耗,并且还可能出现死锁等比较严重的问题;这时候Dart单线程模式的优势就体现出来了,他不会存在资源竞争和数据同步的问题,一旦某个函数开始执行,直到这个函数执行完成,都不会被其他Dart代码打断;
基础语法和类型变量
变量和类型
在Dart中用var或具体类型来声明一个变量,用var声明表示类型是由编译器推断决定,或者你可以用具体类型来声明如:int x=0; 显式的告诉编译器你定义的变量类型;
默认情况下未初始化的变量值都是null,Dart是类型安全语言,所有类型都是对象类型(Object),一切变量的值都是类的实例;
Dart 内置了一些基本类型,如 num、bool、String、List 和 Map;
num:有两个子类int(整形)和double(浮点型)
int x = 10;
double y = 1.5;
bool类型,只有两个对象具有bool类型:true和false,他们都是编译时常量;
String由UTF-16的字符串组成,字符串变量既可以用单引号也可以用双引号表示,还可以在字符串中嵌入变量或表达式;可以用${express}的方式,将表达式放入字符串中,如果是一个标识符,可以省略{}
var text = 'monkey';
var text1 = 'this is :$text';
var text2 = 'this is :${text.toUpperCase()}';
List和Map
就是我们常见的集合或数组和字典类型,在Dart中统称为集合类型,我们用个例子来展示一下List和Map的使用
var arr1 = ["one", "two", "three"];
var arr2 = List.of([1, 2, 3]);
var map1 = {"name": "jiao", "sex": "male"};
var map2 = new Map();
map2['name'] = 'jiao';
map2['sex'] = 'male';
容器里的元素有类型,比如arr2的类型是List<int>,map2则为Map<String,String>,Dart会自动根据上下文来推断;
和Java类似,在初始化集合对象时,我们可以为集合添加类型约束(泛型)如:
var arr1 = <String>["one", "two", "three"];
var arr2 = List<int>.of([1, 2, 3]);
var map1 = <String, String>{"name": "jiao", "sex": "male"};
var map2 = new Map<String, String>();
这样是不是更清晰~
常量
Dart中常量的定义可以用const和final关键字来声明
const:表示在编译期就能确定的值
final:表示在运行时可以确定的值
两者区别:
const i = 1 (正确) const i =x+y(错误)
const必须直接赋一个字面量,而不能是一个变量或者表达式;
final i =1;(正确) final i =x+y(正确)
final则无以上限制,但是final的值一旦确定,就不能更改了;
函数
函数定义
Dart中所有的类型都是对象类型,函数也是对象,类型是function,这说明他可以定义为变量,甚至可以定义为参数传递给另一个函数;
bool isZero(int num) {
return num == 0;
}
void printInfo(int num, Function check) {
print('$num is Zero:${check(num)}');
}
Function f = isZero;
int x = 10;
int y = 0;
printInfo(x, f); // 输出 10 is Zero: false
printInfo(y, f); // 输出 0 is Zero: true
首先定义了一个判断是否为0的函数isZero,然后定义了一个打印判断结果的函数printInfo,可以看到第二个函数的入参为第一个函数;
如果函数体只有一行表达式,可以像JavaScript那样用箭头函数来简化这个函数
bool isZero(int num) => num == 0;
printInfo(int num, Function check) => print('$num is Zero:${check(num)}');
函数可变参数
我们知道在C++或者Java中,函数支持重载,即提供同名但参数不同的函数,但在Dart中并不支持重载,但Dart提供了可选命名参数和可选参数;
- {} 可选命名参数,以name:value 的方式指定参数;
- [] 可选参数 ,意味着这些参数可以忽略;
//{}设置可选命名参数
addValue({int x, int y}) {
print("x=$x,y=$y");
}
addValue(x: 10, y: 20);
addValue(x: 10); //y可选 默认null
addValue(y: 20); //x可选 默认null
//{}可选命名参数设置默认值
addValue1({int x, int y = 1}) {
print("x=$x,y=$y");
}
addValue1(x: 10); //y可选 已设默认值1
//[]可选参数
printValue(bool x, [bool y]) {
print("x=$x,y=$y");
}
printValue(true); //y可忽略 默认null
printValue(true, false);
//[]可选参数 默认值
printValue1(bool x, [bool y = true]) {
print("x=$x,y=$y");
}
printValue1(true); //y可忽略 已设默认值 true
通过上面的示例代码可以看出,既然都是可选参数,两者有什么区别呢?主要有两点区别:
1.{}访问是以 name:value的形式 []则不是
2.{}无需按参数顺序写参数 []则严格按照参数顺序写入参数
类
Dart是面向对象的语言,每个对象都是类的一个实例,都继承自顶层的Object,Dart中并没有public,protected,privite这些关键字,在声明变量和方法是加上""表示private,如果不加"",则默认是public的,这里要注意"_"的限制级别是库级别的,而不是类级别;
class Apple {
num x, y;
static num z = 0;
//语法糖,等同于在函数体内:this.x = x;this.y = y;
Apple(this.x, this.y);
void printXY() => print("x=$x,y=$y");
static void printZ() => print("z=$z");
}
在Apple类中,定义了两个成员变量x和y,通过构造函数语法糖进行初始化,而类变量z在声明时就已经赋好了默认值。
类的构造方法也可以使用可选命名参数和可选参数,另外Dart还支持命名构造函数 ,使类的实例化过程语义更清晰。
class Apple {
num x, y, z;
Apple(this.x, this.y) : z = 0;//初始化变量z
Apple.start(num x) : this(x, 0);//重定向构造函数 调用上面的构造方法
void printXYZ() => print("x=$x,y=$y,z=$z");
}
Apple类中有两个构造函数Apple和Apple.start,Apple.start将其成员变量初始化重定向到了Apple中,而Apple在初始化时为z赋上了默认值0
复用
在其他面向对象的语言中,我们要想复用其他类的变量和方法通常有两种方式继承和接口实现,在Dart中同样也可以:
继承: 继承自父类,子类由父类派生,会自动获得父类的方法和变量,子类也可以根据需要复写父类的构造函数或方法;
接口: 接口实现的话,子类主要获取到的是父类的方法或变量符号,需要自己实现父类的方法和变量,更多的是父类对子类进行约束;
下面,我们看一下在Dart中继承和接口的区别
class Apple {
num x, y;
void printValue() => print("x=$x,y=$y");
}
class Apple1 extends Apple {
num z;
@override//复写 printValue方法
void printValue() => print("x=$x,y=#y,z=$z");
}
class Apple2 implements Apple{
//成员变量需要重新声明
@override
num x;
@override
num y;
@override
void printValue() {
//成员函数需要重新实现
// TODO: implement printValue
}
}
可以看到,继承的话和其他语言都差不多,Apple2是可以采用接口的方式(implements)实现一个类的,然而得到的仅仅是父类的一个空壳子,从语义层面可以当作Apple来用,父类中所有的成员变量和方法都需要重新实现;那有没有什么机制,我们是以非继承的形式实现一个类,并且能复用父类的方法和变量而无需重新声明?答案是肯定的,在Dart中有一个概念Mixin,即混入关键字with修饰,可以实现上述功能,可以被看成是具有实现方法的接口,说白了就是和继承一样的功能,但又不是继承;可以理解为为了解决多继承而出现的一种新的类之间关系。
class Apple1 extends Apple {
num z;
@override //复写 printValue方法
void printValue() => print("x=$x,y=#y,z=$z");
}
class Apple2 with Apple {
num z;
@override //复写 printValue方法
void printValue() => print("x=$x,y=#y,z=$z");
}
看,是不是和继承一模一样的作用
运算符
Dart和其他大多数语言一样,运算符基本作用都是一致的,我们这里只特殊说明几个比较常用但又并是其他语言都有的运算符,类似kotlin中的非空判断
- ?. 如:apple?.printValue(); 这个比较好理解,当apple不为空时,执行printValue方法;
- ??= 如:a??=value,如果a为null 赋给a的值为value,否则跳过;
-
?? 如:我们在java中经常使用的 a!=null?a:b;如果a不等于null返回a的值,否则返回b,在Dart中我们可以直接使用:a??b;
在Dart中一切皆对象,包括运算符也是对象成员函数的一部分
Dart提供了类似C++的运算符复写机制,我们可以复写或自定义运算符;
class Apple {
num x;
Apple(this.x);
num operator +(Apple a) => this.x + a.x * 5;
}
Apple a1 = new Apple(10);
Apple a2 = new Apple(20);
print("a1+a2=${a1 + a2}");
输出结果:a1+a2=110
我们来分析一下:Apple类重写了运算符+,当Apple类进行+号运算时,如上例:a1+a2,首先取a1的x值10+a2的x值20乘以5 返回,所以最后得出的结果是110
总结
最后我们来总结一下以上内容
- Dart语言的诞生,并了解了Dart语言的一些特性,编译模式,单线程模式等;
- Dart语言的基础语法以及变量类型,List和Map的使用等;
- 函数的定义,函数的可变参数,可选命名参数和可选参数以及两者的区别;
- Dart中的类,类的复用的方式,及“混入”概念的理解;
- Dart中的运算符,主要是非空判断的一些运算符使用,以及如何复写运算符;