共计 9883 个字符,预计需要花费 25 分钟才能阅读完成。
最近在学 flutter,发现 flutter 的编程语言 Dart 和 Javascript 有诸多相似,对于前端开发者而已好处在于有 JavaScript 经验学习 Dart 会快一些,缺点在于容易搞混这两种语言。因此在学习的过程中记录了一下 Javascript 和 Dart 的对比,方便记忆。
1. 程序入口(Entry Point)
Javascript:
JS 不像很多语言有一个 main()
函数作为程序入口,JS 并没有标准的程序入口,JS 会从代码的第一行开始执行(在执行之前会有一些预处理的工作,比如变量提升和函数提升)。在实际项目中我们通常会有一个 index.js
这样的入口文件。
Dart:
Dart 有一个标准的程序入口:
main(){}
2. 数据类型(Data Types)
Javascript:
JS 有 8 种内置数据类型,分别为:
-
基本类型:
- Boolean:布尔类型,有两个值
true
和false
- Null:空类型,只有一个值
null
- Undefined:变量未初始化则为
Undefined
类型 - Number:数字类型,取值范围为
-(2^53-1) ~ 2^53 - 1
,可以为整数和小数 - Bigint:表示任意精度的整数,如
const x = 2983479827349701793697123n
- String:字符串类型,可用 “”, ”, “ 表示。其中 “ 用于字符串模板,比如:`
1 + 2 = ${1+2}
` - Symbol:符号类型,用于定义匿名且唯一的值,一般用作 Object 属性的 key
- Boolean:布尔类型,有两个值
- Object
其中 7 个基本类型的值是不可变的(immutable value)。Object 用来定义复杂数据类型,JS 内置了一些复杂类型比如:Function
、Date
、Array
、Map
、Set
等。
Dart:
Dart 也有 8 种内置数据类型:
- Boolean:布尔类型,有两个值
true
和false
-
Number:数字类型,又分为 int 和 double 类型
- int:整型,取值范围为
-2^63 ~ 2^63 - 1
- double:64 位双精度浮点型
- int:整型,取值范围为
- String:字符串类型,可用 ””, ” 表示。与 JS 类似,可使用模板语法
'${expression}'
,当expression 是一个标识符时可省略{}
- List:相当于 Javascript 中的 Array,例如:
var arr = [1, 2, 3]
- Set:与 JavaScript 的 Set 类似,表示无序且无重复元素的集合,例如:
var countries = {'china', 'usa', 'russia', 'german'}
-
Map:与 JavaScript 的 Map 类似,表示一组键值对的集合,其中键必须唯一,键和值都可以为任意类型,例如:
var gifts = { 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' };
-
Runes:表示字符串的 UTF-32 编码值(Unicode 为世界上所有书写系统中使用的每个字母,数字和符号定义了唯一的数值。由于 Dart 字符串是 UTF-16 编码的序列,因此在字符串中表示 32 位 Unicode 值需要特殊的语法),例如:
Runes input = new Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}'); print(new String.fromCharCodes(input)); // ♥ ???? ???? ???? ???? ????
- Symbol:与 JS 的 Symbol 不同,Dart 引入 Symbol 的意义在于在压缩代码后(压缩代码一般会修改标识符的名称,如用
a
,b
,c
代替原有 class、function、variable 的名称),依然能通过标识符的 Symbol 去访问相关的成员。
与 JS 不同的是 Dart 种所有类型都是 class,所有的值都是 class 的实例,而所有的 class 都继承于 Object
类。
3. 变量(Variables)
(1). 变量定义和赋值(Creating and assigning variables)
JavaScript:
JS 中的变量为动态类型,定义变量不需要也无法指定类型。
var name = 'Javascript';
变量可任意赋值其他类型的值。
name = 123; // 正确
Dart:
Dart 中的变量为静态类型,定义变量需要指定类型,或者由编译器进行类型推断。
String name = 'Dart'; // 指定类型
var name2 = 'flutter'; // 推断类型为 String
变量类型一旦确定则不能赋值其他类型的值。
name = 123; // 错误
如果不想指定类型或者类型未知,可以使用 dynamic
定义动态类型的变量:
dynamic name = 'something';
(2). 默认值(Default Values)
Javascript:
若变量未初始化,默认值为undefined
。
Dart:
不管何种类型,默认值都为null
。
(3). 真假值(Truthy and Falsy Values)
Javascript:
在 Javascript 中有七种值会被判定为假值,除此之外都是真值,其中假值分别为:
-
false
:关键词false
-
0
:数字 0 -
0n
:BigInt 0 -
''
,""
, “:空字符串 -
null
:空值 -
undefined
:初始值 -
NaN
:not a number
Dart:
只有布尔值 true
是真值,其余都是假值。
4. 常量(Constants)
Javascript:
ES6 中引入了 const
来定义常量。
const name = 'JavaScript';
Dart:
Dart 中有两种方式定义常量:final
和const
。
区别在于:
- final:
final
定义的常量只在使用时才会初始化和分配内存 - const:
const
用于定义编译时常量(compile-time constant),即在编译时就初始化,且值为不变值(constant value,比如基本数据类型和String
)。 -
final
可以用于类实例的属性(instance variable)而const
不可以
final pi = 3.1415926;
const g = 9.8;
5. 函数(Functions)
在 JS 和 Dart 中,函数都是“first-class object”,意味着函数可以像普通对象一样赋值给变量、作为参数传递。
(1). 普通函数
Javascipt:
function fn(a, b){return a + b;}
Dart:
int sum(int a, int b){return a + b;}
或者省略掉返回值(会降低可读性,不推荐):
sum(a, b){return a + b;}
(2). 匿名函数(anonymous function)
javascript:
JS 中匿名函数有多种用途:
- 赋值给变量:
var sum = function(a, b) {return a + b};
- 作为函数的参数:
setTimeout(function () {console.log('hello world');
}, 1000);
- 用于 IIFE(Immediately Invoked Function Expression):
(function () {console.log('hello world');
})();
Dart:
Dart 同样支持匿名函数,且用法与 JS 类似。
- 赋值给变量:
Function sum = (int a, int b) {return a + b;};
- 作为函数的参数:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {print('${list.indexOf(item)}: $item');
});
(3). 箭头函数(arrow functions)
Javascript:
ES6 中引入了箭头函数:
(a, b) => {return a + b;}
或者简写为:
(a ,b) => a + b;
Dart:
Dart 中也有类似的语法:
int sum(int a, int b) => a + b;
或者,省略返回值和参数类型:
sum(a, b) => a + b;
Dart 和 JS 中箭头函数的区别在于:
- JS 中箭头函数都是匿名的,但 Dart 中可以指定名称,当然也可以匿名,比如作为参数传入的时候。
- Dart 的箭头函数只能用在函数体为单个表达式的情况,比如下面写法是不允许的:
int sum(int a, int b) => {
int c = a + b;
return c;
}
(4). 命名参数(named parameters)
JavaScript:
ES6 中引入了参数解构的特性(parameter destructuring)。通过传入一个对象,并对其进行解构赋值来实现命名参数的特性。示例如下:
function sum({a, b}) {return a + b;}
sum({a: 3, b: 4}); // 7
Dart:
Dart 原生支持命名参数:
int sum({int a, int b}) {return a + b;}
sum(a: 3, b: 4); // 7
(5). 可选参数(optional parameters)
JavaScript:
JS 中所有的参数都是可选参数。这可以理解为 JS 的特性也可以说是设计缺陷,毕竟有时候漏传参数又不报错容易导致各种问题。
Dart:
在 Dart 中,常规的参数都是必传的,而命名参数和位置参数(positional parameter)都可以是可选参数。当然方法体中需要增加容错逻辑,已防止可选参数不传导致的报错。
- 可选命名参数:
int sum({int a, int b}) {if (b == null) b = 0;
return a + b;
}
sum(a: 3); // 3
sum(a: 3, b: 4); // 7
- 可选位置参数:
int sum(int a, [int b, int c]) {if (b == null) b = 0;
if (c == null) c = 0;
return a + b + c;
}
sum(3); // 3
sum(3, 4); // 7
sum(3, 4, 5); // 12
(6). 参数默认值(default parameters)
JavaScript:
JS 中实现参数默认有新旧两种方法:
- 判断参数是否为 undefined,如果是,则赋值为默认值:
function sum(a, b){if(typeof a === 'undefined') a = 0;
if(typeof b === 'undefined') b = 0;
return a + b;
}
sum(); // 0
sum(3); // 3
sum(3,4); // 7
- 使用 ES6 的默认参数特性(default parameters):
function sum(a = 0, b = 0){return a + b;}
sum(); // 0
sum(3); // 3
sum(3,4); // 7
Dart:
Dart 中也支持默认参数,但是只有命名参数和位置参数可以设置默认值:
- 命名参数:
int sum({int a, int b = 0}) {return a + b;}
sum(a: 3); // 3
sum(a: 3, b: 4); // 7
- 位置参数:
int sum(int a, [int b = 0, int c = 0]) {return a + b + c;}
sum(3); // 3
sum(3, 4); // 7
sum(3, 4, 5); // 12
6. 闭包(Closure)
闭包本质上也是函数,放在和函数并列的位置讲是为了凸显闭包的重要性,也方便大家阅读。
JS 和 Dart 都有闭包,本质上是因为它们都使用词法作用域(Lexical Scope)且可以在函数内部再定义函数。所谓的词法作用域又叫静态作用域(Static Scope),也是大部分编程语言采用的机制,即作用域仅由代码的本文结构确定,比如内层大括号中可以访问外层大括号中定义的变量,而外层大括号不能访问内层大括号中定义的变量。与词法作用域相对的是动态作用域(Dynamic Scope),动态作用域不取决于代码的文本结构而是程序的执行状态、执行上下文。
当在函数内部再定义函数,而内部函数使用了外部函数的变量、参数,当外部函数返回后内部函数仍然保存了这些变量、参数。此时内部函数就成为了一个闭包。
JS 和 Dart 中的闭包用法也几乎一样,示例:
JavaScript:
function makeAdder(addBy) {return (x) => addBy + x;
}
var add2 = makeAdder(2);
var add4 = makeAdder(4);
add2(3); // 5
add4(3); // 7
Dart:
Function makeAdder(num addBy) {return (num x) => addBy + x;
}
var add2 = makeAdder(2);
var add4 = makeAdder(4);
add2(3); // 5
add4(3); // 7
7. 类(Class)
Class 是面向对象编程语言的核心特性,JavaScript 虽然是函数式语言,但在 ES6 中还是引入了 class。JavaScript 的 class 本质上是基于原型继承的封装,是语法糖而已,并不是真正的面向对象继承模型的实现。而 Dart 天生就是面向对象的语言,所以 Dart 的 class 相比 JS 更加的强大和完善。
本文只比较 JS 和 Dart 的 class 都有的特性,而 Dart 的其他特性大家看 官方文档 更合适。
(1). 定义 class
JavaScript:
JS 中定义一个 class,有两种方式:类声明(class declaration)和类表达式(class expression)。我们一般都会使用类声明。
类声明:
class Rectangle {}
类表达式:
var Rectangle = class {}
Dart:
而 Dart 中只支持类声明的方式,语法和 JS 一致:
class Rectangle {}
(2). 构造函数
JavaScript:
JS 中 class 的构造函数为统一的 constructor
函数,每个 class 只能定义一个构造函数。也可以不定义,这时会使用一个默认的构造函数。
class Rectangle {constructor(height, width) {
this.height = height;
this.width = width;
}
}
Dart:
Dart 中,构造函数名称和类名相同,而且初始化成员变量之前需要先定义。
class Rectangle {
num width, height;
Rectangle(num height, num width) {
this.height = height;
this.width = width;
}
}
为了减少先定义再初始化成员变量的繁琐,Dart 提供了语法糖来简化这个过程:
class Rectangle {
num width, height;
Rectangle(this.width, this.height);
}
与 JS 不同的是 Dart 的 class 能定义多个构造函数,被称为命名构造函数(named constructor),例如下面的Rectangle.square()
:
class Rectangle {
num width, height;
Rectangle(this.width, this.height);
Rectangle.square() {
width = 100;
height = 100;
}
}
(3). 构造函数的继承
JavaScript:
JS 中 class 的构造函数可以继承,当子类未定义构造函数时默认会使用父类的构造函数:
constructor(...args) {super(...args);
}
而子类也可以通过调用父类的构造函数创建:
class Square extends Rectangle {}
const square = new Square(100, 100);
Dart:
Dart 中,构造函数是不能继承的! 这是 Dart 区别于其他很多高级语言的地方。但是当子类未定义任何构造函数时会默认使用父类的无参构造函数(no-argument constructor)。
如果要在子类中使用和父类一样的构造函数,必须在子类中再次定义,例如这样是不行的:
class Rectangle {
num width, height;
Rectangle();
Rectangle.size(width, height) {
this.width = width;
this.height = height;
}
}
class Square extends Rectangle {}
var square = Square.size(100, 100);
需要在子类中再定义一个 .size()
构造函数:
class Square extends Rectangle {Square.size(size) {
width = size;
height = size;
}
}
var square = Square.size(100);
或者也可以重定向到父类的构造函数:
class Square extends Rectangle {Square.size(size) : super.size(size, size);
}
(4). 成员变量
JavaScript:
JS 中成员变量无需定义就能使用,但是为了类结构更清晰还是推荐先定义。成员变量可以在定义时初始化,也可以只定义而不初始化,例如:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
Private 变量:
成员变量默认是公共的(public),也可以定义为私有(private),只需在变量名前面加#
:
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
Static 变量:
JS 的 class 还支持静态(static)变量,静态变量只会创建一份,由所有的实例公用,适合存放固定不变的值。例如:
class ClassWithStaticField {static staticField = 'static field';}
Dart:
Dart 中的成员变量定义方式和 JS 类似,可以只声明也可以声明 + 初始化:
class Rectangle {
num width;
num height = 100;
}
Private 变量:
同样 Dart 中也可以定义 private 变量,与 JS 在变量名之前加 #
不同,Dart 在前面加_
。但 Dart 中的 private 是相对于 library,而不是 class。
Static 变量:
Dart 也支持定义静态变量:
class Queue {static const initialCapacity = 16;}
print(Queue.initialCapacity); // 16
(5). 成员函数
和成员变量类似,成员函数也分为 public、private、static。
JavaScript:
JS 中成员函数默认为 public,比如:
class Rectangle {constructor(height, width) {
this.height = height;
this.width = width;
}
getArea() {return this.height * this.width;}
}
Private 方法:
定义 private 的成员函数也在函数名前面加 #
就行了:
class Rectangle {#getArea() {return this.height * this.width;}
}
Static 方法:
定义 Static 的成员函数只需在函数名前面加 static
关键词:
class DemoClass {static #privateStaticMethod() {return 42;}
static publicStaticMethod() {return DemoClass.#privateStaticMethod();
}
}
Dart:
Dart 中 class 的成员函数定义和 JS 类似:
class Rectangle {
num width;
num height = 200;
Rectangle(this.width, this.height);
num getArea() {return width * height;}
}
var rect = Rectangle(30, 40);
rect.getArea(); // 1200
Private 方法:
与变量一样,方法名前面加 _
也会被认定为私有方法。
Static 方法:
静态方法的定义也是在前面加上 static
关键词。
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
8. 异步编程(Asynchronous Programming)
使用 Dart 进行异步编程总会有似曾相识感,和 JS 一样都可以使用回调函数、和 Promise
如出一辙的 Future
还有 async/await
语法。
(1). 回调函数(Callback Function)
Javascript:
function fn(callback) {setTimeout(callback, 1000);
}
fn(() => { console.log('hello world') });
Dart:
import 'dart:async';
fn(Function callback){Timer(Duration(seconds: 1), callback);
}
fn((){print('hello world');
});
(2). Promise 和 Future
和 Javascript 中的 Promise
类似,Dart 提供了 Future
用于表示异步操作最终完成的结果。
Javascript:
function getIP() {return fetch('https://httpbin.org/ip')
.then(res => res.json())
.then(resJson => {
const ip = resJson.origin;
return ip;
});
}
getIP()
.then(ip => console.log(`my ip is ${ip}`))
.catch(err => console.error(err));
Dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<String> getIP() {return http.get('https://httpbin.org/ip').then((res) {String ip = jsonDecode(res.body)['origin'];
return ip;
});
}
getIP().then((ip) => print('my ip is $ip')).catchError((error) => print(error));
(3). Async 和 Await
ES2017 中引入的 async/await
语法进一步提升了异步编程的体验,用同步语法进行异步编程,比如:
JavaScript:
async function getIP() {const res = await fetch('https://httpbin.org/ip');
const resJson = await res.json();
const ip = resJson.origin;
return ip;
}
async function main() {
try {const ip = await getIP();
console.log(`my ip is ${ip}`);
} catch (err) {console.error(err);
}
}
main();
Dart:
Dart 的 async/await
语法几乎和 JS 相同,与 JS 的 async 方法返回 Promise
对象类似,Dart 的 async 方法返回一个 Future
对象。
void main() async {Future<String> getIP() async {final res = await http.get('https://httpbin.org/ip');
String ip = jsonDecode(res.body)['origin'];
return ip;
}
try {final ip = await getIP();
print('my ip is $ip');
} catch (err) {print(err);
}
}
有未涉及到的 JS 和 Dart 的语法对比,欢迎大家补充,我会及时更新。