原文 https://ducafecat.com/blog/flutter-interview-questions-with-answers-01
不少朋友不止一次的问我关于 Flutter 面试题是否有推荐。
本节开始陆续整理一些问题,每次 10 道题。
Embedder 嵌入器:作为基础层,它提供了平台特定的集成,使得Flutter能够在不同的系统上运行。
Engine 引擎:使用C++编写,该层管理核心任务,如图形渲染、文本布局和文件/网络操作。
Framework 框架:位于引擎之上,为应用程序开发提供高级类。这包括小部件层,提供了大量的视觉、结构、平台和交互式小部件,渲染层将小部件绘制到画布上,以及提供服务和实用工具的其他几个层。
Flutter Cupertino 和 Material 是 Flutter 框架中的两个设计语言和视觉风格。
Flutter是一个多功能的框架,支持在多种平台上部署:
Flutter 中的 JIT(Just-in-Time)和 AOT(Ahead-of-Time)是两种不同的编译方式,用于将 Flutter 代码转换成可执行的机器代码。
JIT(Just-in-Time)编译:在开发和调试阶段,Flutter 使用 JIT 编译方式。JIT 编译器将 Dart 代码转换为中间代码(IL),然后在运行时动态地将中间代码转换为机器代码。这种编译方式允许热重载(Hot Reload)功能,开发者可以在不重新启动应用程序的情况下即时查看代码更改的结果。JIT 编译器还提供了更快的开发周期和更快的编译时间,但相对而言,生成的代码执行速度可能较慢。
AOT(Ahead-of-Time)编译:在发布到生产环境时,Flutter 使用 AOT 编译方式。AOT 编译器将 Dart 代码预先编译为机器代码,生成二进制文件,无需在运行时进行即时编译。这种编译方式提供了更快的启动时间和更高的执行性能,因为代码已经编译成机器代码,无需再进行运行时的转换。但与 JIT 编译相比,AOT 编译不支持热重载功能,并且可能导致较长的编译时间。
JIT 编译方式适用于开发和调试阶段,提供了更快的开发周期和热重载功能,但执行速度可能较慢。而 AOT 编译方式适用于发布到生产环境,提供了更快的启动时间和更高的执行性能,但不支持热重载功能。在开发过程中,开发者可以充分利用 JIT 编译的便利性和开发速度,而在发布时则可以选择 AOT 编译以获得更好的性能和用户体验。
在 Dart 语言中,final
和 const
是用来声明常量的关键字,但它们有一些不同之处:
final
:final
用于声明一个只能被赋值一次的变量。这意味着一旦变量被赋值后,其值就不能再被修改。final
变量在运行时被初始化,可以根据需要进行延迟初始化。final
变量的值可以是在运行时计算得到的结果,但一旦初始化后,就不能再改变。
例如:
final int x = 5; final String name = 'John'; // 错误的用法,final 变量不能再次赋值 x = 7;
const
:const
用于声明一个编译时常量,这意味着变量的值必须在编译时就已知且不可更改。const
变量在编译时被初始化,可以在运行时之前进行优化。const
变量的值必须是编译时常量,如字面量、常量构造函数创建的对象或其他 const
变量的组合。
例如:
// 正确的用法 const int x = 5; const String name = 'John'; // 错误的用法,const 变量不能再次赋值 x = 7;
final
用于声明运行时常量,其值在运行时初始化且不能更改,而 const
用于声明编译时常量,其值在编译时初始化且不能更改。const
变量的使用更加严格,要求值必须在编译时就已知,因此适用于需要在编译时进行优化和确定的场景。而 final
变量则更适用于需要在运行时确定并且不可更改的常量。
在 Dart 中,有以下几种访问修饰符:
默认访问修饰符(No modifier):如果没有显式地指定访问修饰符,则默认为包内可见(package-private),即同一个包内的其他文件可以访问。
public
:在 Dart 中,默认情况下,所有的成员(变量、函数、类等)都是公开的,即可在任何地方访问。公开成员不使用任何访问修饰符进行标识。
_private
:使用下划线 _
开头的标识符表示私有成员,只能在当前文件中访问。私有成员在其他文件中是不可见的。
例如,下面是一个示例类,演示了访问修饰符的使用:
class Person { String name; // 默认访问修饰符,默认为包内可见 int _age; // 私有成员,只能在当前文件中访问 void sayHello() { print('Hello, $name!'); } int _calculateAge() { // 私有方法,只能在当前文件中访问 // ... } }
在上述示例中,name
是一个默认访问修饰符的成员,可以在同一个包内的其他文件中访问。_age
是一个私有成员,只能在当前文件中访问。sayHello()
是一个公开的方法,可以在任何地方访问。_calculateAge()
是一个私有方法,只能在当前文件中访问。
需要注意的是,Dart 中没有像 Java 那样的 public
和 private
关键字来显式地标识访问修饰符。默认情况下,成员是公开的,使用下划线 _
开头的标识符表示私有成员。
在 Dart 语言中,命名参数(Named Parameters)和可选参数(Optional Parameters)是用于定义函数接受参数的方式。
命名参数:命名参数允许你通过指定参数名称来传递参数值,而不必按照参数定义的顺序传递。使用大括号 {}
包围参数名称,并在函数调用时使用 参数名: 参数值
的形式进行传递。
void printPerson({String name, int age}) { print('Person: $name, $age years old'); } // 使用命名参数调用函数 printPerson(name: 'John', age: 30); printPerson(age: 25, name: 'Alice');
在上述示例中,printPerson
函数接受两个命名参数 name
和 age
。通过使用参数名称来传递参数值,可以不受参数顺序的限制。
可选参数:可选参数允许你定义函数接受可选的参数,可以在函数调用时省略该参数。可选参数分为两种类型:位置参数和命名参数。
位置参数:使用中括号 []
包围参数名称,表示该参数为可选的位置参数。位置参数在函数调用时按照参数定义的顺序进行传递。
void printMessage(String message, [String prefix]) { if (prefix != null) { print('$prefix: $message'); } else { print(message); } } // 使用位置参数调用函数 printMessage('Hello'); // 无前缀 printMessage('World', 'Prefix'); // 带前缀
命名参数:使用大括号 {}
包围参数名称,表示该参数为可选的命名参数。命名参数在函数调用时通过指定参数名称进行传递。
void printPerson(String name, {int age, String address}) { print('Person: $name, $age years old, $address'); } // 使用命名参数调用函数 printPerson('John', age: 30, address: '123 Main St'); printPerson('Alice', address: '456 Park Ave', age: 25);
在上述示例中,printMessage
函数接受一个位置参数 message
和一个可选的位置参数 prefix
。printPerson
函数接受一个位置参数 name
和两个可选的命名参数 age
和 address
。
通过使用命名参数和可选参数,可以使函数的调用更加灵活和可读性更高。可以根据需要选择使用命名参数或可选参数,或者同时使用它们来定义函数接受的参数。
在 Dart 语言中,命名构造函数(Named Constructors)和工厂函数(Factory Constructors)是两种用于创建对象的不同方式,它们有以下区别:
命名构造函数:命名构造函数是在类中定义的特殊构造函数,通过使用类名后跟一个句点和构造函数名称来定义。命名构造函数用于提供不同的构造方式或创建具有特定初始化逻辑的对象。
class Person { String name; int age; // 默认构造函数 Person(this.name, this.age); // 命名构造函数 Person.fromBirthYear(this.name, int birthYear) { age = DateTime.now().year - birthYear; } } // 使用默认构造函数创建对象 var john = Person('John', 30); // 使用命名构造函数创建对象 var alice = Person.fromBirthYear('Alice', 1995);
在上述示例中,Person
类定义了一个默认构造函数和一个命名构造函数 Person.fromBirthYear
。默认构造函数用于直接传递 name
和 age
参数创建对象,而命名构造函数 Person.fromBirthYear
接受 name
和 birthYear
参数,并通过计算得到 age
值。
工厂函数:工厂函数是通过使用 factory
关键字定义的特殊构造函数,用于创建对象的灵活方式。工厂函数可以返回一个新的对象,也可以返回一个已存在的对象。工厂函数通常用于创建单例对象或根据特定条件决定返回哪个对象。
class Logger { String name; static Logger _instance; // 私有构造函数 Logger._internal(this.name); // 工厂函数 factory Logger(String name) { if (_instance == null) { _instance = Logger._internal(name); } return _instance; } } // 使用工厂函数创建对象 var logger1 = Logger('Logger 1'); var logger2 = Logger('Logger 2');
在上述示例中,Logger
类定义了一个工厂函数 Logger
,用于创建 Logger
对象。工厂函数通过判断是否已存在对象,来决定返回一个新的对象或一个已存在的对象。这种方式可以实现单例模式,确保只有一个 Logger
对象被创建。
命名构造函数用于提供不同的构造方式或初始化逻辑,而工厂函数用于提供创建对象的灵活方式,可以返回新的对象或已存在的对象。
面向对象编程(OOP)的四个基本原则是抽象(Abstraction)、封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。这些原则被称为「抽象、封装、继承、多态」(Abstraction, Encapsulation, Inheritance, Polymorphism)或「AEIP原则」。
这些原则是面向对象编程的基础,它们用于指导设计和组织代码的方式,以实现代码的可维护性、可扩展性和重用性。
抽象(Abstraction):抽象是将复杂的现实世界问题简化为适合程序处理的模型。通过抽象,我们可以关注对象的关键特征和行为,忽略其细节。抽象可以通过类、接口和抽象类来实现。
封装(Encapsulation):封装是将数据和操作数据的方法封装在一个单元(类)中,以实现信息隐藏和访问控制。封装通过将相关的数据和方法组织在一起,形成一个独立的模块,并限制外部访问来保护数据的完整性。
继承(Inheritance):继承是通过创建新的类(子类)来继承现有类(父类)的属性和方法。继承可以实现代码的重用和层次化的组织。子类可以继承父类的属性和方法,并可以添加新的属性和方法,或者重写父类的方法。
多态(Polymorphism):多态是指同一个方法可以在不同的对象上具有不同的行为。多态允许使用基类或接口类型的引用来引用具体的子类对象,从而实现动态绑定和灵活的代码扩展。
本节讲了 10 个 Flutter 面试中出现的问题,如果你在面试中遇到奇怪问题可以联系我。
下次再见。
感谢阅读本文
如果有什么建议,请在评论中让我知道。我很乐意改进。
Flutter 优秀插件推荐 https://flutter.ducafecat.com
Flutter 基础篇1 - Dart 语言学习 https://ducafecat.com/course/dart-learn
Flutter 基础篇2 - 快速上手
Flutter 实战1 - Getx Woo 电商APP
Flutter 实战2 - 上架指南 Apple Store、Google Play https://ducafecat.com/course/flutter-upload-apple-google
Flutter 基础篇3 - 仿微信朋友圈 https://ducafecat.com/course/flutter-wechat
Flutter 实战3 - 腾讯 tim 即时通讯开发 https://ducafecat.com/course/flutter-tim
© 猫哥 ducafecat.com
end
Copyright 2023 ducafecat. All rights reserved.
微信: ducafecat, line: ducafecat,京ICP备2021009050号-3