近来想拓展一下技能,感觉 Flutter 挺有奔头,所以就扎进来了,但要使用 Flutter 必先学会 Dart。粗略看了一下,Dart 在计上与 JS 很像:事件驱动、单线程环境、熟悉的var
/const
关键字……
既然我已经学会 JS 了,从挖掘两种语言的共同点并带入相关的不同点入手可能是一种不错的学习方案,也可以顺道复习一下 JS 。
当然,这系列文章更多的是自学者的一些见解,疏漏和谬误难免,如果诸君慧眼识虫,望不吝指正。
如无特殊说明,本文中的 JS 包含 ES5 至 ES2021 的全部特性。
1. 普通函数
相同之处
函数结构相似:首先是函数声明关键字(这里略有不同,后面会说),后面是函数名,然后分别是以圆括号
()
包裹、以逗号,
区分的参数列表,最后是以花括号{}
包裹的函数体:> // JavaScript | // Dart > function foo(){ | int foo() { > // do some thing here | // do some thing here > } | }
;
- 使用
=
为函数参数指定默认值; JS 可以使用对象作为参数,从而规定该对象的某些值,Dart 也支持形似的语法,但仅限于可选参数。
> // JavaScript | // Dart > function foo(a = 1, b = 2){ | int foo([a = 1, b = 2]){ > return a + b; | return a + b; > } | }
不同之处
声明关键字不同
- JS 中的普通函数须以
function
关键字声明; Dart 中的普通函数以类型声明开始,该声明也可以缺省。
> // JS: starts with `function` | // Dart 可缺省返回类型 > function foo(a = 1, b = 2){ | /*int*/ foo(a = 1, b = 2){ > return a + b; | return a + b; > } | }
JS 不支持类型声明
- JS 不支持类型声明,因此返回值和参数的类型只有在运行时才会确定;
Dart 支持类型声明,可以指定返回值与参数的类型,但均可以缺省。
> // JS | // Dart > function foo(a = 1, b = 2){ | int foo(int a, int b){ > return a + b; | return a + b; > } | }
可选参数
- JS 的所有参数(除了作为命名参数载体的对象,及其前面的位置参数之外)都是可选参数;
Dart 函数可使用一个
[]
包裹其参数列表尾部的可选参数,所有未标为required
的命名参数都是可选参数。// Dart 位置参数 foo(int a, [int b = 2]){ return a + b; } // Dart 命名参数 foo({required a, b = 2}){ return a + b; }
命名参数的传入方式
如前所述,JS 的命名参数实际上是使用对象占位,所以调用函数的时候,直接在对应位置传入一个对象即可:
function testFunc(a, {b = 1}){ return a + b; } testFunc(a, {b: 2}); // 3 testFunc(a); // 会报错,因为第二个位置参数必须是一个对象
;
Dart 的命名参数是特定的语法,调用函数的时候用
name: value
的方式指定:int testFunc(a, {b = 1}){ return a + b; } testFunc(a, b : 2); // 3
。
相比之下 Dart 的语法可以简洁也可以精确,但是初见的时候多少有些不习惯。
其他
- 在某个例子里,我似乎瞥见了
foo.call
,看来某些 JS 的“精粹”也被 Dart 借用了,不知道二者有多大的相似之处; - JS 中没有可变参数的概念,但可以用
arguments
或者[...args]
访问函数的全部参数,这是在开发中使用高阶函数(HOC
)的基础,但是我暂未看到类似的 Dart 语法或方法,不知道使用 Dart 如何获取未知长度的参数列表; - 两种语言中的匿名函数的异同点基本与命名函数一致,按下不表。
- 提到 JS ,就不得不说那“迷人”的
this
指向,幸运的是 Dart 的普通函数没有这个东西! JS 的命名函数可以在声明的同时赋给一个变量,但是在 Dart 中只能对匿名函数这么做:
> JS | // Dart 把注释去掉,将无法通过编译 > var foo = function fooName (){ | var foo = /*fooName*/ (){ > // do some thing | // do some thing > } | }; > | //↑ 注意!赋值语句后的分号不可或缺
。
2. 箭头函数(Dart中叫“箭头表达式”)
相似之处
两者的语法都是
(参数列表) => 表达式
;> // JS | // Dart > let foo = (a) => a++; | var foo = (a) => a++;
不同之处
圆括号不可省
- JS 的箭头函数,如果只有一个参数的话,参数列表外可以不套圆括号;
Dart 的箭头函数必须用圆括号包括参数列表。
> // JS | // Dart > var foo = a => a++; | var foo = (a) => a++;
Dart 箭头表达式只能写一个表达式
严格来说二者都只能写一个表达式,但是两种语言中{}
的语意不同,表现出来就是 JS 的箭头函数表达力更强一点。
- 在 JS 中,胖箭头后面的花括号和普通函数的花括号一样,表示函数体,其中包含的是一个代码块,如果有返回值的话需要在末尾处
return
; 但在 Dart 中,胖箭头后的花括号是
Set
或Map
的字面量语法,加了花括号意味着函数的返回值变成了一个Set
或Map
实例。> // JS | // Dart:return 你就错了 > var foo = (a) => {return a++}; | var foo = (a) => {/*return*/ a++}; > console.log(`res: ${foo(2)}`); | print(`res: ${foo(2)}`)); > // res: 3 | // res: {3} // 返回的是一个 Set
,如果你对 Dart 的处理方式不太适应,想想 JS 箭头函数后面是
[]
的情形,另外还有人想这样返回一个对象:var foo = a => { a: a }; // 当然是不可能的,这会被识别成 label 语法,而非对象
,在 JS 中无法做到,现在 Dart 帮你实现了:这样的语法会返回一个
Map
实例。不过如果你想在箭头函数中多放些语句,可以用一个
IIFE
包裹起来:var testFunc = (start, {end = 7}) => (() { var finalEnd = start + end; for(num i = start; i < finalEnd; i++){ display((i + 1).toString()); } })();
,嗯……让人不由得想起 ES5 的某经典面试题。
如果我强迫自己加个圆括号——
var foo = (a) => ({'a': a});
的形式,那么 JS 和 Dart 的表现就很相似,前者返回对象,后者返回
Map
。或者,下面的代码
var fun = (a) => [a];
在两种语言中的表现高度一致:JS 返回 Array;Dart 返回 List。
3. Dart 的 main 函数
- 众所周知, JS 没有入口函数的说法,在当前执行上下文中“裸泳”的表达式会直接被执行,而 JS 的顶层作用域也是第一个执行上下文。
不过 Dart 并不阻止你在全局作用域写表达式——前提是把它们写在变量声明语句里:
void main(){ // invoke functions } var a = 3 + 2 - 5 * 0;
;Dart 也不阻止你在全局调用函数——但是这个语句可能在编译时优化掉了:
void main(){ // invoke functions } testFunc(){ print('called me'); } var testVar = testFunc(); // 控制台空空如也
(当然这可能是 web 版 DartPad 的特色,其他语法检查器/编译器可能会报错,规范之外的sao操作建议还是少做一点比较好)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。