Dark 语言入门

2,911 阅读8分钟

Dark 语言入门

Dark 语言是 2011 年 10 月,在丹麦召开的 GOTO 大会上发布的编程语言。如同 Kotlin 和 Swift 的出现,分别是为了解决 Java 和 Objective-C 在编写应用程序的一些实际问题一样,Dart 的诞生正是要解决 JavaScript 存在的、在语言本质上无法改进的缺陷。

但是由于JavaScript 生命力太过顽强,原来的JavaScript只能在浏览器运行,但随着Node.js 的出现让它开始有能力运行在服务端,很快手机应用与桌面应用也成为了 JavaScript 的宿主容器,一些明星项目比如 React、React Native、Vue、Electron等框架如雨后春笋般崛起,迅速扩展了它的边界。Dark 最初想替代JavaScript的梦想也随之破灭了,随着Google 内部孵化了移动开发框架 Flutter,Dark也进入了移动开发的领域;而在 Google 未来的操作系统 Fuchsia 中,Dart 更是被指定为官方的开发语言。

Flutter 开发框架采用的开发语言是 Dart,所以对Dark 语言的了解和学习是有必要的。

我们都知道开始学习一门语言,千万不要一开始就陷入细节里面,而是要鸟瞰其全貌,了解其特性,然后再其他语言的基础上,对比学习,可以收获更好的效果。

Dark 的特性

同时支持 JIT 与 AOT 编译

JIT(Just In Time,即时编译):在执行代码时,不需要提前编译,提高开发效率,但是会影响运行速度和执行性能,适合在开发阶段使用。 Flutter 的热重载就是基于此。JavaScript、Python 等几乎所有的脚本语言都是采用即时编译。

AOT(Ahead of Time,运行前编译):在代码执行前,需要提前编译成二进制代码,然后再执行,这样会提高运行速度和执行性能。Java C/C++ 都是采用 运行前编译。

单线程模型

Java、C、C++等高级语言,对并发的处理是是通过抢占式来实现多线程的,即:每个线程都会被分配一个固定的时间片来执行,超过了时间片后线程上下文将被抢占后切换。如果这时正在更新线程间的共享资源,抢占后就可能导致数据不同步的问题。

而为了解决数据不同步的问题,通过给线程加锁来完成,但是这样会加大对性能的消耗,甚至会出现严重的死锁问题。

Dark则是另辟蹊径,只有单线程,这样就不会存在资源竞争和同步状态的问题,代码从一执行,就不会被打断,一直到执行结束。

Dart 中并没有线程,只有 Isolate(隔离区)。Isolates 之间不会共享内存,就像几个运行在不同进程中的 worker,通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信,这样就达到了类似“多线程”的效果。

布局直接写在代码里

在过去Android 开发中,布局需要单独写在一个XML文件里,但在Flutter中布局直接通过Dark 编码来完成。

Dart 使用的是声明式布局,和iOS 的swift 语言一样,不需要单独的声明式布局语言,通过 热重载可以让我们立即在手机上看到运行效果。

学习了Dark语言的一些特性后,再继续学习Dark的基础用法。虽然编程的语言千差万别,都是最终都是回答两个问题:如何表示信息? 如何处理信息?

Dark 如何表示信息


printInteger(int number) {
  print('Hello world, this is $number.'); 
}

main() {
  var number = 2021; 
  printInteger(number); 
}

可以看到这其他语言一样,入口函数还是 main,整体的写法和Java 一样,在代码末尾都要有 ;

Dark 中,变量的表示有两种,一种是 使用 var 通过编译器推断是那种类型,另一种是 :通过具体类型声明;

var name = "jay";
String name2 = "jay2";

在默认情况下,未初始化的变量的值都是 null;Dart 是类型安全的语言,并且所有类型都是对象类型,都继承自顶层类型 Object,因此一切变量的值都是类的实例(即对象),甚至数字、布尔值、函数和 null 也都是继承自 Object 的对象。

看看开发中常用的基本数据类型,数字类型、字符串类型、布尔类型、集合是怎么表示的。

数字的表示

Dart 的数值类型 num,只有两种子类:即 64 位 int 和符合 IEEE 754 标准的 64 位 double。

  int number1 = 1;
  double number2 = 1.23;

除了常见的基本运算符,比如 +、-、*、/,以及位运算符外,还可以使用继承自 num 的 abs()、round() 等方法,来实现求绝对值、取整的功能Dark文档

image.png mun中有很多的api 方便我们的使用。如果这些还不够,可以使用 dart:math 这个库提供了诸如三角函数、指数、对数、平方根等高级函数。

字符串的表示

Dart 的 String 由 UTF-16 的字符串组成。和 JavaScript 一样,构造字符串字面量时既能使用单引号也能使用双引号,还能在字符串中嵌入变量或表达式:你可以使用 ${express} 把一个表达式的值放进字符串。而如果是一个标识符,你可以省略{} 字符串的拼接可以使用 +完成; 对于多行字符串的构建,你可以通过三个单引号或三个双引号的方式声明。

var str = 'hello';
var str1 = 'this is a uppercased string: ${s.toUpperCase()}';

var str3 = str1 + str2;

  var str4 = ''' this is line 1 
  this is line 2
  this is line3
  ''';


布尔类型的表示

在 Dart 里,只有两个对象具有 bool 类型:true 和 false,它们都是编译时常量。

集合的表示

在 Java 中有 数组,有list,有map,在Dark 里面也有。

// 数组
  var arr = <String>['string1', 'string2', 'string3'];
  //list
  var list = new List<int>.of([11,12,13]);
  list.add(100);
  list.forEach((v) => print('${v}'));
  print(list is List<int>); // true
  //map
  var map1 = <String, String>{'name': 'jay','sex': 'male'};
  var map2 = new Map<String, String>();
  map2['name'] = 'jay';
  map2['sex'] = 'male';
  map2.forEach((k,v) => print('${k}: ${v}'));
  print(map2 is Map<String, String>); // true

常量的表示

在定义变量前加上 final 或 const 关键字:

const,表示变量在编译期间即能确定的值;

final 则不太一样,用它定义的变量可以在运行时确定值,而一旦确定后就不可再变。

final name = 'Jay';
const number = 3;

var x = 4;  
var y = 2;
final z = x / y;// 这里的z 不可以使用 const

简单的了解了 Dark 语言是如何表示信息的,下面再看看 Dark 是如何处理信息的。

Dark 如何处理信息

Dart 将处理信息的过程抽象为了对象,以结构化的方式将功能分解,而函数、类与运算符就是抽象中最重要的手段。

函数

函数就是一段独立完成某个功能的代码。在Dark 中所有类型都是对象类型,函数作为对象,所以函数也是对象类型,可以定义为变量、作为参数传递给另一个函数。 Dark 中,函数体里面的代码只有一行时,可以使用 =>来连接函数名和函数体,不用 {}

int add(int a,int b)=> a + b;

一个函数中可能需要传递多个参数。那么,如何让这类函数的参数声明变得更加优雅、可维护,同时降低调用者的使用成本呢?

在Java 和C/C++中通过函数的重载来完成;但Dark 中没有函数的重载,但提供了两个别的方式来完成;可选命名参数和可选参数。

可选命名参数:在参数调用的时候,指定参数名,这样就可以避免不按顺序传递参数出现的错误了;在定义函数时,参数的定义是这样的 {type:value}

可选参数:就是这个参数不是必须的,可传可不传,在定义函数时,参数的定义是这样的 [type:value]


//要达到可选命名参数的用法,那就在定义函数的时候给参数加上 {}
void optionalParameters({bool bold, bool hidden}) => print("$bold , $hidden");

//定义可选命名参数时增加默认值
void optionalParameters2({bool bold = true, bool hidden = false}) => print("$bold ,$hidden");

//可忽略的参数在函数定义时用[]符号指定
void optionalParameters3(bool bold, [bool hidden]) => print("$bold ,$hidden");

//定义可忽略参数时增加默认值
void optionalParameters4(bool bold, [bool hidden = false]) => print("$bold ,$hidden");

//可选命名参数函数调用
optionalParameters(bold: true, hidden: false); //true, false
optionalParameters(bold: true); //true, null
optionalParameters2(bold: false); //false, false

//可忽略参数函数调用
optionalParameters3(true, false); //true, false
optionalParameters3(true,); //true, null
optionalParameters4(true); //true, false
optionalParameters4(true,true); // true, true

可选命名参数在Dark 中很常用,

类是特定类型的数据和方法的集合。

Dart 在声明变量与方法时,在前面加上“”即可作为 private 方法使用。如果不加“”,则默认为 public。不过,“_”的限制范围并不是类访问级别的,而是库访问级别。

class Person{
  String name;
  int age;
  Person(this.name,this.age);
  void say()=>print("name = $name,arg = $age"); 
}

Person("jay",22)
    ..say();// name = jay,arg = 22

除了可选命名参数和可选参数之外,Dart 还提供了命名构造函数的方式。在构造函数的函数体真正执行之前,还有机会给实例变量赋值,甚至重定向至另一个构造函数。

class Person{
  String name;
  int age;
  Person({this.name,this.age});
  void say()=>print("name = $name,arg = $age");
  Person.withDefaultAge({name:String}):this(name:name,age:22);
  
}
Person(name: "jay",age: 23)
    ..say();//name = jay,arg = 23

Person.withDefaultAge(name: "jeck")
  ..say();//name = jeck,arg = 22

可以看到 在 Person.withDefaultAge方法中,可以重定向到构造方法,但是需要注意的是,在 命名构造函数 方法里面,不能初始化变量,否则就会报错。

image.png

复用

在编程语言中的复用有:接口的实现、类的继承,Dark 也不例外。

接口的实现:子类获取到的仅仅是接口的成员变量符号和方法符号。

class D{
  void sysHello(){
    print("hello,i am D");
  }
}

class E implements D{
  @override
  void sysHello() {
    print("hello,i am E");
  }
}

在Dark 中,接口的定义也是使用 关键字 class ,只是在继承的时候,使用 implements

类的继承:子类由父类派生,会自动获取父类的成员变量和方法实现。和Java 一样,Dark 中也只有单继承,不支持多继承,因为多重继承可能导致的歧义(菱形问题)。

继承歧义,也叫菱形问题,是支持多继承的编程语言中一个相当棘手的问题。当 B 类和 C 类继承自 A 类,而 D 类继承自 B 类和 C 类时会产生歧义。如果 A 中有一个方法在 B 和 C 中已经覆写,而 D 没有覆写它,那么 D 继承的方法的版本是 B 类,还是 C 类的呢?
class D{
  void sysHello(){
    print("hello,i am D");
  }
}
class F extends D{

}
F()..sysHello();//hello,i am D

但是 Dark 除了接口的实现和类的继承,还提供了一种方式来完成类的复用-- 混入使用 with 关键字来完成。

class D{
  void sysHello(){
    print("hello,i am D");
  }
}

class I{
  void sayHi(){
    print("hi,i am I");
  }
}
class G with D,I{

}
G()..sysHello()//hello,i am D
..sayHi();//hi,i am I

可以看到通过混入实现的效果和继承一样,但是通过多混入,实现多继承的效果,(需要注意的是,当混入的类实现同一个接口时,后面的混入会覆盖前面的)

class D{
  void sysHello(){
    print("hello,i am D");
  }
}

class E implements D{
  @override
  void sysHello() {
    print("hello,i am E");
  }
}

class J implements D{
  @override
  void sysHello() {
    print("hello,i am J");
  }
}

class K with E,J{

}

K()..sysHello();//hello,i am J