Dart - 语法糖(持续更新)

本文介绍了Dart语言中的几种语法糖,包括操作符?.、??、??=、..、?..、...、...?的中间表示形式,以及for-in循环、箭头函数和awaitfor关键字的工作原理。通过对Dart代码的序列化和反编译,帮助读者深入理解这些语法糖的实现和用途。
摘要由CSDN通过智能技术生成


前言

通过将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都没找到定义的源码,没办法直接开启调试,结果原来是文件名缩写。

screenshot1

关键源码(位于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<core::String> l = core::_GrowableList::•<core::String>(0);
  core::List<core::String> l1 = let final core::List<core::String> #t1 = l in block {
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::add}("a"){(core::String) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #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<library dart:core::String>] l.{core::List::add}("a"){(core::String) → void};
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] l.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
  l.{core::List::length}{core::int};
  core::List<core::String> l2 = l;
}

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

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

使用示例:

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

中间表示:

static method test() → void {
  core::List<core::String>? l;
  core::List<core::String>? l1 = let final core::List<core::String>? #t1 = l in #t1 == null ?{core::List<core::String>?} null : block {
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1{core::List<core::String>}.{core::List::add}("a"){(core::String) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1{core::List<core::String>}.{core::List::[]=}(0, "b"){(core::int, core::String) → void};
    #t1{core::List<core::String>}.{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<core::String> l = core::_GrowableList::_literal2<core::String>("a", "b");
  core::List<core::String> l1 = block {
    final core::List<core::String> #t1 = core::List::of<core::String>(l);
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::add}{Invariant}("c"){(core::String) → void};
  } =>#t1;
  core::List<core::String> l2 = core::List::of<core::String>(l);
  [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] 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<core::String>? l;
  core::List<core::String> l1 = block {
    final core::List<core::String> #t1 = core::_GrowableList::•<core::String>(0);
    final core::Iterable<core::String>? #t2 = l;
    if(!(#t2 == null))
      [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #t1.{core::List::addAll}{Invariant}(#t2{core::Iterable<core::String>}){(core::Iterable<core::String>) → void};
    [@vm.call-site-attributes.metadata=receiverType:library dart:core::List<library dart:core::String>] #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<core::String> params = core::_GrowableList::•<core::String>(0);
  {
    synthesized core::Iterator<core::String> :sync-for-iterator = params.{core::Iterable::iterator}{core::Iterator<core::String>};
    for (; :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
      core::String param = :sync-for-iterator.{core::Iterator::current}{core::String};
      {}
    }
  }
  core::Iterator<core::String> iterator = params.{core::Iterable::iterator}{core::Iterator<core::String>};
  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<core::bool>}) {
        _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<dynamic>};
  }
  _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<core::bool>}) {
      _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<dynamic>};
    }
  }
}

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请求时请尽量使用异步。

最后

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Dart-sass和node-sass都是用于编译Sass代码的工具。Dart-sass是由Dart语言编写的,而node-sass是由Node.js编写的。它们都可以将Sass代码编译成CSS代码,并提供了许多功能,如变量、嵌套、混合等。Dart-sass相对于node-sass来说,速度更快,但是需要安装Dart环境。而node-sass则可以直接在Node.js环境下使用。 ### 回答2: Dart-Sass和Node-Sass是两种常用的Sass编译器。 Dart-Sass是一款基于Dart语言开发的Sass编译器,由Sass核心开发者Hampton Catlin和其他贡献者共同开发和维护。Dart-Sass是官方推荐的Sass编译器,支持最新的Sass语法和功能,如@forward、CSS模块以及其他的一些更新。 Node-Sass是一款基于Node.js平台开发的Sass编译器,由Hugo Giraudel和Andrew Nesbitt共同开发和维护。Node-Sass是最早出现的Sass编译器之一,得到了广泛的使用和认可。它支持较早版本的Sass语法和功能,并且能够提供更好的性能让开发者可以更快地编译Sass文件。 两款编译器的主要区别在于他们的运行平台,Dart-Sass运行于Dart平台上,而Node-Sass运行于Node.js平台上。此外,Dart-Sass更加符合最新的Sass语法规范,更能提供更好的性能和处理能力,因此已被官方推荐为首选的Sass编译器。而Node-Sass则更注重与Node.js的兼容性和能够兼容更早的Sass语法版本。 总的来说,选择哪一款Sass编译器应该根据项目需要和开发者的喜好而定。如果注重使用最新的Sass语法和功能,以及更好的性能,可以选择Dart-Sass。而如果需要通用性更强的编译器,并且更能兼容早期的Sass语法版本,可以选择Node-Sass。 ### 回答3: Dart-sass和node-sass都是Sass编译器,它们可以将Sass或者Scss代码编译成CSS代码。Sass是一种CSS预处理器,它使用类似于编程语言的语法来定义CSS样式,让CSS的编写更加快捷、直观。而Dart-sass和node-sass则是Sass语言的解析器,在编译Sass代码时可以实现一些高级功能,如使用变量、函数、嵌套等。 Dart-sass使用Dart语言进行编写,其最明显的特点是速度快,比node-sass快得多,且支持最新的Sass语法特性。另外,Dart-sass的错误信息也更加明显和容易理解。但是,Dart-sass的代码数量比较多,对于初学者来说可能会有一点陌生。 node-sass是一款基于Node.js的Sass编译器,它是由C++实现的,性能也非常好。在安装和使用上比较简单,不需要学习新的语言的语法。但是,相比Dart-sass,node-sass对于Sass 3.5语法的支持不太好,而且在错误提示方面可能不如Dart-sass。 对于哪种Sass编译器更好,在很大程度上取决于个人情况和需求。如果对于速度要求比较高,或者需要使用新的Sass语法特性,那么Dart-sass比较适合。如果希望使用方便、错误提示较为清晰,且不需要使用最新的Sass语法特性,那么node-sass可能比较适合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值