在 Java 反射详解 一文中,我们知道反射是一种计算机处理方式,是程序可以访问、检测和修改它本身状态或行为的一种能力
。换一个角度说,反射可以细分为自省——程序在运行时决定自身结构的能力,以及自我修正——程序在运行时改变自身的能力。Dart 的反射基于 mirror 概念,它指的是反映其他对象的对象,并且目前只支持自省,不支持自我修改。
Mirror
Example
main() { |
class Simple { |
|
控制台输出为
Symbol("==") >>> MethodMirror on '==' |
分类
在官方 API 页面可以看到所有的 Mirror 类型:dart:mirrors library。Mirror 的主要类型如下
ClassMirror:Dart 类的反射类型
InstanceMirror:Dart 实例的反射类型
ClosureMirror: 闭包的反射类型
DeclarationMirror:类属性的反射类型
IsolateMirror:Isolate 的反射类型
MethodMirror:Dart 方法(包括函数、构造函数、getter/setter 函数)的反射类型
通过dart:mirrors
包内顶层函数reflecClass
获得类的“镜像”的实例,该实例的instanceMembers
属性如下
Map<Symbol, MethodMirror> get instanceMembers; |
由控制台输出结果可以看到,对于普通字段(属性),除自身外还列出了以“=”结尾的 setter 字段,对于不提供 setter 的final
字段则只出现一次。
使用staticMembers
将列出所有的静态字段
cm.staticMembers.forEach((key, value) => print('$key >>> $value')); |
输出如下
Symbol("staticField") >>> Instance of '_SyntheticAccessor' |
可以发现父类静态成员没有出现在列表中,这是因为静态属性不会被继承、不能被ChildClass
调用。
Symbol
Symbol
表示使用 Dart 的 mirror API 反射得到的实例类型,位于dart:core
包
part of dart.core; |
源码
通过 ClassMirror 的源码,可以大概看出 Dart 语言关于反射的设计思想以及对外提供的 API
abstract class ClassMirror implements TypeMirror, ObjectMirror { |
影响
在 Java 中,当开发者多次(10w 次以上)访问、修改某一属性时,使用反射的成本会比正常访问高很多,同时会让private
修饰符失去作用。在 Dart 中,反射的影响主要在于,编译器使用tree shaking
的过程确定应用真正运行时使用的代码,以减少程序的大小。但是使用反射将使tree shaking
失效,因为任何代码都有可能被使用,由此严重影响应用的启动时间和内存占用。
解决👆一问题的有效方法是,通过代码生成执行反射。为了“告知”编译器使用反射的代码和方式,开发者可以使用dart:reflectable
库,通过特定元数据注解反射代码。reflectable
另一个影响在于最小化,其表示对下载到 Web 浏览器的源程序进行压缩的过程。在最小化过程中,源代码使用的名称在编译代码中被压缩成了短名称。这一过程会对反射带来不良影响,因为最小化之后,原来表示声明的名称的字符串,不再对应程序中的实际名称。
为了解决这一问题, Dart 反射使用 symbol 而非字符串作为 key,symbol 会被执行最小化的程序minifier
识别并使用与标识符同样的压缩方式。这也是上面的输出中出现Symbol(...)
的原因。开发者也可以通过 MirrorSystem 提供的static String getName(Symbol symbol)
方法获得非最小化名称字符串。
小结
目前来看 Dart 还不算一门“足够完善”的语言,比如反射机制的不完全、文档教程匮乏等等,相信随着 Flutter 的发展,这门语言的发展会更加地好。关于 Dart 反射的知识全部来自 Gilad Bracha 所著《Dart 编程语言》,不知道是不是翻译的问题,写得不够明晰,看得也是一头雾水。希望有朝一日,更加掌握 Dart 的反射机制,再写一篇《Dart 反射机制详解》的文章 😄。