Flutter 中 const 使用

語言: CN / TW / HK

在檢視 Flutter Widget 的原始碼的時候,常常會遇到 const 這個關鍵字。

1)帶有 const 關鍵字的 TextStyle

TextStyle effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
複製程式碼

2)帶有 const 的全域性變數

const Color _kLightThemeHighlightColor = Color(0x66BCBCBC);
複製程式碼

3)帶有 const 關鍵字的建構函式

const Text.rich(
    this.textSpan, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
  }) : assert(
         textSpan != null,
         'A non-null TextSpan must be provided to a Text.rich widget.',
       ),
       data = null,
       super(key: key);
複製程式碼
  1. 在方法形參中的 const 關鍵字
Flow({
    Key key,
    @required this.delegate,
    List<Widget> children = const <Widget>[],
  }) : assert(delegate != null),
       super(key: key, children: RepaintBoundary.wrapAll(children));
複製程式碼

5)...

官方的程式碼 const 隨處可見,自己寫的程式碼找不到它的影子。

任何事物的存在必有其意義

當然我也不推崇為了用而用,但是官方大把使用,事情肯定不會這麼簡單,在某方面肯定是有其益處的。

自己也簡單分析了下用的少的原因:

  • 沒去了解
  • 沒去思考
  • 沒去使用

所以本文的目的就是,瞭解使用 const,以及知道為什麼?

const

看到它的第一眼,就會想到 C 語言中的 指標常量和常量指標,老犯難了。但是在 Dart 裡我們不需要明面上跟指標打交道,所以不用怕。

對於 const 的使用,我們可以簡單歸耐成 3 種場景:

  • const 用在 = 左邊:定義常量
  • const 用在 = 右邊:建立常量
  • const 建構函式:建立常量

const 用在 = 左邊

對於 const 用在=左邊,這個比較容易理解,使用關鍵字 const 修飾變量表示該變數為 編譯時常量

const 在宣告變數時可以省略變數的型別, double, int 等。 必須在宣告變數時賦值,一旦賦值就不允許修改,而宣告值一定要是編譯時常數

什麼是編譯時常數?

編譯時值就要確定下來

  • 數值、字串、其它的 const 變數

    const bar = 1000000; // 直接賦值 
    const b = 'hello';
    const double atm = 1.01325 * bar;
    複製程式碼
  • 表示式,表示式的所有值都是編譯時可知的。

    const a = 1;
    const b = a > 1 ? 2 : 1;
    複製程式碼
  • 集合或物件 常量集合或者 const建構函式建立的物件

    const a = const [1,2,3];
    const b = ConstObj(2);
    複製程式碼

    對於const建構函式下面會進行詳解

const 用在 = 右邊

當const用在=右邊,其作用是 修飾值,它意味著物件的整個深度狀態可以在編譯時完全確定,並且物件將被凍結並且完全不可變。

一般用於修飾集合,它要求兩點:

  • 集合的元素必須是遞迴的編譯時常數。

    var c = 2;
    //ERROR, 集合元素必須是編譯時常數。
    var a = const [c,2,3];
    複製程式碼
  • 不允許對集合做任何改變。

    const a = const [1,2,3];
    // ERROR, 不允許修改。
    a[1] = 2;
    複製程式碼

那麼對於這兩點我們做個小測驗:

/// 測驗 1
var a = [1,2,3]
a[0] = 3
print(a); 

/// 測驗 2 
const a = [1,2,3];
a[0] = 3;
print(a);

/// 測驗 3
var a = const [1,2,3];
a[0] = 3;
print(a);
複製程式碼

如果你想驗證自己答案,請對這 3 個小測驗分別在 dartpad.dev 執行下。

寫到這裡,個人覺得最讓人困惑的是 const 建構函式建立物件,之前我的理解是物件是在執行時期生成的,為什麼可以用 const?

Dart const 常量建構函式

一般物件是在執行時期生成,所以常量建構函式的條件還是比較苛刻的。

  • 常量建構函式需以 const 關鍵字修飾
  • const 建構函式必須用於成員變數都是 final 的類
  • 構建常量例項必須使用定義的常量建構函式
  • 如果例項化時不加const修飾符,即使呼叫的是常量建構函式,例項化的物件也不是常量例項

比如定義一個 Point 類:

class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}
複製程式碼

常量建構函式需以 const 關鍵字修飾

如下程式碼定義一個const物件,但是呼叫的構造方法不是 const 修飾的,則會報 Cannot invoke a non-'const' constructor where a const expression is expected. 錯誤

void main() {
  const point = Point(1, 2); // 報錯
}

class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
}
複製程式碼

const 建構函式必須用於成員變數都是 final 的類

如下程式碼中成員變數x為非final,會報 Constructor is marked 'const' so all fields must be final. 的錯誤

void main() {
  const point = Point(1, 2); // 報錯
}

class Point {
  int x;
  final int y;
  const Point(this.x, this.y);
}
複製程式碼

構建常量例項必須使用定義的常量建構函式

如下程式碼,定義一個const物件,但是呼叫的卻是非常量建構函式,會報Cannot invoke a non-'const' constructor where a const expression is expected.錯誤

void main() {
  var point = const Point(1, 2); // 報錯
}
 
class Point {
  int x;
  int y;
  Point(this.x, this.y); // 非const建構函式
}
複製程式碼

如果例項化時不加 const 修飾符,即使呼叫的是常量建構函式,例項化的物件也不是常量例項

如下程式碼,用常量建構函式構造一個物件,但是未用const修飾,那麼該物件就不是const常量,其值可以再改變

void main() {
  var point = Point(1, 2); // 呼叫常量建構函式,但是未定義成常量
  print(point.toString());
  point = Point(10, 20); // 可以重新賦值,說明定義的變數為非常量
  print(point.toString());
}
 
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
  
  String toString() {
    return 'Point(${x}, ${y})';
  }
}
複製程式碼

為什麼官方很多地方使用 const ?

  • 具有 const 關鍵字的變數在編譯時初始化,常量值必須在編譯期就確定。

  • 在執行時無法修改常量,無法重新建立。 對於任何給定的常量值,無論常量表達式計算了多少次,都將在記憶體中建立一個物件。常量物件在需要時可以重用,但從不重新建立

  • 對於 Flutter 而言,狀態更新後, build 方法中不會再次初始化。

    build(context) {
        return Row(
            children: <Widget>[
                const Text("Hello"),
                const SizedBox(width: 10),
                const Text("Hello"),
                const SizedBox(width: 10),
                const Text("Can you hear me?"),
            ]
        )
    }
    複製程式碼

    Row 有五個孩子。當我們使用 const 關鍵字建立具有 const 建構函式的類的例項時,這些值在編譯時建立,並且每個唯一值僅在記憶體中儲存一次。前兩個 Text 例項將解析為對記憶體中同一物件的引用,兩個 SizedBox 例項也是如此。如果要新增 const SizedBox(width: 15),則將為該新值建立一個單獨的常量例項。

    同樣也可用使用 new 關鍵字(預設省略)來建立例項而不是使用 const,最終的執行效果也是一樣的,但是如果你想減少你程式執行佔用的記憶體或者想提高程式的效能,那麼使用 const 是比較好的選擇。

  • 常量的不可變性是具有依賴關係

    final size = 12.0;
    const Text(
      "Hello",
      style: TextStyle(
        fontSize: size,    // error
      ),
    )
    複製程式碼

    我們嘗試建立 Text 的常量例項,但是,我們說過 const 建構函式的條件比較苛刻,其成員必須是常量,"hello" 是字面量,可以省略 const 關鍵字。同樣 Dart 也會嘗試將 TextStyle 建立為常量,因為它知道 TextStyle 必須為常量才滿足 const Text() 的呼叫。但是由於 TextStyle 依賴於變數 size, 但 size 不是常數,指導執行時才具有值。所以不滿足 const 建構函式條件報錯。要解決此問題,必須替換 size 為常量或者直接使用數字。

文章到此結束!總結 Dart 之 const 的學習要點:

  • 不可修改
  • 編譯期初始化
  • const 建構函式

更多文章請關注官方微信公眾號:OldBirds