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

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

最后

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/47638.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【腾讯云 Cloud Studio 实战训练营】沉浸式体验编写一个博客系统

文章目录 前言项目中技术栈新建工作空间登录(注册)Cloud Studio 账号&#xff1a;进入 Cloud Studio 控制台&#xff1a;配置工作空间参数&#xff1a;确认并创建工作空间&#xff1a;项目搭建 配置nuxt 脚手架运行项目报错信息解决错误脚手架运行预览问题 开启博客代码配置lay…

法大大携手盘子女人坊,以数字化唤醒国风摄影新体验

第三方数据显示&#xff0c;目前&#xff0c;我国共有163万家摄影相关企业&#xff0c;有约1900个从事摄影相关业务的品牌&#xff0c;且预计到2025年艺术摄影市场规模将达到7063.18亿元。艺术摄影行业作为在时代进步、科技发展以及人民生活水平提高的推动下逐渐发展起来的行业…

GPT一键化身「AI助理」——自定义指令功能

最近GPT又更新了一个超实用的功能——自定义指令&#xff0c;启用后&#xff0c;你可以给GPT设置一些固定指令&#xff0c;让它记住或扮演某个角色&#xff0c;比如客服、律师、投资管理师、老师、营养师...... 这样&#xff0c;我们就不再需要每次都要打开新的聊天&#xff0c…

spring-websocket在SpringBoot(包含SpringSecurity)项目中的导入

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f96d;本文内容&#xff1a;spring-websocket在SpringBoot(包含SpringSecurity…

瓴羊Quick BI:可视化大屏界面设计满足企业个性需求

大数据技术成为现阶段企业缩短与竞争对手之间差距的重要抓手&#xff0c;依托以瓴羊Quick BI为代表的工具开展内部数据处理分析工作&#xff0c;也成为诸多企业持续获取竞争优势的必由之路。早年间国内企业倾向于使用进口BI工具&#xff0c;但随着瓴羊Quick BI等一众国内数据处…

milvus: 专为向量查询与检索设计的向量数据库

1. 什么是milvus&#xff1f; milvus docs milvus release Milvus的目标是&#xff1a;store, index, and manage massive embedding vectors generated by deep neural networks and other machine learning (ML) models. Milvus 向量数据库专为向量查询与检索设计&#xf…

无涯教程-jQuery - trigger( event, data )方法函数

trigger(event&#xff0c;[data])方法在每个匹配的元素上触发一个事件。 触发事件不仅限于基于浏览器的事件&#xff0c;还可以触发向bind注册的自定义事件。 trigger( event, [data] ) - 语法 selector.trigger( event, [data] ) 这是此方法使用的所有参数的描述- event…

Numpy

系列文章目录 第一章 python数据挖掘基础环境安装和使用 第二章 Matplotlib 文章目录 系列文章目录一、介绍ndarray优势属性使用 二、ndarray的形状三、ndarray的类型四、创建数组的时候指定类型五、基本操作生成数组的方法生成0和1的数组从现有数组生成生成固定范围的数组生成…

【算法与数据结构】222、LeetCode完全二叉树的节点个数

文章目录 一、题目二、一般遍历解法三、利用完全二叉树性质四、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、一般遍历解法 思路分析&#xff1a;利用层序遍历&#xff0c;然后用num记录节点数量。其他的例如…

(学习笔记-IP)IP协议相关技术

DNS 我们在上网的时候&#xff0c;通常使用的方式是域名&#xff0c;而不是IP地址&#xff0c;因为域名方便人类记忆。 那么实现这一技术的就是DNS域名解析器&#xff0c;DNS可以将域名网址自动转换为具体的IP地址。 域名的层级关系 DNS中的域名都是用句点来分隔的&#xff0…

3.5 Bootstrap 输入框组

文章目录 Bootstrap 输入框组基本的输入框组输入框组的大小复选框和单选插件按钮插件带有下拉菜单的按钮分割的下拉菜单按钮 Bootstrap 输入框组 本章将讲解 Bootstrap 支持的另一个特性&#xff0c;输入框组。输入框组扩展自 表单控件。使用输入框组&#xff0c;您可以很容易地…

多租户分缓存处理

多租户redis缓存分租户处理 那么数据库方面已经做到了拦截&#xff0c;但是缓存还是没有分租户&#xff0c;还是通通一个文件夹里&#xff0c; 想实现上图效果&#xff0c;global文件夹里存的是公共缓存。 首先&#xff0c;那么就要规定一个俗称&#xff0c;缓存名字带有globa…

字符串函数介绍应用

字符串 1.前言 C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符串中或者字符数组中。 字符串常量适合于那些对他不做修改的函数。 2.库函数及其模拟实现 2.1 strlen函数 size_t strlen ( const char *…

mysql(四)数据备份

目录 前言 一、概述 二、备份的类型 &#xff08;一&#xff09;物理与逻辑角度 &#xff08;二&#xff09;数据库备份策略角度 三、常见的备份方法 四、完整备份 &#xff08;一&#xff09;打包数据库文件备份 &#xff08;二&#xff09;备份工具备份 五、增量备份 六、操…

uniapp兼容微信小程序和支付宝小程序遇到的坑

1、支付宝不支持v-show 改为v-if。 2、v-html App端和H5端支持 v-html &#xff0c;微信小程序会被转为 rich-text&#xff0c;其他端不支持 v-html。 解决方法&#xff1a;去插件市场找一个支持跨端的富文本组件。 3、导航栏处有背景色延伸至导航栏外 兼容微信小程序和支…

用OpenCV图像处理技巧之白平衡算法(二)

1. 引言 在上一节中我们介绍了白平衡算法的原理&#xff0c;并详细实现了基于白色补丁算法的白平衡实现&#xff0c;本文继续就白平衡的其他算法实现进行展开。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. Gray-world Algorithm 灰色世界算法&#xff08;Gray-wor…

【N32L40X】学习笔记11-ADC规则通道采集+dma数据传输

ADC规则通道转换 概述 支持 1 个 ADC&#xff0c;支持单端输入和差分输入&#xff0c;最多可测量 16 个外部和 3 个内部源。支持 12 位、10 位、8 位、6 位分辨率。ADC 时钟源分为工作时钟源、采样时钟源和计时时钟源 仅可配置 AHB_CLK 作为工作时钟源。可配置 PLL 作为采样时…

【大数据之Flume】三、Flume进阶之Flume Agent 内部原理和拓扑结构

1 Flume事务 2 Flume Agent 内部原理 重要组件&#xff1a; 1、ChannelSelector&#xff08;选择器&#xff09;   ChannelSelector 的作用就是选出 Event 将要被发往哪个 Channel。   &#xff08;1&#xff09;Replicating ChannelSelector&#xff08;复制或副本&#x…

【面试题】芯片中的IR drop现象是什么?

这里是尼德兰的喵芯片面试相关文章,欢迎您的访问! 如果文章对您有所帮助,期待您的点赞收藏,也欢迎您对文中存在的问题和疑惑进行评论 此外,gitee仓库尼德兰的喵 (gjm9999) - Gitee.com与微信公众平台也期待您的访问 让我们一起为芯片前端全栈工程师而努力!!!! 今天突然…