热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

带你深入Dart解析一个有趣的引用和编译实验

带你深入Dart解析一个有趣的引用和编译实验-本篇主要通过一个简单例子,讨论一下Dart代码里一个有趣的现象。我们都知道Dart里一切都是对象,就连基础类型int、double、

本篇主要通过一个简单例子,讨论一下 Dart 代码里一个有趣的现象。

我们都知道 Dart 里一切都是对象,就连基础类型 intdoublebool 也都是 class

当我们对于 intdouble 这些 class 进行的 +-*\ 等操作时,其实是执行了这个 classoperator 操作符的操作, 然后返回了新的 num 对象。

对于这些 operator 操作最终会通过 VM 去进行实现返回,而本质上 dart 代码也只是文本,需要最终编译成二进制去运行。

以下例子基于 dart 2.12.3 测试

那这里想要讨论什么呢?

首先我们看一段代码,如下代码所示,可以看到:

  • 首先我们定义了一个叫 idxint 型参数;
  • 然后在 for 循环里添加了三个 InkWell 可点击控件;
  • 最后在 onTap 里面将 idx 打印出来;
class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR",];
  @override
  Widget build(BuildContext context) {
    List cOntents= [];
    int idx = 0;
    for (var imgUrl in images) {
      contents.add(InkWell(
          onTap: () {
            print("######## $idx");
          },
          child: Container(
            height: 100,
            width: 100,
            color: Colors.red,
            child: Text(imgUrl),
          )));
      idx++;
    }
    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Column(
        children: [
          ...contents,
        ],
      )));
  }
}

  • 问题来了,你觉得点击这三个 InkWell 打印出来的会是什么结果?

  • 答案是打印出来的都是 3。

为什么呢?让我们看这段代码编译后的逻辑,如下所示代码,可以看到上述代码编译后, print 函数里指向的永远是 idx 这个 int* 指针,当我们点击时,最终打印出来的都是最后的 idx 的值

    @#C475
    method build(fra2::BuildContext* context) → fra2::Widget* {
      core::List* cOntents= core::_GrowableList::•(0);
      core::int* idx = 0;
      {
        core::Iterator* :sync-for-iterator = this.{main::MyHomePage::images}.{core::Iterable::iterator};
        for (; :sync-for-iterator.{core::Iterator::moveNext}(); ) {
          core::String* imgUrl = :sync-for-iterator.{core::Iterator::current};
          {
            [@vm.call-site-attributes.metadata=receiverType:dart.core::List*] contents.{core::List::add}(new ink5::InkWell::•(onTap: () → Null {
              core::print("######## ${idx}");
            }, child: new con7::Container::•(height: 100.0, width: 100.0, color: #C40086, $creationLocationd_0dea112b090073317d4: #C66610), $creationLocationd_0dea112b090073317d4: #C66614));
            idx = idx.{core::num::+}(1);
          }
        }
      }

那如果我们需要打印出来的是每个 InkWell 自己的 index 呢?

如下代码所示,我们在 for 循环里增加了一个 index 参数,把每次 idx 都赋值给 index ,这样点击打印出来的结果,就会是点击对应的 index

class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR",];
  @override
  Widget build(BuildContext context) {
    List cOntents= [];
    int idx = 0;
    for (var imgUrl in images) {
      int index = idx;
      contents.add(InkWell(
          onTap: () {
            print("######## $index");
          },
          child: Container(
            height: 100,
            width: 100,
            color: Colors.red,
            child: Text(imgUrl),
          )));
      idx++;
    }
    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Column(
        children: [
          ...contents,
        ],
      )));
  }
}

为什么呢?

让我们看新编译出来的代码,如下所示,可以看到对了 core::int* index = idx; 这段代码,然后回忆下前面所说的,Dart 里基本类型都是对象,而 operator 操作符运算后返回新的对象。

这样就等于用 index 把每次的操作到保存下来,而 print 打印的自然就是每次被保存下来的 idx

    @#C475
    method build(fra2::BuildContext* context) → fra2::Widget* {
      core::List* cOntents= core::_GrowableList::•(0);
      core::int* idx = 0;
      {
        core::Iterator* :sync-for-iterator = this.{main::MyHomePage::images}.{core::Iterable::iterator};
        for (; :sync-for-iterator.{core::Iterator::moveNext}(); ) {
          core::String* imgUrl = :sync-for-iterator.{core::Iterator::current};
          {
            core::int* index = idx;
            [@vm.call-site-attributes.metadata=receiverType:dart.core::List*] contents.{core::List::add}(new ink5::InkWell::•(onTap: () → Null {
              core::print("######## ${index}");
            }, child: new con7::Container::•(height: 100.0, width: 100.0, color: #C40086, $creationLocationd_0dea112b090073317d4: #C66610), $creationLocationd_0dea112b090073317d4: #C66614));
            idx = idx.{core::num::+}(1);
          }
        }
      }

那再来个不一样的写法

如下代码所示,把 InkWell 放到一个 getItem 函数里返回,然后 index 通过函数参数传递进来,可以看到运行后的结果,也是点击对应 InkWell 打印对应的 index

class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR",];
  @override
  Widget build(BuildContext context) {
    List cOntents= [];
    int idx = 0;
    getItem(int index, String imgUrl) {
      return InkWell(
          onTap: () {
            print("######## $index");
          },
          child: Container(
            height: 100,
            width: 100,
            color: Colors.red,
            child: Text(imgUrl)));
    }
    for (var imgUrl in images) {
      contents.add(getItem(idx, imgUrl));
      idx++;
    }
    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Column(
        children: [
          ...contents,
        ],
      )));
  }
}

为什么呢?

我们继续看编译后的代码,如下代码所示,其实就是每次的 idx 都通过 getItem.call(idx)getItemindex 引用,然后下次又再次传递一个对应的 idx 进去,原理其实和上面的情况一样,所以每次点击也会打印对应的 index

    @#C475
    method build(fra2::BuildContext* context) → fra2::Widget* {
      core::List* cOntents= core::_GrowableList::•(0);
      core::int* idx = 0;
      function getItem(core::int* index) → ink5::InkWell* {
        return new ink5::InkWell::•(onTap: () → Null {
          core::print("######## ${index}");
        }, child: new con7::Container::•(height: 100.0, width: 100.0, color: #C40086, $creationLocationd_0dea112b090073317d4: #C66610), $creationLocationd_0dea112b090073317d4: #C66614);
      }
      {
        core::Iterator* :sync-for-iterator = this.{main::MyHomePage::images}.{core::Iterable::iterator};
        for (; :sync-for-iterator.{core::Iterator::moveNext}(); ) {
          core::String* imgUrl = :sync-for-iterator.{core::Iterator::current};
          {
            [@vm.call-site-attributes.metadata=receiverType:dart.core::List*] contents.{core::List::add}([@vm.call-site-attributes.metadata=receiverType:library package:flutter/src/material/ink_well.dart::InkWell* Function(dart.core::int*)*] getItem.call(idx));
            idx = idx.{core::num::+}(1);
          }
        }
      }
      

最后我们再换种写法。

如下代码所示,直接用最基本的 for 循环添加 InkWell 并打印 idx ,结果会怎么样呢?

class MyHomePage extends StatelessWidget {
  var images = [ "RRR","RRR", "RRR"];

  @override
  Widget build(BuildContext context) {
    List cOntents= [];
    for (int idx = 0; idx print("######## $idx");
          },
          child: Container(
            height: 100,
            width: 100,
            color: Colors.red,
            child: Text(images[idx]),
          )));
    }
    return Scaffold(
        appBar: AppBar(),
        body: Center(
            child: Column(
          children: [
            ...contents,
          ],
        )));
  }
}

答案就是:点击对应 InkWell 打印对应的 index

为什么呢?

我们继续看编译后的代码,可以看到都是打印的 idx ,为什么这样就可以正常呢?

这里最大的不同就是idx 被声明的位置不同

    @#C475
    method build(fra2::BuildContext* context) → fra2::Widget* {
      core::List* cOntents= core::_GrowableList::•(0);
      for (core::int* idx = 0; idx.{core::num::<}(this.{main::MyHomePage::images}.{core::List::length}); idx = idx.{core::num::+}(1)) {
        [@vm.call-site-attributes.metadata=receiverType:dart.core::List*] contents.{core::List::add}(new ink5::InkWell::•(onTap: () → Null {
          core::print("######## ${idx}");
        }, child: new con7::Container::•(height: 100.0, width: 100.0, color: #C40086, child: new text::Text::•(this.{main::MyHomePage::images}.{core::List::[]}(idx), $creationLocationd_0dea112b090073317d4: #C66607), $creationLocationd_0dea112b090073317d4: #C66613), $creationLocationd_0dea112b090073317d4: #C66617));
      }

那这时候我们重新调整下,idx 放到 for 外面,点击测试会发现,打印的结果又都是 3

class MyHomePage extends StatelessWidget {
  var images = [ "RRR", "RRR","RRR"];

  @override
  Widget build(BuildContext context) {
    List cOntents= [];
    int idx = 0;
    for (; idx 

这是为什么呢?

看编译后的代码,唯一不同的就是 core::int* idx 的声明位置,那原因究竟是什么呢?

    @#C475
    method build(fra2::BuildContext* context) → fra2::Widget* {
      core::List* cOntents= core::_GrowableList::•(0);
      core::int* idx = 0;
      for (; idx.{core::num::<}(this.{main::MyHomePage::images}.{core::List::length}); idx = idx.{core::num::+}(1)) {
        [@vm.call-site-attributes.metadata=receiverType:dart.core::List*] contents.{core::List::add}(new ink5::InkWell::•(onTap: () → Null {
          core::print("######## ${idx}");
        }, child: new con7::Container::•(height: 100.0, width: 100.0, color: #C40086, child: new text::Text::•(this.{main::MyHomePage::images}.{core::List::[]}(idx), $creationLocationd_0dea112b090073317d4: #C66607), $creationLocationd_0dea112b090073317d4: #C66613), $creationLocationd_0dea112b090073317d4: #C66617));
      }

因为 onTap 是在点击后才输出参数的,而对于 for (core::int* idx = 0; 来说,idx 的作用域是在 for 循环之内,所以编译后在 onTap 内要有对应持有一个值,来保存需要输出的结果。

而对于 for 循环外定义的 core::int* idx , 循环内的所有 onTap 都可以指向它这个地址,所以导致点击时都输出了同一个 idx 的值。

至于为什么会有这样的逻辑,在深入的运行时逻辑就没有去探索了(懒),推测应该是编译后的二进制文件在运行时,针对循环外的参数和循环内的参数优化有关系

理论上,应该是属于变量捕获:

  • 对于全局变量,不会捕获,通过全局变量访问。
  • 对于局部变量,自动变量将会捕获,且是值传递。

最后,如果你也想查看 dill 内容,可以通过 mac 下的 xxd 命令:

xxd /Users/xxxxxxx/workspace/flutter-wrok/flutter_app_test/.dart_tool/flutter_build/bf7ed8e7e7b3e64f28f0af8a89a29ca9/app.dill

也可以通过 dump_kernel.dart (在完整版 dart-sdk/Users/guoshuyu/workspace/dart-sdk/pkg/vm 目录下)执行如下命令,生成 app.dill.txt 查看,比如你可以查看 finalconst 编译后的区别。

dart dump_kernel.dart /Users/xxxxxxx/workspace/flutter-wrok/flutter_app_test/.dart_tool/flutter_build/bf7ed8e7e7b3e64f28f0af8a89a29ca9/app.dill /Users/xxxxxxx/workspace/flutter-wrok/flutter_app_test/.dart_tool/flutter_build/bf7ed8e7e7b3e64f28f0af8a89a29ca9/app.dill.txt

推荐阅读
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 在探讨如何在Android的TextView中实现多彩文字与多样化字体效果时,本文提供了一种不依赖HTML技术的解决方案。通过使用SpannableString和相关的Span类,开发者可以轻松地为文本添加丰富的样式和颜色,从而提升用户体验。文章详细介绍了实现过程中的关键步骤和技术细节,帮助开发者快速掌握这一技巧。 ... [详细]
  • 深入解析 Android TextView 中 getImeActionLabel() 方法的使用与代码示例 ... [详细]
  • 开发笔记:深入解析Android自定义控件——Button的72种变形技巧
    开发笔记:深入解析Android自定义控件——Button的72种变形技巧 ... [详细]
  • Spring Boot 实战(一):基础的CRUD操作详解
    在《Spring Boot 实战(一)》中,详细介绍了基础的CRUD操作,涵盖创建、读取、更新和删除等核心功能,适合初学者快速掌握Spring Boot框架的应用开发技巧。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 在《ChartData类详解》一文中,我们将深入探讨 MPAndroidChart 中的 ChartData 类。本文将详细介绍如何设置图表颜色(Setting Colors)以及如何格式化数据值(Formatting Data Values),通过 ValueFormatter 的使用来提升图表的可读性和美观度。此外,我们还将介绍一些高级配置选项,帮助开发者更好地定制和优化图表展示效果。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 本文介绍了如何在iOS平台上使用GLSL着色器将YV12格式的视频帧数据转换为RGB格式,并展示了转换后的图像效果。通过详细的技术实现步骤和代码示例,读者可以轻松掌握这一过程,适用于需要进行视频处理的应用开发。 ... [详细]
  • HBase Java API 进阶:过滤器详解与应用实例
    本文详细探讨了HBase 1.2.6版本中Java API的高级应用,重点介绍了过滤器的使用方法和实际案例。首先,文章对几种常见的HBase过滤器进行了概述,包括列前缀过滤器(ColumnPrefixFilter)和时间戳过滤器(TimestampsFilter)。此外,还详细讲解了分页过滤器(PageFilter)的实现原理及其在大数据查询中的应用场景。通过具体的代码示例,读者可以更好地理解和掌握这些过滤器的使用技巧,从而提高数据处理的效率和灵活性。 ... [详细]
  • 本次发布的Qt音乐播放器2.0版本在用户界面方面进行了细致优化,提升了整体的视觉效果和用户体验。尽管核心功能与1.0版本保持一致,但界面的改进使得操作更加直观便捷,为用户带来了更为流畅的使用体验。此外,我们还对部分细节进行了微调,以确保软件的稳定性和性能得到进一步提升。 ... [详细]
author-avatar
最棒小小丫_635
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有