Flutter面试题——面试题整理7

一 面试题汇总

  1. 你能提供一下 SOLID 原则的概述吗?
  2. Dart 中 Object、dynamic 和 var 有何不同?
  3. 什么是 Dart 中 cascade 级联和 extension 扩展运算?
  4. mixin 混入和 interface 接口在 Dart 中有何不同?
  5. Dart 中的空安全是什么?
  6. 你能解释 Dart 中的 Isolate、Event Loop 和 Future 的概念吗?
  7. Flutter 无状态和有状态小部件之间有什么区别,以及 setState() 的作用是什么?
  8. Flutter InheritedWidget 是什么?
  9. 你能解释一下在 Flutter 中 keys 键的作用吗?
  10. 在 Flutter 中,Keys(键)有哪几种类型?

二 面试题解答(仅供参考)

2.1 你能提供一下 SOLID 原则的概述吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SOLID是由五个单独的原则组成,每个原则都关注不同的方面,但它们共同促进了高内聚、低耦合的代码结构。

以下是每个SOLID原则的概述:

1-单一职责原则(Single Responsibility Principle,SRP):
一个类应该只有一个引起它变化的原因。换句话说,一个类应该只负责一项单一的职责。
这样设计的类更容易理解、维护和扩展。

2-开放封闭原则(Open-Closed Principle,OCP):
软件实体(类、模块、函数等)应该对扩展开放,而对修改关闭。
通过使用抽象、接口和多态性,可以在不修改现有代码的情况下扩展系统的功能。

3-里氏替换原则(Liskov Substitution Principle,LSP):
子类必须能够替换其基类并被客户端代码透明地使用,而不会导致意外的行为。
遵循LSP可以确保代码的正确性和一致性。

4-接口隔离原则(Interface Segregation Principle,ISP):
客户端不应该强迫依赖它们不使用的接口。
应该将庞大而臃肿的接口拆分为更小、更具体的接口,以便客户端只需知道它们所需的接口。

5-依赖倒置原则(Dependency Inversion Principle,DIP):
高层模块不应该依赖于低层模块,它们都应该依赖于抽象。
抽象应该依赖于细节,而不是细节依赖于抽象。
这通过依赖注入、控制反转等技术来实现,以提高系统的灵活性和可测试性。

2.2 Dart 中 Object、dynamic 和 var 有何不同?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1-Object是Dart中所有类的基类。
它是一个通用的类型,可以表示任何对象。
所有的Dart对象都可以赋值给Object类型的变量。由于Object是所有类的超类,
因此可以使用Object类型的变量来调用一些通用的方法,如toString()和hashCode()。

2-dynamic是Dart中的一种特殊类型。
使用dynamic类型声明的变量可以在运行时具有任何类型的值。
它被称为动态类型,因为它的类型在编译时不会被静态检查。
这意味着可以对dynamic类型的变量执行任何操作,而编译器不会发出类型错误。
但是,由于缺乏静态类型检查,使用dynamic类型可能会导致类型错误和运行时异常。

3-var是Dart中的一种关键字,用于声明变量而不指定其类型。
编译器会根据变量的初始值推断出其类型,并在编译时进行静态类型检查。
一旦变量的类型被推断出来,它就被视为具有该类型,不能更改为其他类型。
与dynamic不同,var变量在编译时进行类型检查,如果尝试对其执行不兼容的操作,编译器会发出类型错误

2.3 什么是 Dart 中 cascade 级联和 extension 扩展运算?

1
2
3
4
5
6
1-级联运算符(Cascade):
级联运算符(..)允许在同一个对象上执行多个操作,而无需重复引用该对象。
使用级联运算符,可以依次调用同一个对象的多个方法或属性。这在链式调用中特别有用

2-扩展(extension)是一种机制,允许开发人员向现有的类添加新的功能,而无需修改原始类的代码。
它提供了一种在不继承该类的情况下为其添加方法和属性的方式

2.4 mixin 混入和 interface 接口在 Dart 中有何不同?

1
2
3
4
5
6
7
8
9
1-mixin(混入):mixin是一种用于在类中重用代码的机制。
通过使用mixin关键字,可以定义一个包含一组方法和属性的混入类,并将其混入到其他类中。
混入类的成员可以在目标类中被重用,从而实现代码的复用和组合。
一个类可以混入多个混入类,但Dart不支持多继承。混入类不能直接实例化,只能作为其他类的一部分来使用。

2-interface(接口):在Dart中,并没有显式的interface关键字。
相反,每个类都隐式地定义了一个接口。
其他类可以通过implements关键字实现该接口,并保证实现类需要提供接口中定义的所有方法和属性。
这种方式使得类之间可以建立合同关系,并允许多态性

2.5 Dart 中的空安全是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dart的空安全引入了一套类型系统和编译时检查,以帮助开发者在编译时发现和解决潜在的空引用错误。
它通过以下方式提供了更强的代码安全性:

1-非空类型(Non-nullable types):Dart中的变量可以标记为非空类型,表示该变量永远不会为空。
使用非空类型可以在编译时捕获可能的空引用错误。

2-可空类型(Nullable types):Dart中的变量可以标记为可空类型,表示该变量可以为空。
对于可空类型的变量,必须使用特殊的操作符(如?.和!.)来访问其属性或方法,
以确保安全地处理可能为空的情况。

3-后置感叹号操作符(Postfix ! operator):
当开发者确定一个可空类型的变量在某个特定点不为空时,
可以使用后置感叹号操作符!来显式地将其标记为非空,以告诉编译器不再对其进行空值检查。

4-非空断言(Non-null assertion):使用非空断言操作符!可以告诉编译器某个变量在某个点不为空,
类似于后置感叹号操作符,但不同的是,非空断言不会自动将可空类型转换为非空类型,而是在运行时强制断言。

2.6 你能解释 Dart 中的 Isolate、Event Loop 和 Future 的概念吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1-Isolate(隔离区):Isolate 是 Dart 中的并发执行单元。
每个 Isolate 都有自己独立的内存堆,并且可以同时执行自己的代码。
不同的 Isolate 之间是相互独立的,它们之间不能直接共享内存。
Isolate 可以用于执行耗时的计算、并发处理任务和提高应用程序的性能。
Dart 提供了 Isolate API,使得创建和通信多个 Isolate 变得简单。

2-Event Loop(事件循环):
Event Loop 是 Dart 运行时中的一个机制,用于管理和调度异步任务的执行。
它是单线程的,负责处理事件和任务的调度。
Event Loop 会不断地从事件队列中获取事件,如果队列中有任务,就执行任务。
当执行耗时操作时,会将任务转移到 Isolate 中执行,
从而避免阻塞主Event Loop。Event Loop的工作方式确保了Dart代码的非阻塞执行,使得异步编程成为可能。

3-Future(未来):Future 是 Dart 中用于表示异步操作结果的对象。
它表示一个可能在未来完成的值或错误。Future 可以看作是一个占位符,表示异步操作的结果。
当异步操作完成时,Future 可以被解析(resolved)为一个值或一个异常。
通过使用 Future,我们可以编写异步代码,可以注册回调函数来处理操作完成的结果,
或者使用 async/await 语法来编写更简洁的异步代码。

2.7 Flutter 无状态和有状态小部件之间有什么区别,以及 setState() 的作用是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在 Flutter 中,无状态小部件(stateless widget)和有状态小部件(stateful widget)之间有以下区别:

1-无状态小部件(Stateless Widget):

1.1-无状态小部件是不可变的,它们在创建后不会发生变化。
它们的外观和行为仅取决于输入的属性(parameters)。
1.2-无状态小部件通常用于显示静态内容,如文本、图像等,或者用于根据输入属性构建 UI 布局。
1.3-由于无状态小部件是不可变的,当它们的属性发生变化时,它们会被完全重建。

2-有状态小部件(Stateful Widget):

2.1-有状态小部件是可变的,它们在创建后可以发生变化。它们可以包含可变的状态(state)数据。
2.2-有状态小部件通常用于处理用户交互、响应事件和动态更新 UI 等情况。
2.3-有状态小部件通过继承 StatefulWidget 类来创建,并使用单独的 State 类来管理状态数据。

setState() 方法用于在有状态小部件中更新状态并触发 UI 的重新构建。
当状态发生变化时,调用 setState() 方法会通知 Flutter 框架重新构建小部件的 UI,
以反映更新后的状态。

2.8 Flutter InheritedWidget 是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
lutter 中的 InheritedWidget 是一种特殊的小部件(Widget),
它允许在小部件树中共享和传递数据给其子孙小部件,而无需显式地通过构造函数传递数据。

InheritedWidget 的主要特点是它可以将数据在小部件树中向下传递,
并且在数据发生变化时,能够自动通知依赖它的小部件进行更新。
这样可以避免手动管理数据传递和手动触发小部件的重新构建。

InheritedWidget 的工作原理是基于 Dart 中的继承机制。
当一个小部件被标记为 InheritedWidget 并且其数据发生变化时,
Flutter 会自动遍历小部件树,找到依赖该 InheritedWidget 的小部件,并通知它们进行更新。

使用 InheritedWidget 可以方便地实现一些全局或跨多个小部件共享的数据,
例如应用程序的主题、语言设置、认证状态等。

2.9 你能解释一下在 Flutter 中 keys 键的作用吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在 Flutter 中,Keys(键)是一种用于标识小部件的机制,
它们对于在更新小部件树时进行识别和比较非常重要。Keys 提供了以下作用:

1-识别 Widget 小部件:每个小部件都可以关联一个 Key 对象,用于标识自身。
通过将 Key 分配给小部件,可以确保在小部件树中唯一地标识该小部件。

2-有效地更新 Widget 小部件:当 Flutter 重新构建小部件树时,
它会使用新的小部件实例与之前的小部件实例进行比较。
通过使用相同的 Key,Flutter 可以确定哪些小部件是相同的(相同类型且具有相同的 Key),
从而可以有效地更新这些小部件而不是重新创建它们。

3-保留状态:当使用 Key 标识 Widget 小部件时,即使小部件树发生变化,
具有相同 Key 的小部件仍将保留其状态。
这对于在动态列表或小部件重排时保留用户输入或滚动位置等状态非常有用。

尽管 Keys 提供了一些优势,但在大多数情况下,Flutter 可以自动处理 Widget小部件树的更新和重建,
而无需显式地使用 Keys。
只有在特定情况下,如动态列表、Widget 小部件重用或需要保留状态时,才需要使用 Keys。

2.10 在 Flutter 中,Keys(键)有哪几种类型?

1
2
3
4
5
6
7
8
9
10
11
12
在 Flutter 中,Key 有两个主要的子类:LocalKey 和 GlobalKey。

1-LocalKey(局部键):

1.1-ValueKey:使用特定的值作为标识符,可以是数字、字符串或其他可比较的对象。
例如,ValueKey(1) 或 ValueKey('myKey')。
1.2-ObjectKey:使用对象作为标识符,使用对象的引用进行识别和比较。例如ObjectKey(myObject)。
1.3-UniqueKey:生成全局唯一的标识符,用于确保在每次重建时都会创建新的小部件实例。例如,UniqueKey()。

2-GlobalKey(全局键):
GlobalKey 是 LocalKey 的一个特殊子类,用于在整个应用程序中跨小部件树进行引用和识别。
通过 GlobalKey,可以直接访问关联小部件的状态和方法

三 参考

  • 狗哥课堂—Flutter 面试题整理 02