Appearance
代码优化基础
代码优化的核心就是如何让代码以最低消耗成本运行,从而安全的达到运行的目的。本文提及的代码优化,是指Dart语言层面的优化。
这样的优化,很多层面在于开发者的逻辑思考和代码优化。结合对Dart运行原理的深入了解,进一步减少运行开销。在这边文章中,我们简单提及一些设计模式,更多的,需要开发者对程序设计基础深入学习,来不断优化。
减少运算次数
避免不必要的计算,尽量减少循环次数、条件语句的判断和重复的计算。可以使用缓存结果、使用位运算来替代乘法和除法等方式来优化计算过程。
使用短路运算符:短路运算符(&&和||)能够根据第一个条件的结果决定是否计算第二个条件。当第一个条件已经能够确定整个表达式的结果时,后续条件将不会被计算。因此,将最有可能为假的条件放在第一个判断位置可以避免不必要的计算。例如:
dart
final List<int>? list = [];
if (list != null && list.isNotEmpty) {
// 避免在list为null时计算list.length
}
短路运算符其实就是提升代码运行的条件,尽早切断向下运行的可能,避免不必要运算。 减少运算的方法有很多,除了短路运算符,还有下面的优化方法,需要开发者进一步了解:
- 计算结果缓存
- 提前返回
- 提前退出
- 惰性计算
- Debounce(延迟计算)
对象复用和缓存
通过复用和缓存对象来减少内存分配和垃圾回收的开销,从而提高代码的性能和效率。在dart中,可以使用dart:collection包中的ReactiveWidgetPool来创建一个对象池。对象池是一种维护对象实例的集合,可以从池中获取对象以复用,而不是每次需要时都创建新对象。这样可以减少垃圾回收的开销并提高内存使用效率
dart
import 'dart:collection';
class ObjectPool<T> {
final Queue<T> _pool = Queue<T>();
final int _maxSize;
ObjectPool(this._maxSize);
T acquire() {
if (_pool.isNotEmpty) {
return _pool.removeFirst();
} else {
return createInstance();
}
}
void release(T instance) {
if (_pool.length < _maxSize) {
_pool.add(instance);
}
}
T createInstance() {
// 在这里创建新的对象实例
// 例如:
// return SomeClass();
throw UnimplementedError('createInstance method needs to be implemented');
}
}
使用上述的对象池,当他们不在需要时你可以轻易的释放对象。
dart
class SomeClass {
// ...
}
void main() {
final pool = ObjectPool<SomeClass>(5);
// 获取对象
final obj1 = pool.acquire();
final obj2 = pool.acquire();
// 使用对象
// ...
// 释放对象
pool.release(obj1);
pool.release(obj2);
}
对象复用的方法有很多,最经常用的还有单例模式,后面的优化详解中,我们对单例的应用场景也会做一个介绍,因为它极为重要。
避免使用全局变量
全局变量可能会导致性能下降。尽量使用局部变量,或者将全局变量封装在类的内部。而且,全局变量使得代码的依赖关系变得不明确,难以理解和维护。当多个函数或模块直接或间接地依赖于同一个全局变量时,修改该变量可能会产生意想不到的副作用,增加调试和维护的难度。
弱引用
在Dart中,弱引用(Weak References)是一种特殊类型的引用,它允许对象在没有被强引用时被垃圾回收器自动回收。相比于强引用,弱引用不会阻止被引用对象的垃圾回收。 弱引用在Dart中,是极为有用的一种缓存技术,可以大大避免很多内存泄露的问题。
当需要缓存对象但又不想让这些对象影响垃圾回收时,可以使用弱引用缓存。如果一个对象没有被强引用引用,它可以自动被回收,并释放占用的资源。这可以用来实现缓存中的对象自动过期或基于某些条件的自动清理。
弱引用可用于实现观察者模式中的监听器。当一个监听器被创建时,将对象的弱引用作为监听者注册到对象的事件或状态改变的通知列表中。如果监听者没有被其他地方强引用,它将自动从监听列表中移除,避免无效的通知或内存泄漏。
虽然通过使用弱引用,可以在需要时自动回收没有强引用的对象,提高内存利用率和应用程序的性能。但需要注意的是,弱引用可能会导致对象过早地被回收,需要根据实际情况谨慎使用。 有关于弱引用的详细介绍,可以参考:官方文档,我们会在后面的章节中详细说明。
尽可能使用使用const或者final
使用const或final可以将对象或变量声明为不可变的,即其值在初始化后不能被修改。这有助于提高代码的可靠性和安全性,避免了不必要的错误和副作用。在多线程环境下,不可变对象是线程安全的,不会存在因并发修改而导致的竞态条件。此外最重要的是,编译器可以根据const或final关键字的存在进行优化。Dart编译器可以对其进行常量折叠(constant folding)和静态优化(static optimization)。此外,使用const关键字还可以利用常量池(constant pool)减少内存占用。 Flutter中常用的Widget类型,如Text、Container等,都是不可变的,使用const关键字可以确保它们在widget树中的比较和重建时进行高效的绘制和渲染。后面的文章中,我们会提到const构造函数
使用 final 关键字声明的变量是只读的,即一旦初始化后就无法再修改它们的值。因此,final 变量没有相应的 set(JS中的Setter) 方法。
dart
class Person {
final String name;
Person(this.name);
}
void main() {
final person = Person('John');
print(person.name); // 输出:John
// 无法通过 Setter 方法修改 final 变量的值
// person.name = 'Jane'; // 错误!无法修改 final 变量的值
}
使用列表推导式
列表推导式(List comprehension)是一种在 Dart 中快速创建列表的简洁语法。它允许您通过对一个或多个可迭代对象(例如列表、集合或字符串)应用变换或条件逻辑来创建新的列表。 列表推导式的基本语法如下:
[expression for element in iterable]
其中,expression 是一个用于从 iterable 的每个 element 进行变换或条件逻辑的表达式。通过迭代 iterable 中的每个 element,列表推导式会根据 expression 的计算结果创建一个新的列表。最后,toList() 方法可以用于将推导出的结果转换为一个列表。 以下是一些使用列表推导式的示例:
dart
// 示例1:创建一个包含1到5的平方的列表
List<int> squaredList = [for (var i in [1, 2, 3, 4, 5]) i * i];
print(squaredList); // 输出:[1, 4, 9, 16, 25]
// 示例2:从一个字符串列表中筛选包含字母 'a' 的字符串
List<String> names = ['Alice', 'Bob', 'Charlie', 'Dave'];
List<String> namesWithA = [for (var name in names) if (name.contains('a')) name];
print(namesWithA); // 输出:['Alice', 'Charlie']
// 示例3:使用嵌套推导式生成二维列表
List<List<int>> grid = [
for (var i in [1, 2, 3])
[for (var j in [1, 2, 3]) i * j]
];
print(grid); // 输出:[[1, 2, 3], [2, 4, 6], [3, 6, 9]]
使用列表推导式来创建了不同类型的列表。示例1中,对于给定的整数数组,通过平方运算创建了一个新的整数列表。示例2中,可以根据条件逻辑筛选出包含字母 "a" 的字符串列表。示例3中,使用嵌套的列表推导式创建了一个二维列表。当然,列表推导式不适用于所有情况,对于某些复杂的场景,可能需要使用传统的循环和条件语句来实现。
列表推导式是如何达到性能优化的?
减少了不必要的变量声明和赋值操作:通过使用列表推导式,可以避免创建临时变量并使用循环进行迭代和赋值的过程,从而减少了不必要的操作
可以将多个变换和过滤逻辑合并为单个表达式:列表推导式允许在一个表达式中同时完成多个变换和过滤操作,从而减少了迭代和处理的次数,提高了效率。
异步防止UI阻塞
在Dart中,异步操作通常会阻塞UI线程,导致界面的响应性差。为了防止UI阻塞,可以使用Future、Stream、Isolate等异步操作来处理。组件渲染时,还要学会使用FutureBuilder等异步组件。我们将在后续的异步优化中,详细说明。
以上的提案,都是优化性能的基础方案,需要结合具体的场景和需求来选择合适的方案。后面的文章中,我们也会不断的来说明,从而让大家更加了解Dart的优化方案。以及什么Dos和什么Donts。