前端面试 · flutter

284 阅读5分钟

记录一下面试遇到的flutter相关问题:

1.Widget 和 element 和 RenderObject 之间的关系

三棵树

首先先了解三棵树,这是我们的核心,需要首先建立一个概念。

Widget 树

我们平时用 Widget 使用声明式的形式写出来的界面,可以理解为 Widget 树,这是要介绍的第一棵树。

RenderObject 树

Flutter 引擎需要把我们写的 Widget 树的信息都渲染到界面上,这样人眼才能看到,跟渲染有关的当然有一颗渲染树 RenderObject tree,这是第二颗树,渲染树节点叫做 RenderObject,这个节点里面处理布局、绘制相关的事情。这两个树的节点并不是一一对应的关系,有些 Widget是要显示的,有些 Widget ,比如那些继承自 StatelessWidget & StatefulWidget 的 Widget 只是将其他 Widget 做一个组合,这些 Widget 本身并不需要显示,因此在 RenderObject 树上并没有相对应的节点。

Element 树

Widget 树是非常不稳定的,动不动就执行 build方法,一旦调用 build 方法意味着这个 Widget 依赖的所有其他 Widget 都会重新创建,如果 Flutter 直接解析 Widget树,将其转化为 RenderObject 树来直接进行渲染,那么将会是一个非常消耗性能的过程,那对应的肯定有一个东西来消化这些变化中的不便,来做cache。因此,这里就有另外一棵树 Element 树。Element 树这一层将 Widget 树的变化(类似 React 虚拟 DOM diff)做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

这三棵树如下图所示,是我们讨论的核心内容。

从上图可以看出,widget 树和 Element 树节点是一一对应关系,每一个 Widget 都会有其对应的 Element,但是 RenderObject 树则不然,只有需要渲染的 Widget 才会有对应的节点。Element 树相当于一个中间层,大管家,它对 Widget 和 RenderObject 都有引用。当 Widget 不断变化的时候,将新 Widget 拿到 Element 来进行对比,看一下和之前保留的 Widget 类型和 Key 是否相同,如果都一样,那完全没有必要重新创建 Element 和 RenderObject,只需要更新里面的一些属性即可,这样可以以最小的开销更新 RenderObject,引擎在解析 RenderObject 的时候,发现只有属性修改了,那么也可以以最小的开销来做渲染。

以上只是引出了非常重要的三棵树和他们之间的关系,简而言之,Widget 树就是配置信息的树,我们平时写代码写的就是这棵树,RenderObject 树是渲染树,负责计算布局,绘制,Flutter 引擎就是根据这棵树来进行渲染的,Element 树作为中间者,管理着将 Widget 生成 RenderObject和一些更新操作。

2.extends implement mixin 之间的关系

2.1 extends

extends只可以单继承,要注意的点是:

  • 子类可以继承父类里面 可见的属性和方法

    • Dart来说,指的是非下划线_开头。
  • 子类调用父类的方法,使用super关键字。

  • 子类不会 继承 父类的构造函数。

class Extends {
  
  void base() {
    print('base');
  }
  
  void log() {
    print('extends');
  }
  
}

class Log extends Extends {
  
  log() {
    print('log');
  }
  
}

void main() {
  Log().base();
  Log().log();
}

输出结果:

base
log

2.2 implements

implementsextends最大的不同就是允许后面接上多个普通或者抽象类,当我们使用B implement A修饰时,那么A中的所有的属性和方法都要在B中实现,无论它原来是抽象方法还是普通方法

也就是说如果我们只想要A中的接口定义,而不想要它的实现,那么就用implements

class Implements {
  
  void base() {
    print('base');
  }
    
  void log() {
    print('extends');
  }
  
}

class Log implements Implements {
  
  base() {
    print('log#base');
  }
  
  log() {
    print('log');
  }
  
}

void main() {
  Log().base();
  Log().log();
}

输出结果:

log#base
log

2.3 mixin

mixin用于修饰类,和abstract类似,该类可以拥有成员变量、普通方法、抽象方法,但是不可以实例化。mixin一般用于描述一种具有某种功能的组块,而某一对象可以拥有多个不同功能的组块。

(1) 最简单

最简单的mixinmixin & with关键字组成。

举个例子,我们有一种能力是 '绘画',而拥有这种能力的是 ‘教师’,那么实现如下:

mixin DrawFunc {
  
  String content = '..';
  
  String what();
    
  void draw() {
    print('I can draw ${what()}');  
  }
  
}

class Teacher with DrawFunc {
  
  String what() => "car";
  
}

void main() {
  Teacher().draw();
}

(2) 限定类型

我们限定了 '绘画' 这种能力只能够用在 '人类' 上面,示例如下:

class Person {}

mixin DrawFunc on Person {
  
  String content = '..';
  
  String what();
    
  void draw() {
    print('I can draw ${what()}');  
  }
  
}

class Teacher extends Person with DrawFunc {
  
  String what() => "car";
  
}

void main() {
  Teacher().draw();
}

当我们在mixin上使用了on关键字,那么mixin只能在那个类的子类上使用,而mixin可以调用那个类的方法。

(3) 多个类型

在 '绘画' 的基础上,我们增加一种新的能力 '唱歌',示例如下:

class Person {}

mixin DrawFunc on Person {
  
  String content = '..';
  
  String what();
    
  void draw() {
    print('I can draw ${what()}');  
  }
  
}

mixin SingFunc on Person {
  
  void sing() {
    print('I can sing');
  }
}

class Teacher extends Person with DrawFunc, SingFunc {
  
  String what() => "car";
  
}

void main() {
  Teacher().draw();
  Teacher().sing();
}

(4) on 的一种复杂变形

关于on还有一种复杂的变形,我们在 '唱歌' 上增加一条约束,要求它必须是在DrawFunc之上:

mixin SingFunc on Person, DrawFunc {
  
  void sing() {
    print('I can sing');
  }
}

那么这时候,虽然Teacher没有extends DrawFunc,但是如下的代码仍然可以编译通过:

class Teacher extends Person with DrawFunc, SingFunc {
  
  String what() => "car";
  
}

而我们交换一下DrawFuncSingFunc的顺序就不行了:

class Teacher extends Person with SingFunc, DrawFunc {
  
  String what() => "car";
  
}

提示信息是:

Error compiling to JavaScript:
main.dart:22:7:
Error: 'Person' doesn't implement 'DrawFunc' so it can't be used with 'SingFunc'.
 - 'Person' is from 'main.dart'.
 - 'DrawFunc' is from 'main.dart'.
 - 'SingFunc' is from 'main.dart'.
class Teacher extends Person with SingFunc, DrawFunc {
      ^
Error: Compilation failed.
复制代码

结论:要满足on的要求,除了使用extends之外,还可以在with列表中,在它之前进行声明。在FlutterWidgetsFlutterBinding中,就涉及到了这一点的运用。

abstract class BindingBase {}

mixin ServicesBinding on BindingBase {}

mixin SchedulerBinding on BindingBase, ServicesBinding {}

mixin RendererBinding on BindingBase, ServicesBinding {}

class WidgetsFlutterBinding extends BindingBase with ServicesBinding, SchedulerBinding, RendererBinding {}