Dart中的const如何提高性能的

325 阅读3分钟

一、起因

在Flutter开发中,官方的Widget使用了使用大量的const修饰符的构造函数,这样做的好处,在于重复使用Widget,不用频繁的构建与销毁Widget。

在Flutter中,会频繁的调用build()方法,每次调用后Flutter会构建新的Widget子树,销毁子树上所有Widget。

对于不用重新构建的Widget,我们可以使用const作为修饰符,构建Widget的 常量实例,就可以重复使用,不至于每次都销毁后,重新构建,消耗性能。这里有个概念常量实例,在下面会详细说到。

二、const与final

在dart中声明常量有两种constfinal,两者的区别

  • const 编译时常量

    只能在编译的时候确定常量值,只能赋值一次,只能赋值常量值

  • final 运行时常量

    可以在运行时赋值,只能赋值一次,可以赋值任意类型

声明常量方式何时赋值赋值类型赋值次数
const编译时常量值一次
final编译时、运行时任意类型一次

三、const使用方式

  • 作为关键字,用于声明常量

    const name = '小明';
    const levels = [1, 2, 3]; 
    
  • 作为修饰符,用于创建常量值

    /// 虽然只赋值一次,但可以修改里面的内容
    final levels = [1, 2, 3];
    levels[0] = 10;			
    
    /// 使用常量修饰符,就不能修改里面的内容
    final levels = const [1, 2, 3];
    levels[0] = 10;		//报错:Unsupported operation: Cannot modify unmodifiable list
    
    /// 看了以上的示例,你可能感觉是脱裤子放屁,多此一举。
    /// 这是只是展示一下修饰符的作用,真正的使用场景是:创建常量实例,重复使用常量实例,提高性能。
    

四、常量值

1、普通常量值

  • 基本常量
    • 数值型
    • 字符串型
    • 包含数值与字符串的表达式
  • 集合常量
    • 包含基本常量类型的List、Map、Set、Records

示例:

const name = '小明';   // 字符串型
const english = 86.0; // 数值型 
const math = 100;     // 数字型号

const desc = '$name 总分:${english + math}';  // 表达式

const person = (name: name, desc: desc);  // Records常量
const scores = [english, math];           // List<num>常量

2、 常量实例

在我们认知中,一个类的实例对象的构建,是在运行过程中,通过构造函数构建的。而使用const修饰的构造函数,并使用const修饰构建的实例对象,该实例对象就称为常量实例,能够在编译时,作为常量值使用。

如何创建常量实例

  • 新建可以构建常量实例的类

    使用const修饰类的构造函数,该类可以构建普通的实例(运行时的类实例),也可以构建常量实例(编译时常量)。

  • 将该类构建为常量实例

    • 使用final赋值常量时,需要再次使用const修饰,才能真正的作为常量实例使用。
    • 使用const赋值常量时,不需要使用const修饰。

示例:

/// 新建一个可构建常量实例的类
class Person {
  final String name;
  final int age;
  const Person({required this.name, required this.age});
}

/// const赋值常量,将该类构建为常量实例
const person1 = Person(name:'xiaoming', age: 18);

/// final赋值常量,需要const修饰,才能将该类构建为常量实例
const person2 = const Person(name:'xiaoming', age: 18);

举例验证

const constPerson = Person(name: 'xiaoming', age: 18);	//常量实例

final finalPerson = Person(name: 'xiaoming', age: 18);	//普通实例

final fianlConstPerson = const Person(name: 'xiaoming', age: 18);	//常量实例

/// const_person 与 final_person 不是一个对象的引用
print(identical(constPerson, finalPerson)); // false

/// const_person 与 fianl_const_person 是一个对象的引用,此为常量实例
print(identical(constPerson, fianlConstPerson)); // true

/// 再构建一个普通的实例,判断是否与之前的普通实例同属于一个对象
final finalPerson1 = Person(name: 'xiaoming', age: 18);	// 普通实例

print(identical(finalPerson, finalPerson1)); // false

上面的示例,直观的展现了:

  • 构建的多个常量实例,都是引用同一个地址,也就是同属于一个对象。
  • 构建的普通实例,不是同属一个对象。

bool identical(Object? a, Object? b)用于判断2个对象,是否同属于一个对象。