乐趣区

前端转Flutter-对照Javascript学Dart

最近在学 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:布尔类型,有两个值 truefalse
    • Null:空类型,只有一个值null
    • Undefined:变量未初始化则为 Undefined 类型
    • Number:数字类型,取值范围为-(2^53-1) ~ 2^53 - 1,可以为整数和小数
    • Bigint:表示任意精度的整数,如const x = 2983479827349701793697123n
    • String:字符串类型,可用 “”, ”, “ 表示。其中 “ 用于字符串模板,比如:`1 + 2 = ${1+2}`
    • Symbol:符号类型,用于定义匿名且唯一的值,一般用作 Object 属性的 key
  • Object

其中 7 个基本类型的值是不可变的(immutable value)。Object 用来定义复杂数据类型,JS 内置了一些复杂类型比如:FunctionDateArrayMapSet等。

Dart:

Dart 也有 8 种内置数据类型:

  • Boolean:布尔类型,有两个值 truefalse
  • Number:数字类型,又分为 int 和 double 类型

    • int:整型,取值范围为-2^63 ~ 2^63 - 1
    • double:64 位双精度浮点型
  • 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 中有七种值会被判定为假值,除此之外都是真值,其中假值分别为:

  1. false:关键词false
  2. 0:数字 0
  3. 0n:BigInt 0
  4. '', "", “:空字符串
  5. null:空值
  6. undefined:初始值
  7. NaN:not a number

Dart:

只有布尔值 true 是真值,其余都是假值。

4. 常量(Constants)

Javascript:

ES6 中引入了 const 来定义常量。

const name = 'JavaScript';

Dart:

Dart 中有两种方式定义常量:finalconst

区别在于:

  • 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 中匿名函数有多种用途:

  1. 赋值给变量:
var sum = function(a, b) {return a + b};
  1. 作为函数的参数:
setTimeout(function () {console.log('hello world');
}, 1000);
  1. 用于 IIFE(Immediately Invoked Function Expression):
(function () {console.log('hello world');
})();

Dart:

Dart 同样支持匿名函数,且用法与 JS 类似。

  1. 赋值给变量:
Function sum = (int a, int b) {return a + b;};
  1. 作为函数的参数:
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 中箭头函数的区别在于:

  1. JS 中箭头函数都是匿名的,但 Dart 中可以指定名称,当然也可以匿名,比如作为参数传入的时候。
  2. 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)都可以是可选参数。当然方法体中需要增加容错逻辑,已防止可选参数不传导致的报错。

  1. 可选命名参数:
int sum({int a, int b}) {if (b == null) b = 0;
  return a + b;
}

sum(a: 3); // 3
sum(a: 3, b: 4); // 7
  1. 可选位置参数:
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 中实现参数默认有新旧两种方法:

  1. 判断参数是否为 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
  1. 使用 ES6 的默认参数特性(default parameters):
function sum(a = 0, b = 0){return a + b;}

sum(); // 0
sum(3); // 3
sum(3,4); // 7

Dart:

Dart 中也支持默认参数,但是只有命名参数和位置参数可以设置默认值:

  1. 命名参数:
int sum({int a, int b = 0}) {return a + b;}

sum(a: 3); // 3
sum(a: 3, b: 4); // 7
  1. 位置参数:
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 的语法对比,欢迎大家补充,我会及时更新。

退出移动版