Dart - 语法糖(持续更新)

文章目录

  • 前言
  • 开发环境
  • 中间表示
  • 语法糖
    • 1. 操作符/运算符(?./??/??=/../?../.../...?)
    • 2. 循环(for-in)
    • 3. 函数/方法(=>)
    • 4. 关键字(await for)
  • 最后


前言

通过将dill文件序列化为可读文本查看Dart语法糖的中间表示(IR),并尝试反推大致的等价源码,便于进一步理解和使用这些语法糖。

开发环境

  • macOS: 13.4
  • Dart: 3.0.5

中间表示

中间表示序列化文本的本质是解析抽象语法树(AST)的各个节点并打印拼接成文本,节点打印以及内容拼接格式请参考ast_to_text.dart。以下列出一些常见的格式方便后面阅读理解中间表示序列化为文本后的内容:

注意,这些不是官方说明,只是通过阅读和调试源码得到的个人经验总结,仅供参考。如果需要调试源码,请参考这篇文章Dart - dill文件序列化为可读文本。

  • ::

用于指定库或类的成员。例如core::String表示dart.core库中的String类型,Dart常见类型都属于这个核心库,详见core.dart。

如果你遇到奇怪的库名称,那一般是因为成员找不到所属的具体库,比如你自己自定义的类或者顶级函数等,这时的库名称是成员所在dart文件的文件名(有所缩减)。例如下文中经常出现的syn,刚开始我也以为是Dart中的某个库,结果搜遍了Dart SDK都没找到定义的源码,没办法直接开启调试,结果原来是文件名缩写。

Dart - 语法糖(持续更新)_第1张图片

关键源码(位于ast_to_text.dart):

class NameSystem {
  ...
  String nameCanonicalNameAsLibraryPrefix(Reference? node, CanonicalName? name,
      {String? proposedName}) {
    return prefixes.disambiguate(node, name, () {
      if (proposedName != null) return proposedName;
      CanonicalName? canonicalName = name ?? node?.canonicalName;
      if (canonicalName?.name != null) {
        String path = canonicalName!.name;
        int slash = path.lastIndexOf(pathSeparator);
        if (slash >= 0) {
          path = path.substring(slash + 1);
        }
        if (path.endsWith('.dart')) {
          path = path.substring(0, path.length - '.dart'.length);
        }
        return abbreviateName(path);
      }
      return 'L';
    });
  }

  final RegExp punctuation = new RegExp('[.:]');

  String abbreviateName(String name) {
    int dot = name.lastIndexOf(punctuation);
    if (dot != -1) {
      name = name.substring(dot + 1);
    }
    if (name.length > 4) {
      return name.substring(0, 3);
    }
    return name;
  }
}

文件名为syntactic_sugar.dart时,会先截掉文件扩展名,然后截取前三个字符,得到syn。如果文件名是aa.bb.dart,会得到bb

  • let表达式

let表达式通过定义一个局部变量避免一些重复计算,一般长这样(来源Let节点的文档注释):

let v = x in y

let表达式的返回值取决于in子句中表达式y的值。举个下文中的例子:

let final core::String? #t1 = syn::a in #t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int}
  • vfinal core::String? #t1
  • xsyn::a
  • y#t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int}

#t1是一个局部变量,命名规则是#t加上索引值,参考NameSystem类。

  • @vm.call-site-attributes.metadata=receiverType:library

这是由Dart VM使用的一种特殊的元数据,用于存储调用接收者的静态类型,以便优化后端编译。以上是个人理解,可能有偏差。其实这个不了解也不太影响阅读序列化的文本。

语法糖

1. 操作符/运算符(?./??/??=/…/?../…/…?)

  • ?.(条件访问成员)

使用示例:

String? a;
var b = a?.length;

中间表示:

static field core::String? a;
static field core::int? b = let final core::String? #t1 = syn::a in #t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int};

?.语法糖通过条件表达式(条件 ? 表达式 1 : 表达式 2)做了一次判空操作,当对象为空时返回null,不为空时继续访问成员。

  • ??(空感知运算符)

使用示例:

String? a;
var b = a ?? 'c';

中间表示:

static field core::String? a;
static field core::String b = let final core::String? #t1 = syn::a in #t1 == null ?{core::String} "c" : #t1{core::String};

??语法糖和?.语法糖类似,也是通过条件表达式做了一次判空操作,当对象为空时返回提供的默认值,不为空时返回对象。

  • ??=(空感知赋值运算符)

使用示例:

void test() {
  String? a;
  // 作用域有限制,需要在方法中使用
  a ??= 'b';
  // 大致等价源码
  a == null ? a = 'b' : null;
}

中间表示:

static method test() → void {
  core::String? a;
  a == null ?{core::String} a = "b" : null;
  a{core::String} == null ?{core::String?} a = "b" : null;
}

??=语法糖通过判断对象是否为空决定是否要给对象赋值。

  • ..(级联运算符)

使用示例:

void test() {
  List<String> l = [];
  var l1 = l
    ..add('a')
    ..[0] = 'b'
    ..length;
  // 大致等价源码
  l.add('a');
  l[0] = 'b';
  l.length;
  var l2 = l;
}

中间表示:

static method test() → void {
  core::List l = core::_GrowableList::•(0);
  core::List l1 = let final core::List #t1 = l in block {
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1.{core::List::add}("a"){(core::String) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
    #t1.{core::List::length}{core::int};
  } =>#t1;
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] l.{core::List::add}("a"){(core::String) → void};
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] l.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
  l.{core::List::length}{core::int};
  core::List l2 = l;
}

..语法糖通过let表达式创建一个临时变量并赋值为,然后在代码块中依次调用方法,最后返回临时变量。

  • ?..(空感知级联运算符)

使用示例:

void test() {
  List<String>? l;
  var l1 = l
    ?..add('a')
    ..[0] = 'b'
    ..length;
}

中间表示:

static method test() → void {
  core::List? l;
  core::List? l1 = let final core::List? #t1 = l in #t1 == null ?{core::List?} null : block {
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1{core::List}.{core::List::add}("a"){(core::String) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1{core::List}.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
    #t1{core::List}.{core::List::length}{core::int};
  } =>#t1;
}

?..语法糖和..语法糖相比,主要区别在于执行代码块之前通过条件表达式做了一次判空操作。

  • ...(扩展操作符)

使用示例:

void test() {
  List<String> l = ['a', 'b'];
  var l1 = [...l, 'c'];
  // 大致等价源码
  var l2 = List.of(l);
  l2.add('c');
}

中间表示:

static method test() → void {
  core::List l = core::_GrowableList::_literal2("a", "b");
  core::List l1 = block {
    final core::List #t1 = core::List::of(l);
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1.{core::List::add}{Invariant}("c"){(core::String) → void};
  } =>#t1;
  core::List l2 = core::List::of(l);
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] l2.{core::List::add}("c"){(core::String) → void};
}

...语法糖通过List.of方法从已有数组中创建一个新的数组,然后调用add方法添加其他元素。如果语法糖用于Map,和用于数组时所做的操作类似。

  • ...?(空感知扩展操作符)

使用示例:

void test() {
  List<String>? l;
  var l1 = [...?l, 'c'];
}

中间表示:

static method test() → void {
  core::List? l;
  core::List l1 = block {
    final core::List #t1 = core::_GrowableList::•(0);
    final core::Iterable? #t2 = l;
    if(!(#t2 == null))
      [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1.{core::List::addAll}{Invariant}(#t2{core::Iterable}){(core::Iterable) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List] #t1.{core::List::add}{Invariant}("c"){(core::String) → void};
  } =>#t1;
}

...?语法糖和...语法糖相比,主要区别在于增加了临时变量用于判断已有数组是否为空,如果为空则不处理已有数组,不为空则通过addAll方法添加已有数组。

2. 循环(for-in)

  • for-in

使用示例:

void test() {
  List<String> params = [];
  for (var param in params) {}
  // 大致等价源码
  Iterator<String> iterator = params.iterator;
  for (; iterator.moveNext();) {
    String param = iterator.current;
  }
}

中间表示:

static method test() → void {
  core::List params = core::_GrowableList::•(0);
  {
    synthesized core::Iterator :sync-for-iterator = params.{core::Iterable::iterator}{core::Iterator};
    for (; :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
      core::String param = :sync-for-iterator.{core::Iterator::current}{core::String};
      {}
    }
  }
  core::Iterator iterator = params.{core::Iterable::iterator}{core::Iterator};
  for (; iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
    core::String param = iterator.{core::Iterator::current}{core::String};
  }
}

for-in语法糖是由标准的for循环Iterator实现的。每次循环调用moveNext方法会迭代到下一个元素,通过current方法获取当前迭代的元素。如果已经迭代结束,moveNext方法会返回false结束循环。

3. 函数/方法(=>)

  • =>(箭头函数)

使用示例:

bool test() => true;
// 大致等价源码
bool test1() {
  return true;
}

中间表示:

static method test() → core::bool
  return true;
static method test1() → core::bool {
  return true;
}

=>语法糖补全了return,同时因为是单行函数,所以不加{}

4. 关键字(await for)

  • await for

使用示例:

void test() async {
  var server = await HttpServer.bind(
    InternetAddress.anyIPv4,
    8080,
  );
  await for (HttpRequest request in server) {}
  // 大致等价源码
  var stream = server;
  StreamIterator<HttpRequest>? iterator = StreamIterator<HttpRequest>(stream);
  try {
    while (await iterator.moveNext()) {
      var request = iterator.current;
    }
  } finally {
    if (iterator != null) {
      await iterator.cancel();
    }
  }
}

中间表示:

static method test() → void async /* futureValueType= void */ {
  _ht::HttpServer server = await _ht::HttpServer::bind(io::InternetAddress::anyIPv4, 8080);
  {
    synthesized _ht::HttpServer :stream = server;
    synthesized asy::_StreamIterator<_ht::HttpRequest>? :for-iterator = new asy::_StreamIterator::•<_ht::HttpRequest>(:stream);
    try
      while (let dynamic #t1 = asy::_asyncStarMoveNextHelper(:stream) in await :for-iterator.{asy::_StreamIterator::moveNext}(){() → asy::Future}) {
        _ht::HttpRequest request = :for-iterator.{asy::_StreamIterator::current}{_ht::HttpRequest};
        {}
      }
    finally
      if(!(:for-iterator.{asy::_StreamIterator::_subscription}{asy::StreamSubscription<_ht::HttpRequest>?} == null))
        await :for-iterator.{asy::_StreamIterator::cancel}(){() → asy::Future};
  }
  _ht::HttpServer stream = server;
  asy::StreamIterator<_ht::HttpRequest>? iterator = asy::StreamIterator::•<_ht::HttpRequest>(stream);
  try {
    while (await iterator{asy::StreamIterator<_ht::HttpRequest>}.{asy::StreamIterator::moveNext}(){() → asy::Future}) {
      _ht::HttpRequest request = iterator{asy::StreamIterator<_ht::HttpRequest>}.{asy::StreamIterator::current}{_ht::HttpRequest};
    }
  }
  finally {
    if(!(iterator{asy::StreamIterator<_ht::HttpRequest>} == null)) {
      await iterator{asy::StreamIterator<_ht::HttpRequest>}.{asy::StreamIterator::cancel}(){() → asy::Future};
    }
  }
}

await for语法糖用于处理流(Stream),在服务端开发中比较常见,以上使用示例是对HTTP请求进行处理。从中间表示可见,该语法糖先用HttpServer对象创建流迭代器StreamIterator,然后开启一个while循环迭代流的值。

这里简单讲讲流的迭代原理,如果不感兴趣可以略过。如果一个Future对象有await关键字修饰但又一直不调用complete方法,那么Future对象就会一直处于未完成的状态,程序会被阻塞无法继续往下执行。while循环中的判断语句就是利用这个实现对流的迭代,具体实现源码位于_StreamIterator类。

_StreamIterator类中的moveNext方法:

Future<bool> moveNext() {
  var subscription = _subscription;
  if (subscription != null) {
    if (_hasValue) {
      var future = new _Future<bool>();
      _stateData = future;
      _hasValue = false;
      subscription.resume();
      return future;
    }
    throw new StateError("Already waiting for next.");
  }
  return _initializeOrDone();
}

StreamIterator对象首次调用moveNext方法时,因为还未订阅流,所以会执行_initializeOrDone方法。如果已经订阅且当前有值(不是在等待下一个值)则恢复被暂停的订阅并返回一个新的Future对象,这时因为while循环中的判断语句有await关键字修饰,程序将被阻塞,直到下一个值的到来(在_onData方法中接收处理)。

_StreamIterator类中的_initializeOrDone方法:

Future<bool> _initializeOrDone() {
  assert(_subscription == null);
  var stateData = _stateData;
  if (stateData != null) {
    Stream<T> stream = stateData as dynamic;
    var future = new _Future<bool>();
    _stateData = future;
    // The `listen` call may invoke user code, and it might try to emit
    // events.
    // We ignore data events during `listen`, but error or done events
    // are used to asynchronously complete the future and set `_stateData`
    // to null.
    // This ensures that we do no other user-code callbacks during `listen`
    // than the `onListen` itself. If that code manages to call `moveNext`
    // again on this iterator, then we will get here and fail when the
    // `_stateData` is a future instead of a stream.
    var subscription = stream.listen(_onData,
        onError: _onError, onDone: _onDone, cancelOnError: true);
    if (_stateData != null) {
      _subscription = subscription;
    }
    return future;
  }
  return Future._falseFuture;
}

_initializeOrDone方法的作用是订阅流,监听流的各个事件,当有新的值时会调用_onData方法。返回Future对象的作用和moveNext方法一样,是为了阻塞程序。

_StreamIterator类中的_onData方法:

void _onData(T data) {
  // Ignore events sent during the `listen` call
  // (which can happen if misusing synchronous broadcast stream controllers),
  // or after `cancel` or `done` (for *really* misbehaving streams).
  if (_subscription == null) return;
  _Future<bool> moveNextFuture = _stateData as dynamic;
  _stateData = data;
  _hasValue = true;
  moveNextFuture._complete(true);
  if (_hasValue) _subscription?.pause();
}

_stateData变量指向的是moveNext方法或initializeOrDone方法返回的Future对象,新的值到来后_stateData变量改为指向新的值,后续可以通过current方法获取。Future对象调用complete方法结束while循环中的判断语句阻塞,同时订阅对象调用pause方法暂停订阅直到循环体执行结束再次调用moveNext方法恢复订阅,如此往复循环,只要流不关闭,可以一直迭代处理。

从以上流的迭代原理可知,如果有并发要求则不能在循环体内阻塞程序,不然会导致流的迭代被阻塞,实测确实如此,所以在处理HTTP请求时请尽量使用异步。

最后

如果这篇文章对你有所帮助,请不要吝啬你的点赞加星,谢谢~

你可能感兴趣的:(Dart,开发记录,dart)