flutter之Dart基础

369 阅读12分钟

前言

当下是在移动互联网的时代,跨平台是一个很热门的话题,而且也往后也一定会成为一个趋势。做一个应用,采用原生开发,需要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('张三'); // 查看有没有这个值,返回truefalse

循环数据的方法

在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();
}

对象操作符

  1. ? 条件运算符
  2. as 类型转换
  3. is 类型判断
  4. .. 级联操作
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();
}

继承

  1. 子类使用extends继承父类
  2. 子类会继承父类可见的属性和方法,但不会继承构造函数
  3. 子类可以覆写父类的方法
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();
}

抽象类

抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。

  1. 抽象类用abstract关键字来定义
  2. Dart中的抽象方法不能用abstract声明,没有方法体的方法称为抽象方法
  3. 子类继续抽象类,必须要实现里面的抽象方法
  4. 如果把抽象类当作接口实现的话,必须要实现抽象类里面定义的所有属性和方法
  5. 抽象类不能被实例化,只有继承抽象类的子类可以实例化。
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();
}

多态

多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类都要不同的表现。

  1. 允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果
  2. 子类的实例赋值给父类的引用
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的区别

什么时候用抽象类,什么时候用接口

  1. 如果要复用抽象类里面的方法,并且要用抽象方法约束子类的话,就用extends继承抽象类
  2. 如果只是把抽象类当作标准的话,用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。

  1. 作为mixins的类,只能继承自Object,不能继承其他类
  2. 作为mixins的类,不能有构造函数
  3. 一个类可以mixins多个mixins类
  4. mixins不是继承,也不是接口,是一个新的特性
  5. 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的相关知识点,爬坑完之后再来做一个整理。