前言
当下是在移动互联网的时代,跨平台是一个很热门的话题,而且也往后也一定会成为一个趋势。做一个应用,采用原生开发,需要Android和ios工程师,这无疑增加了开发成本和维护成本。因次,hybird、react nitive、weex等跨平台解决方案如雨后春笋般兴起。但这些跨平台技术,对于很多开发者来说,爬坑的过程都是苦不堪言。直到Flutter的出现,像是给开发者打开了一扇新世界的大门。
5G的时代已经来临,Google也即将推出fuchsia物联网系统,而fuchsia的UI就是用flutter构建的,Dart 更是被指定为官方的开发语言。我们可以看到,google在大力推广flutter,可以说Google在用flutter下一盘很大的棋。而flutter采用的开发语言就是Dart。所以,要用好flutter框架,一定要先搞清楚Dart这门语言。其实,不管从哪个角度来开,作为一个前端工程师,于情于理都应该来学习了解Dart。
Dart的前世今生
Dart是2011年10月,在丹麦召开的 GOTO 大会上,Google 发布了这种新的编程语言。Dart 的诞生正是要解决 JavaScript 存在的、在语言本质上无法改进的缺陷。Google为了推广Dart, 甚至将自己的 Chrome 浏览器内置了 Dart VM,可以直接高效地运行 Dart 代码。可以说,当时Google是想让Dart取代js。但,js因为nodejs的出现,又火爆了一波。而Dart却一直不温不火,后来Google也将Dart VM引擎从Chrome中移除。Dart开始转型,在Google内部孵化出了flutter框架,进入的移动领域。Dart最终能否成功,还是得看flutter能否成功。Dart同时具有JIT(提前编译)和AOT(即时编译)两种类型。Flutter在开发阶段采用,采用JIT模式,这样就避免了每次改动都要进行编译,极大的节省了开发时间;Flutter在发布时可以通过AOT生成高效的ARM代码以保证应用性能。
Dart入口函数
main () {
print('hello world'); // 在dart里,print是打印信息的方法
}
void main () {
print('hello world');
}
main函数是dart的入口函数,代码允许时,会找到入口函数执行。所有的函数前面都可以加void,表示这个函数没有返回值。
注意:Dart语言结束必须加分号。
变量
Dart是一个强大的脚本类型语言,可以不预先定义变量类型,自动回类型检测。 Dart中定义变量可以通过var关键字,也可以通过类型来申明变量。
var str = '123'; // String类型
String str = '123';
注意:使用var 申明后就不要写类型,写了类型不要写var,两者都写,比如 var a int = 5会报错。
变量的命名规则
1.变量名由字母、数字、下划线、$组成,但不能以数字开头。
2.不能是保留字和关键字。
3.变量名区分大小写。
常量
const
1.只有静态成员才能用const修饰。
2.const是编译时常量,在编译时就要确定下来。
const a = 1;
a = 2; // 这种写法是错误的,不能再被修改
final
1.final是运行时常量。
2.final是惰性初始化,即在运行时第一次使用前才初始化。
final a = 1;
a = 2; // 这种写法是错误的,不能再被修改
final和const的区别
Final 表明这个变量不能再发生更改,但是这个初始化的值在编译时是不确定的, 只有在运行时,才能确定其值。一旦初始化,则不允许再次发生更改。而const是一个明确的值,编译时就要确定下来。
final或者const修饰的变量,变量类型可以省略。
<!--代码演示最直观-->
final time = new DateTime.now(); // 可以正常运行
const time2 = new DateTime.now(); // 这句代码会报错
Dart数据类型
Dart是强类型语言,但其也有类型推断等弱类型语言的特性。
字符串
定义字符串的方式
var str1 = 'hello world';
String str2 = '你好';
字符串拼接
String str1 = 'hello';
String str2 = 'world';
<!--方式一,采用加号-->
print(str1 + str2); // helloworld
print(str1 + ' ' + str2); // hello world
<!--方式二,采用$符号-->
print('$str1 $str2'); // hello world
数字
int类型
int类型必须是整形
int a = 123;
int a = 123.4; // 报错
double
double可以是整形,也可以是浮点型
double b = 2;
double c = 3.14;
运算符
和js差不多的运算符,就不多介绍了。
算术运算符
+、-、*、/、%(取余)、~/(取整)
关系运算符
==、!=、>、<、>=、<=
**注意:**没有===、!==
逻辑运算符
!、&&、||
自增自减
++、--
var a = 1;
var b = a++;
print(a); // 2
print(b); // 1
前置加加:先自增再参与运算。
后置加加:先运算再自增。
减减和加加的规则是一样的。
赋值运算符
1、基础赋值运算符 =、??=
b ??= 123; // 表示如果b为空的话,把值赋给b
bool
定义布尔值,只能是true或false
bool flag = true;
bool isShow = false;
条件判断
if else
bool flag = true;
if (flag) {
print('真');
} else {
print('假')
}
/*
注意:Dart没有隐式类型转换,在if判断的时候,必须得到具体的bool值
*/
<!--举例-->
var flag;
if (flag) { // 这种写法在js里非常常见,但在Dart里是不允许的
print('真');
}
// 正确写法
if (flag != null) { // 必须要显式地进行判断
print('真');
}
switch case
用法和js一样
三目运算
用法和js一样
??
var a;
var b = a ?? 10; // 表示如果a为空,把10赋给b,否则把a赋值给b
List
List就是 数组或者叫集合
定义list的方式
<!--方式一-->
var list1 = ['a', 'b', 'c'];
<!--方式二-->
var list2 = new List();
list2.add(1);
list2.add(2);
list2.add(3);
<!--方式三-->
List list3 = ['a', 'b', 'c']; // 这种方式和用var相似
<!--定义指定类型的List-->
var list4 = new List<String>(); // 这种方式表示数组里只能放置字符串类型的元素
Maps(字典)
定义Map的方式
<!--方式一-->
var person = {
"name": "张三", // 这里的key,必须用引号引起来
"age": 18
}
<!--方式二-->
Map person = {
"name": "张三", // 这里的key,必须用引号引起来
"age": 18
}
<!--方式三-->
var person2 = new Map();
person['name'] = '李四'
<!--访问元素-->
print(person['name']); // 张三
/*
注意:这里不能用person.name的方式来访问元素
*/
类型判断
用is来判断类型, 得到的结果是bool值
var test = '123';
if (test is String) {
print('string类型')
} else if (test is int) {
print('int类型')
} else {
print('其他类型')
}
类型转换
Number和String类型之间的转换
1.Number转String,调用toString()
2.String转Number,调用int.parse() 或double.parse()
<!--字符串转Number-->
String str = '123';
var myNum = int.parse(str); // 可以转换
var myNum2 = double.parse(str); // 可以转换
String str2 = '12.3';
var myNum3 = int.parse(str2); // 报错,浮点型只能用double
String str3 = '’;
var myNum4 = double.parse(str3); // 报错
字符串类型的数字转Number的时候,最好使用double.parse,容错处理可以加上try catch
String price = '';
try {
var myPrice = double.parse(price);
} catch(err) {
print(0);
}
<!--Number转String-->
var price = 123;
var str = price.toString();
其他类型转换成bool类型
1.isEmpty判断是否为空
var str = '';
if (str.isEmpty ) {
print('为空');
}
2.isNaN
var myNum = 0 / 0;
if (myNum.isNaN) {
print('NaN');
}
循环语句
for、while、do while,和js用法一样。
List常用属性和方法
常用属性
List list = [1, 2, 3];
list.length; // 数组长度
list.reversed; // 翻转
list.isEmpty; // 是否为空
list.isNotEmpty; // 是否不为空
常用方法
List list = ['a', 'b', 'c'];
list.add(4); // 增加 ['a', 'b', 'c', 4]
list.addAll([5, 6]); // 拼接数组 ['a', 'b', 'c', 4 , 5, 6]
list.indexOf(2); // 返回元素所在的索引值,元素不存在则返回-1
list.remove('a'); // 删除具体的元素
list.removeAt(1);// 删除,传入索引
list.fillRange(1, 2, 'bb'); // ['a', 'bb', 'c'],修改数组,fillRange(开始的索引,结束的索引,修改后的值)
/*
insert(index, value) 指定位置插入元素
insertAll(index, list) 指定位置插入List
join和split,用法和js一样
toList() // 其他类型转换为数组类型,接下来讲Set时演示
forEach、map、where、any、every这几个方面后面统一讲解
*/
Set
主要的功能是去除数组重复的内容。Set是没有顺序且不能重复的集合,所以不能通过索引去获取值。
var test = new Set();
test.add('a');
test.add('b');
test.add('b');
print(test); // {'a', 'b'} 得到的是这种类型的,需要调用toList()转换成数组
print(test.toList()); // ['a', 'b']
数组去重
List list = ['苹果', '香蕉', '猕猴桃', '苹果', '香蕉'];
var test = new Set();
test.addAll(test);
test.toList(); // ['苹果', '香蕉', '猕猴桃']
Maps
Maps是无序的键值对
常用属性
Map person = {
"name": "张三",
"age": 18
}
print(person.keys); // 得到的是('name', 'age')
print(person.keys.toList()); // 得到的是['name', 'age']
print(person.value.toList()); // ['张三', 18]
person.isEmpty; // 是否为空
person.isNotEmpty; // 是否不为空
####常用方法
Map person = {
"name": "张三",
"age": 18
}
person['sex'] = '男';
person.addAll({ // 增加
"work": ['前端', 'java'],
"hobby": '外卖骑手'
})
person.remove('name'); // 删除指定key的数据
person.containsValue('张三'); // 查看有没有这个值,返回true或false
循环数据的方法
在List、Set、Map是通用的
List list = ['a', 'b', 'c'];
for (var i = 0; i < list.length; i++) {
print(list[i]);
}
for (var item in list) {
print(item);
}
list.forEach((value) { // 注意:forEach没有返回值,list调用forEach,这里的参数只有一个value,和js不同
print(value);// 'a' 'b' 'c'
})
List numList = [1, 2, 3];
var newList = numList.map((value){
return value * 2;
})
print(newList); // (2,4,6)得到的不是真正的数组
print(newList.toList()); // [2,4,6]
var whereList = numList.where((value) {
return value > 2;
})
print(whereList.toList()); // [3]
var result = numList.any((value) { // 只要集合里面有满足条件的就返回true
return value > 2;
})
print(result); // true
var result1 = numList.every((value) { // 集合里面所以都满足条件的才返回true
return1 value > 3;
})
print(result1); // false
<!--循环Set-->
var test = new Set();
test.addAll([1,2,3])
test.forEach((value) {
print(vale); // 1 2 3
})
Map person = {
"name": "张三",
"age": 18
}
person.forEach((key, value){ // Map调用forEach有两个参数
print('$key $value');
函数
定义函数
getNum() {
var myNum = 123;
return myNum;
}
int getNum() { // 指定函数的返回值必须是int类型
var myNum = 123;
return myNum;
}
var currentNum = 1;
void changeNum() { // void表示函数没有返回值
currentNum = 2;
}
函数作用域
Dart是静态作用域语言,变量的作用域在写代码的时候就确定了。基本上大括号里面定义的变量就只能在大括号里面访问。
var a = 1;
void main() {
var b = 2;
foo() {
var c = 3;
bar() {
var d = 4;
print(a);
print(b);
print(c);
print(d);
}
}
}
只有bar函数可以访问所有的变量。
传参
约束参数类型
String getUerInfo(String name, int age){
return '姓名:$name, 年龄:$age';
}
getUerInfo('张三', 18);
可选参数及参数默认值
参数用[]括起来,表示参数是可选类型的,可以传,也可以不传
String getUerInfo(String name, [String sex = '男', int age]){ // sex如果不传默认值为男
if (age != null) {
return '姓名:$name, 性别:$sex, 年龄:$age';
}
return '姓名:$name, 性别:$sex, 年龄:保密';
}
getUerInfo('张三');
命名参数
String getUerInfo(String name, {String sex = '男', int age}){ // sex可以传,可以不传,如果不传默认值为男
if (age != null) {
return '姓名:$name, 性别:$sex, 年龄:$age';
}
return '姓名:$name, 性别:$sex, 年龄:保密';
}
getUerInfo('张三', sex: '女', age: 18);
函数作为参数
foo() {
print('foo');
}
fn1(fn) {
fn();
}
fn1(foo);
箭头函数
dart里的箭头表达式与js不一样,它只能包含一个表达式,也就是只能写一句代码
List<int> list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
<!--这种写法,在dart里是不支持的-->
list.forEach((num) => {
if (num % 2 == 0) {
print(num);
}
});
<!--支持的写法-->
list.forEach((num) => print(num));
list.forEach((num) => {
print(num) // 注意:这种写法,这一句不能加分号
});
自执行函数
((int a) {
print(a);
})(1);
闭包
闭包的概念和js类似
会常驻内存,不会污染全局
// 函数嵌套,跨作用域访问变量就会形成闭包
fn() {
var a = 123;
return () {
a++;
print(a);
}
}
类
面向对象的三大特征:封装、继承、多态。
dart一切都是对象,所有的对象都继承自Object类。
class Person {
String name = '张三';
int age = 18;
void getInfo() {
print('$name, $age'); // 可以直接用这种方式访问
print('${this.name}, ${this.age}'); // 也可以用this这种方式访问,推荐写法
}
}
void main () {
// 实例化
var p1 = new Person();
print(p1.name);
p1.getInfo();
Person p2 = new Person();
p2.getInfo();
}
构造函数
默认构造函数
class Person {
String name = '张三';
int age = 18;
Person() { // 默认构造函数
print('这个方法会在实例化的时候触发');
}
void getInfo() {
print('${this.name}, ${this.age}');
}
}
void main () {
// 实例化
Person p1 = new Person();
p1.getInfo();
}
<!--优化-->
class Person {
String name;
int age;
Person(this.name, this.age); // 默认构造函数
void getInfo() {
print('${this.name}, ${this.age}');
}
}
void main () {
// 实例化
Person p1 = new Person('张三', 18);
p1.getInfo();
}
命名构造函数
class Person {
String name;
int age;
Person(this.name, this.age); // 默认构造函数
Person.eat() { //命名构造函数
print('命名构造函数eat');
}
Person.playGame() { //命名构造函数
print('命名构造函数playGame');
}
void getInfo() {
print('${this.name}, ${this.age}');
}
}
void main () {
// 实例化
Person p1 = new Person('张三', 18); // 默认实例化类的时候,调用的是默认构造函数
Person p2 = new Person.eat(); // 调用的是命名构造函数
}
**注意:**认构造函数只能有一个,命名构造函数可以有多个。
私有属性和方法
在属性或方法前面加一个_,就可以定义私有的属性或方法。需要注意的是,私有属性和方法必须单独抽离到一个文件,否则加上下划线也不会生效。
class Person {
String _name; // 私有属性
int age;
Person(this._name, this.age); // 默认构造函数
void getInfo() {
print('${this._name}, ${this.age}');
}
String getName() {
return this._name;
}
void _run() { // 私有方法
print('私有方法');
}
goRun() {
this._run(); // 类里面的方法可以相互调用
}
}
get和set
class Person {
String name; // 私有属性
int age;
Person(this.name, this.age); // 默认构造函数
get getName {
return this.name;
}
set setAge(value) {
this.age = value;
}
}
void main () {
Person p1 = new Person('张三', 18);
print(p1.getName);
p1.setAge = 22;
}
静态成员
1.使用static 关键字来实现类级别的变量和函数。
2.静态方法不能访问非静态成员,非静态方法可以访问静态成员。
3.静态属性和方法不能通过实例对象来访问。
4.类里面访问静态属性和方法,不需要通过this。
class Person {
static String name = '张三'; // 私有属性
int age = 18;
static void getInfo() {
print(name); // 可以访问
print(this.age); // 报错
}
void printInfo() {
print(name); // 访问静态属性
print(this.age); // 访问非静态属性
}
}
void main () {
Person p1 = new Person();
print(p1.name); // 这样会报错
print(Person.name); // 正确
Person.getInfo();
}
对象操作符
- ? 条件运算符
- as 类型转换
- is 类型判断
- .. 级联操作
class Person {
String name;
int age;
Person(this.name, this.age);
void getInfo() {
print('${this.name}, ${this.age}');
}
}
void main () {
Person p1;
p1?.getInfo(); // p1非空才会调用方法
Person p2 = new Person('张三', 18);
if (p2 is Person) {
p2.name = '李四'
}
var p3 = '';
p3 = new Person('张三', 18);
(p3 as Person).getInfo(); // 强制转为person对象
Person p4 = new Person('张三', 18);
p4..name = '李四' // p4.name = '李四';
..age = 22 // p4.age = 22;
..getInfo(); // p4.getInfo();
}
继承
- 子类使用extends继承父类
- 子类会继承父类可见的属性和方法,但不会继承构造函数
- 子类可以覆写父类的方法
class Person {
String name;
int age;
Person(this.name, thi.age);
void printInfo((;) {
print('${this.name}, ${this.age}');
}
show() {
print('show');
}
eat() {
print('eat');
}
}
class Son extends Person {
String sex;
Son(String name, int age, String sex) : super(name, age) {
this.sex = sex;
} // super表示在执行子类构造函数之前,把参数赋值给父类
run () {
print('${this.name}, ${this.age}, ${this.sex}');
// 子类调用父类的方法,可以用super,也可以用this
super.show();
this.eat();
}
// 覆写父类的方法
// @override注解表示覆写父类的方法,可以不写,但一般建议写上
@override
void printInfo() {
print('姓名:${this.name}, 年龄:${this.age}');
}
}
void main() {
Son s1 = new Son('张三', 18, '男');
s1.printInfo();
}
抽象类
抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
- 抽象类用abstract关键字来定义
- Dart中的抽象方法不能用abstract声明,没有方法体的方法称为抽象方法
- 子类继续抽象类,必须要实现里面的抽象方法
- 如果把抽象类当作接口实现的话,必须要实现抽象类里面定义的所有属性和方法
- 抽象类不能被实例化,只有继承抽象类的子类可以实例化。
abstract class Animal{
eat(); // 抽象方法
run(); // 抽象方法
printInfo() {
print('info'); // 普通方法,所有的子类都会有这个方法
}
}
class Cat extands Animal {
@override
eat() { // 子类必须实现父类所有的抽象方法
print('eat');
}
@override
run() { // 子类必须实现父类所有的抽象方法
print('run');
}
}
void main () {
Cat c1 = new Cat();
c1.eat();
c1.printInfo();
}
多态
多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类都要不同的表现。
- 允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果
- 子类的实例赋值给父类的引用
abstract class Animal{
eat(); // 抽象方法
}
class Cat extands Animal {
@override
eat() {
print('cat吃鱼');
}
}
class Dog extands Animal {
@override
eat() {
print('Dog吃肉');
}
}
void main () {
Cat c1 = new Cat();
Dog d1 = new Dog();
c1.eat();
d1.eat();
}
子类的实例赋值给父类的引用
子类的实例赋值给父类的引用,子类只能调用父类提供的标准。
abstract class Animal{
eat(); // 抽象方法
}
class Dog extands Animal {
@override
eat() {
print('Dog吃肉');
}
run() {
print('run');
}
}
void main () {
Dog d1 = new Dog();
d1.eat(); // 可以调用eat方法
d1.run();// 可以调用run方法
Animal d2 = new Dog();
d2.eat(); // 只能调用eat方法,不能调用子类的run
}
接口
dart普通类或抽象类,都可以作为接口被实现,同样适用implements关键字进行实现。但如果实现的类是普通类或抽象,会将普通类或抽象类的属性、方法全部覆写一遍。因为抽象类可以定义抽象方法,普通类不可以,所以一般建议用抽象类定义接口。
接口其实就是约定、规范。
abstract class Db { // 当作接口
String name;
add();
delete();
save();
}
class Mysql implements Db {
@override
String name;
Mysql(this.name);
@override
add() {
print('add');
}
@override
delete() {
print('delete');
}
@override
save() {
print('save');
}
}
extends抽象类和implements的区别
什么时候用抽象类,什么时候用接口
- 如果要复用抽象类里面的方法,并且要用抽象方法约束子类的话,就用extends继承抽象类
- 如果只是把抽象类当作标准的话,用implements实现抽象类
一个类实现多个接口
abstract class Person {
String name;
printInfo();
}
abstract class Father {
show();
}
class Son implements Person, Father {
@override
String name;
@override
printInfo() {
print('person');
};
@override
show() {
print('show');
}
}
void main () {
Son s = new Son();
s.printInfo();
}
mixins
dart本身是不能用extends实现多继承的,而使用with关键字可以实现类似多继承的功能,这就是mixins。
- 作为mixins的类,只能继承自Object,不能继承其他类
- 作为mixins的类,不能有构造函数
- 一个类可以mixins多个mixins类
- mixins不是继承,也不是接口,是一个新的特性
- mixins的实例类型就是其超类的子类型
class A {
// A不能再继承自其它类
/*
例如:
class A extends D {
}
这样A是不能mixins的
*/
// A不能有构造函数
printA() {
print('a');
}
}
class B {
// B不能再继承自其它类
// B不能有构造函数
printB() {
print('b');
}
}
class Person {
String name;
int age;
Person(this.name, this.age)
}
class C with A,B{
// A和B就是C的超类
}
<!--继承并mixins-->
class E extends Person with A,B {
E(String name, int age) : super(name, age);
}
void main () {
C c = new C();
c.printA();
c.printB();
}
/*
注意:如果A和B有相同的方法,那么with的时候, 后面的会把前面的相同方法覆盖,比如
class A {
run() {
print('A');
}
}
class B {
run() {
print('B');
}
}
class C with B, A {
}
C在调用run的时候就是执行A里面的run方法
*/
泛型
泛型就是解决类、接口、方法的复用性,以及对不特定数据类型的支持(类型校验)。
不指定类型,放弃了类型检查,传入什么返回什么
T getData<T>(T value) {
return value;
}
void main () {
print(getData(111)); // 111 没有类型校验
print(getData('abc')); // 'abc' 没有类型校验
getData<int>(12); // 有类型校验,这样只能传入int类型
}
泛型类
class P<T> {
List list = new List<T>();
void add(T value) {
this.list.add(value);
}
void printInfo() {
for (var i = 0; i < this.list.length; i++) {
print(this.list[i]);
}
}
}
void main () {
P p1 = new P<int>();
p.add(1);
p.add(2);
p.printInfo;
}
泛型接口
abstract class Cache<T>{
getData(String key);
void setData(String key, T value);
}
class FileCache<T> implements Cache<T> {
@override
getData(String key) {
print('getData');
}
@override
setData(String key, T value) {
print('value: $value');
}
}
class MemoryCache<T> implements Cache<T> {
@override
getData(String key) {
print('getData');
}
@override
setData(String key, T value) {
print('value: $value');
}
}
void main () {
MemoryCache m = new MemoryCache<String>();
m.setData('name', '张三');
}
总结
Dart其实和java、js都是非常相似,这边文章对Dart以及Dart的基本用法做了大概的讲解,总的来说是比较基础的知识点,万丈高楼平地起,基础扎实,后面的空间也会更大。最近在做flutter项目,flutter的相关知识点,爬坑完之后再来做一个整理。