不可变 vs 可变:对比机制的本质区别

一、核心区别:浅对比 vs 深对比

关键概念

// 不可变对象:可以用引用对比(浅对比)
final a = 'Hello';
final b = 'Hello';
a == b // true(值对比)
identical(a, b) // 可能 true(引用对比,字符串池优化)

// 可变对象:必须用值对比(深对比)
final list1 = [1, 2, 3];
final list2 = [1, 2, 3];
list1 == list2 // true(值对比,需要遍历)
identical(list1, list2) // false(不同对象)

二、不可变 Widget 的对比优势

场景 1:const Widget 的极致优化

// 不可变 + const:编译时创建,全局共享
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Hello'), // Widget A
        const Text('Hello'), // Widget B
      ],
    );
  }
}

// 内存中的实际情况
const textA = Text('Hello'); // 地址:0x1000
const textB = Text('Hello'); // 地址:0x1000(同一个对象!)

print(identical(textA, textB)); // true

// Flutter 的对比
if (identical(oldWidget, newWidget)) {
  // ⚡ 引用相同,直接跳过!O(1) 操作
  return; // 不需要对比任何属性
}

性能对比

// ✅ const Widget:引用对比
identical(widget1, widget2) // 1 次指针比较,纳秒级

// ❌ 非 const Widget:需要对比属性
widget1.text == widget2.text &&
widget1.style == widget2.style &&
widget1.textAlign == widget2.textAlign &&
// ... 对比 N 个属性,微秒级

场景 2:不可变对象的安全假设

// 不可变 Widget:可以安全地缓存和比较
class ImmutableWidget extends StatelessWidget {
  final String title; // final = 不可变
  final int count;
  
  const ImmutableWidget({required this.title, required this.count});
  
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true; // ⚡ 快速路径
    return other is ImmutableWidget &&
           title == other.title &&  // 只需对比值
           count == other.count;
  }
}

// Flutter 的对比逻辑
Widget oldWidget = ImmutableWidget(title: 'A', count: 1);
Widget newWidget = ImmutableWidget(title: 'A', count: 1);

// 第一步:引用对比(最快)
if (identical(oldWidget, newWidget)) {
  return; // ⚡ 跳过
}

// 第二步:值对比(较快,因为不可变)
if (oldWidget == newWidget) {
  // ✅ 值相同,跳过重建
  // 因为不可变,可以确信内部状态也没变
}

三、可变对象的对比困境

问题:可变对象无法信任

// ❌ 假设 Widget 可变
class MutableWidget extends Widget {
  String title; // 可变!
  List<String> items; // 可变!
  
  MutableWidget({required this.title, required this.items});
  
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    
    // 问题 1:即使引用相同,内容可能已经变了!
    if (identical(this, other)) {
      // ⚠️ 不能直接返回 true,因为 title 可能被修改了
      // 必须检查所有字段
    }
    
    return other is MutableWidget &&
           title == other.title && // 简单值对比
           _deepEquals(items, other.items); // ❌ 需要深度对比!
  }
  
  // 深度对比:昂贵的操作
  bool _deepEquals(List a, List b) {
    if (a.length != b.length) return false;
    for (int i = 0; i < a.length; i++) {
      if (a[i] != b[i]) return false; // 遍历所有元素
    }
    return true;
  }
}

// 使用场景
final widget = MutableWidget(title: 'A', items: ['1', '2', '3']);

// 问题:外部可能修改了内容
widget.title = 'B'; // 😱 修改了!
widget.items.add('4'); // 😱 修改了!

// Flutter 无法知道是否需要重建
// 必须每次都深度对比所有属性

性能灾难

// 可变 Widget 的对比成本
class MutableComplexWidget {
  String title;
  List<Item> items; // 假设有 1000 个元素
  Map<String, dynamic> config; // 假设有 100 个键值对
  CustomObject data; // 嵌套对象
  
  @override
  bool operator ==(Object other) {
    return other is MutableComplexWidget &&
           title == other.title && // O(1)
           _compareList(items, other.items) && // O(n) = O(1000)
           _compareMap(config, other.config) && // O(m) = O(100)
           _compareObject(data, other.data); // O(?) 递归对比
  }
  
  // 总成本:O(1000 + 100 + ...) 每次重建都要执行!
}

// 不可变 Widget 的对比成本
class ImmutableComplexWidget {
  final String title;
  final List<Item> items;
  final Map<String, dynamic> config;
  final CustomObject data;
  
  const ImmutableComplexWidget(...);
  
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true; // ⚡ O(1) 快速路径
    
    // 因为不可变,可以安全地对比引用
    return other is ImmutableComplexWidget &&
           title == other.title && // O(1)
           identical(items, other.items) && // ⚡ O(1) 引用对比!
           identical(config, other.config) && // ⚡ O(1)
           identical(data, other.data); // ⚡ O(1)
  }
  
  // 总成本:O(1) 或 O(字段数量),远小于深度对比
}

四、"对比属性"的两种含义

1. 不可变 Widget:浅对比(引用对比)

class ImmutableWidget {
  final String title;
  final List<int> numbers; // 不可变引用
  
  const ImmutableWidget({required this.title, required this.numbers});
  
  @override
  bool operator ==(Object other) {
    return other is ImmutableWidget &&
           title == other.title && // 字符串值对比(O(n),但 n 很小)
           identical(numbers, other.numbers); // ⚡ 引用对比(O(1))
           // 不需要对比 numbers 的每个元素!
  }
}

// 使用
final list1 = [1, 2, 3];
final widget1 = ImmutableWidget(title: 'A', numbers: list1);
final widget2 = ImmutableWidget(title: 'A', numbers: list1); // 同一个 list

widget1 == widget2 // true
// 对比过程:
// 1. title == title ✓ (字符串对比)
// 2. identical(list1, list1) ✓ (引用对比,O(1))

2. 可变 Widget:深对比(值对比)

class MutableWidget {
  String title;
  List<int> numbers; // 可变引用
  
  MutableWidget({required this.title, required this.numbers});
  
  @override
  bool operator ==(Object other) {
    if (other is! MutableWidget) return false;
    
    // ❌ 不能用引用对比,因为内容可能变了
    // if (identical(numbers, other.numbers)) return true; // 不安全!
    
    // 必须深度对比
    if (numbers.length != other.numbers.length) return false;
    for (int i = 0; i < numbers.length; i++) {
      if (numbers[i] != other.numbers[i]) return false; // O(n)
    }
    
    return title == other.title;
  }
}

// 使用
final list = [1, 2, 3];
final widget1 = MutableWidget(title: 'A', numbers: list);
final widget2 = MutableWidget(title: 'A', numbers: list); // 同一个 list

// 问题:即使是同一个 list,也不能信任
list.add(4); // 😱 list 被修改了!

// 必须每次都遍历对比
widget1 == widget2 // 需要遍历 list 的所有元素

五、实际案例对比

案例:列表 Widget

// ✅ 不可变设计
class ImmutableListWidget extends StatelessWidget {
  final List<String> items; // final = 不可变引用
  
  const ImmutableListWidget({required this.items});
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) => Text(items[index]),
    );
  }
  
  @override
  bool operator ==(Object other) {
    return other is ImmutableListWidget &&
           identical(items, other.items); // ⚡ O(1) 引用对比
  }
}

// 使用
final items = ['A', 'B', 'C'];
final widget1 = ImmutableListWidget(items: items);
final widget2 = ImmutableListWidget(items: items);

// Flutter 的对比
identical(widget1.items, widget2.items) // true,O(1)
// 不需要遍历 items 的每个元素!

// 如果要更新列表
final newItems = [...items, 'D']; // 创建新列表
final widget3 = ImmutableListWidget(items: newItems);
identical(widget1.items, widget3.items) // false,知道需要重建
// ❌ 可变设计
class MutableListWidget extends Widget {
  List<String> items; // 可变
  
  MutableListWidget({required this.items});
  
  @override
  bool operator ==(Object other) {
    if (other is! MutableListWidget) return false;
    
    // ❌ 不能用引用对比
    // 必须深度对比每个元素
    if (items.length != other.items.length) return false;
    for (int i = 0; i < items.length; i++) {
      if (items[i] != other.items[i]) return false; // O(n)
    }
    return true;
  }
}

// 使用
final items = ['A', 'B', 'C'];
final widget1 = MutableListWidget(items: items);

// 问题:items 可能被修改
items.add('D'); // 😱 修改了原列表

// Flutter 无法知道是否需要重建
// 必须每次都遍历对比

六、Flutter 实际的优化策略

Flutter 的 Widget 对比实现

// Flutter 源码(简化版)
class Widget {
  const Widget({this.key});
  
  final Key? key;
  
  // Flutter 默认不重写 ==
  // 使用 Object 的默认实现:引用对比
  @override
  bool operator ==(Object other) => identical(this, other);
  
  @override
  int get hashCode => identityHashCode(this);
}

// Element 的对比逻辑
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType &&
         oldWidget.key == newWidget.key;
  // 注意:不对比 Widget 的其他字段!
}

关键理解:Flutter 默认只对比 runtimeTypekey,不对比字段值!

// 实际行为
final widget1 = Text('Hello');
final widget2 = Text('Hello');

widget1 == widget2 // false(不同对象)
Widget.canUpdate(widget1, widget2) // true(类型和 key 相同)

// Flutter 会:
// 1. 调用 build() 重建
// 2. 但可能复用 RenderObject(底层优化)

const 的特殊优化

// const Widget:编译时创建,全局唯一
const widget1 = Text('Hello');
const widget2 = Text('Hello');

identical(widget1, widget2) // true(同一个对象!)

// Flutter 的对比
if (identical(oldWidget, newWidget)) {
  // ⚡ 直接跳过,连 build() 都不调用
  return;
}

七、总结

不可变 vs 可变的对比成本

对比类型 不可变 Widget 可变 Widget
引用对比 ✅ O(1) 安全可靠 ❌ 不安全(内容可能变)
字段对比 ✅ O(字段数) 浅对比 ❌ O(所有元素) 深对比
const 优化 ✅ 编译时创建,全局共享 ❌ 无法使用 const
缓存策略 ✅ 可以安全缓存 ❌ 缓存不可靠

关键区别

// 不可变:可以信任引用
final list = [1, 2, 3];
final widget1 = ImmutableWidget(items: list);
final widget2 = ImmutableWidget(items: list);

identical(widget1.items, widget2.items) // true
// ✅ 可以确信内容相同,因为不可变

// 可变:不能信任引用
final list = [1, 2, 3];
final widget1 = MutableWidget(items: list);
list.add(4); // 😱 修改了
final widget2 = MutableWidget(items: list);

identical(widget1.items, widget2.items) // true
// ❌ 但内容已经不同了!必须深度对比

核心理解

  • 不可变 = 可以用引用对比 = O(1) = 快
  • 可变 = 必须深度对比 = O(n) = 慢
  • const = 全局共享 = identical = 最快

这就是为什么 Flutter 坚持 Widget 不可变的根本原因!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容