一个|一个 JSer 的 Dart 学习日志(一)(函数)

近来想拓展一下技能,感觉 Flutter 挺有奔头,所以就扎进来了,但要使用 Flutter 必先学会 Dart。粗略看了一下,Dart 在计上与 JS 很像:事件驱动、单线程环境、熟悉的 var/ const 关键字……
既然我已经学会 JS 了,从 挖掘两种语言的共同点并带入相关的不同点入手可能是一种不错的学习方案,也可以顺道复习一下 JS 。如无特殊说明,本文中的 JS 包括 ES5 至 ES2021 的全部特性。
当然,这系列文章更多的是自学者的一些见解,疏漏和谬误难免,如果诸君慧眼识虫,望不吝指正。
一、函数形式 1. 普通函数 相同之处
  • JS 和 Dart 的普通函数结构相似:函数声明,后面是函数名,然后分别是以圆括号()包裹、以逗号 , 区分的参数列表,最后是以花括号{}包裹的函数体;
  • JS 支持使用 = 指定函数参数的默认值,新版 Dart 也支持这一语法;
  • JS 可以使用对象作为参数,从而规定该对象的某些值,Dart 也支持形似的语法(不过调用函数的时候传值方案不太一样,后面详说)。
    // 可以看出和 JS 的函数很像 // 把 int 改成 function 就是了 int foo(a = 1, b = 2){ return a + b; }

不同之处
声明关键字不同
  • JS 中的普通函数须以 function 关键字声明;
  • Dart 中的普通函数以返回类型声明开始,也可以缺省。
    // 缺省返回类型,交给 Dart 推断 /*int*/ foo(a = 1, b = 2){ return a + b; }

JS 不支持类型声明
  • JS 不支持类型声明,因此返回值和参数的类型只有在运行时才会确定;
  • Dart 支持类型声明,可以指定返回值与参数的类型,但均可以缺省。
    // 上面省略了参数类型声明,这里把它补上 int foo(int a = 1, int b = 2){ return a + b; }

可选参数
  • Dart 函数可使用一个 [] 包裹其参数列表尾部的可选参数,所有未标为required 的命名参数都是可选参数;
  • JS 的所有参数(除了作为命名参数载体的对象及其前面的位置参数之外)都是可选参数。
    // Dart 位置参数 foo(int a = 1, [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


  • 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 中只能对匿名函数这么做:
    // 把注释去掉,是无法通过编译的 var testFunc = /*funcName*/ (){}; // 这里的分号不可或缺,这是其他的知识点


2. 箭头函数 相似之处
  • 两者的语法都是 (参数列表) => {表达式}
  • 花括号{}都可以省略。
不同之处
圆括号不可省
  • JS 的箭头函数,如果只有一个参数的话,参数列表外可以不套圆括号:
    const testFunc = a => a++;


  • Dart 的箭头函数必须用圆括号包括参数列表。
只能写一个表达式
  • 严格来说这不是箭头函数的语法差异,因为在 JS 中,用花括号包裹起来的所有代码块都视作一个表达式,所以箭头函数可以包罗万象;
  • 但在 Dart 中,貌似花括号失去了上述“魔力”,表达式的真正分界是分号 ; ,虽然用逗号可以隔开多个语句,但是仍无法再声明任何中间变量了:
    var testFunc = (start, {end = 7}) => { // 去掉这个注释,编译将无法通过 /*int finalEnd */= start + end, for(num i = start; i < end; i++){ display((i + 1).toString()) } };


  • 如果你想在箭头函数中多放些语句,可以用一个 IIFE 包裹起来:
    var testFunc = (start, {end = 7}) => (() { var finalEnd = start + end; for(num i = start; i < finalEnd; i++){ display((i + 1).toString()); } })();

    ,嗯……让人不由得想起某个 ES5 的经典面试题。
二、程序入口 Dart 需要 main 函数
  • 众所周知, JS 是没有入口函数的,在当前执行上下文中“裸泳”的表达式会直接被执行,而 JS 的顶层作用域也是第一个执行上下文。
  • 不过 Dart 并不阻止你在全局作用域写表达式——前提是把它们写在变量声明语句里:
    vaoi main(){ } var a = 3 + 2 - 5 * 0;


  • Dart 也不阻止你在全局调用函数——但是这个语句可能在编译时优化掉了:
    vaoi main(){ }testFunc(){ print('called me'); }var testVar = testFunc(); // 控制台空空如也

    【一个|一个 JSer 的 Dart 学习日志(一)(函数)】(当然这可能是 web 版 DartPad 的特色,其他语法检查器件/编译器可能会报错的,规范之外的sao操作建议还是少做一点比较好)。

    推荐阅读