屬性的獨立寫入型別
在 JavaScript 中,API 在儲存傳入的值之前對其進行轉換是非常常見的做法。這在 getter 和 setter 中也經常發生。例如,設想我們有一個類,其中的 setter 在將值存入私有欄位之前總是將其轉換為 number。
jsTryclassThing {#size = 0;getsize () {return this.#size;}setsize (value ) {letnum =Number (value );// Don't allow NaN and stuff.if (!Number .isFinite (num )) {this.#size = 0;return;}this.#size =num ;}}
我們如何在 TypeScript 中為這段 JavaScript 程式碼編寫型別?實際上,我們在這裡不需要做任何特殊處理——TypeScript 可以檢視這段程式碼,在沒有顯式型別的情況下,它能推斷出 size 是一個數字。
問題在於 size 允許你為其賦不僅僅是 number 型別的值。我們可以透過將 size 的型別宣告為 unknown 或 any 來繞過這個問題,如下面的程式碼片段所示。
tsclass Thing {// ...get size(): unknown {return this.#size;}}
但這樣做並不好——unknown 會強制讀取 size 的人進行型別斷言,而 any 則無法捕獲任何錯誤。如果我們確實想要建模這種轉換值的 API,以往版本的 TypeScript 強迫我們在“精確”(使讀取值更容易,但寫入更難)和“寬泛”(使寫入值更容易,但讀取更難)之間做出選擇。
這就是 TypeScript 4.3 允許你分別為屬性的讀取和寫入指定型別的原因。
tsTryclassThing {#size = 0;getsize (): number {return this.#size;}setsize (value : string | number | boolean) {letnum =Number (value );// Don't allow NaN and stuff.if (!Number .isFinite (num )) {this.#size = 0;return;}this.#size =num ;}}
在上面的例子中,我們的 set 訪問器接收一組更寬泛的型別(string、boolean 和 number),但我們的 get 訪問器始終保證它是 number。現在,我們終於可以向這些屬性賦其他型別的值而不會報錯了!
tsTryletthing = newThing ();// Assigning other types to `thing.size` works!thing .size = "hello";thing .size = true;thing .size = 42;// Reading `thing.size` always produces a number!letmySize : number =thing .size ;
當考慮兩個同名屬性如何關聯時,TypeScript 只會使用“讀取”型別(例如上面 get 訪問器上的型別)。“寫入”型別僅在直接寫入屬性時才會被考慮。
請記住,這種模式並不侷限於類。你也可以在物件字面量中編寫具有不同型別的 getter 和 setter。
tsfunction makeThing(): Thing {let size = 0;return {get size(): number {return size;},set size(value: string | number | boolean) {let num = Number(value);// Don't allow NaN and stuff.if (!Number.isFinite(num)) {size = 0;return;}size = num;},};}
事實上,我們已經在介面/物件型別中添加了語法,以支援屬性上不同的讀取/寫入型別。
ts// Now valid!interface Thing {get size(): numberset size(value: number | string | boolean);}
使用不同的型別讀取和寫入屬性的一個限制是,讀取屬性的型別必須可分配給寫入屬性的型別。換句話說,getter 的型別必須可分配給 setter。這確保了一定程度的一致性,使得屬性始終可以賦值給自己。
關於此功能的更多資訊,請檢視 實現此功能的 PR。
override 和 --noImplicitOverride 標誌
在 JavaScript 中擴充套件類時,該語言使得重寫方法非常簡單(一語雙關),但不幸的是,你可能會遇到一些錯誤。
一個主要的錯誤是錯過了重新命名。例如,看看下面的類。
tsclass SomeComponent {show() {// ...}hide() {// ...}}class SpecializedComponent extends SomeComponent {show() {// ...}hide() {// ...}}
SpecializedComponent 是 SomeComponent 的子類,並重寫了 show 和 hide 方法。如果有人決定移除 show 和 hide 並用單個方法替換它們,會發生什麼?
diffclass SomeComponent {- show() {- // ...- }- hide() {- // ...- }+ setVisible(value: boolean) {+ // ...+ }}class SpecializedComponent extends SomeComponent {show() {// ...}hide() {// ...}}
糟糕!我們的 SpecializedComponent 沒有得到更新。現在它只是添加了這兩個無用的 show 和 hide 方法,它們可能永遠不會被呼叫。
這裡部分問題在於使用者無法明確他們是打算新增一個新方法,還是打算重寫一個現有方法。這就是為什麼 TypeScript 4.3 添加了 override 關鍵字。
tsclass SpecializedComponent extends SomeComponent {override show() {// ...}override hide() {// ...}}
當一個方法被標記為 override 時,TypeScript 將始終確保基類中存在同名方法。
tsTryclassSomeComponent {setVisible (value : boolean) {// ...}}classSpecializedComponent extendsSomeComponent {overrideThis member cannot have an 'override' modifier because it is not declared in the base class 'SomeComponent'.4113This member cannot have an 'override' modifier because it is not declared in the base class 'SomeComponent'.() { show }}
這是一個很大的改進,但如果你忘記在一個方法上寫 override,它就沒用了——這也是使用者可能犯的一個大錯。
例如,你可能會在不經意間“踐踏”了基類中存在的方法而不自知。
tsclass Base {someHelperMethod() {// ...}}class Derived extends Base {// Oops! We weren't trying to override here,// we just needed to write a local helper method.someHelperMethod() {// ...}}
這就是為什麼 TypeScript 4.3 還提供了一個新的 noImplicitOverride 標誌。當啟用此選項時,除非顯式使用 override 關鍵字,否則重寫父類的任何方法都會報錯。在最後一個例子中,TypeScript 會在 noImplicitOverride 下報錯,並提示我們可能需要在 Derived 內部重新命名我們的方法。
我們要感謝社群在這裡所做的實現。這些功能的實現工作是由 Wenlu Wang 透過 一個 PR 完成的,儘管由 Paul Cody Johnston 實現僅 override 關鍵字的早期 PR 為方向和討論提供了基礎。我們對他們投入時間開發這些功能表示感謝。
模板字串型別改進
在近期的版本中,TypeScript 引入了一種新的型別結構:模板字串型別。這些型別要麼透過拼接來構造新的字串類型別……
tstype Color = "red" | "blue";type Quantity = "one" | "two";type SeussFish = `${Quantity | Color} fish`;// same as// type SeussFish = "one fish" | "two fish"// | "red fish" | "blue fish";
……要麼匹配其他字串類型別的模式。
tsdeclare let s1: `${number}-${number}-${number}`;declare let s2: `1-2-3`;// Works!s1 = s2;
我們做的第一個更改是關於 TypeScript 何時推斷模板字串型別的。當模板字串被字串字面量類型別上下文型別化時(即當 TypeScript 看到我們將一個模板字串傳給接受字面量型別的東西時),它會嘗試給該表示式一個模板型別。
tsfunction bar(s: string): `hello ${string}` {// Previously an error, now works!return `hello ${s}`;}
這也適用於型別推斷以及 extends string 的型別引數。
tsdeclare let s: string;declare function f<T extends string>(x: T): T;// Previously: string// Now : `hello ${string}`let x2 = f(`hello ${s}`);
這裡的第二個重大變化是,TypeScript 現在可以更好地關聯不同模板字串型別之間的關係,並在它們之間進行推斷。
要看到這一點,請看以下示例程式碼。
tsdeclare let s1: `${number}-${number}-${number}`;declare let s2: `1-2-3`;declare let s3: `${number}-2-3`;s1 = s2;s1 = s3;
當針對像 s2 這樣的字串字面量型別進行檢查時,TypeScript 可以根據字串內容進行匹配,並計算出 s2 在第一次賦值中與 s1 相容;然而,一旦它看到另一個模板字串,它就放棄了。結果,像 s3 賦值給 s1 這樣的操作根本無法工作。
TypeScript 現在確實在做這項工作來證明模板字串的每個部分是否能成功匹配。你現在可以混合使用具有不同替換的模板字串,TypeScript 將能很好地弄清楚它們是否真的相容。
tsdeclare let s1: `${number}-${number}-${number}`;declare let s2: `1-2-3`;declare let s3: `${number}-2-3`;declare let s4: `1-${number}-3`;declare let s5: `1-2-${number}`;declare let s6: `${number}-2-${number}`;// Now *all of these* work!s1 = s2;s1 = s3;s1 = s4;s1 = s5;s1 = s6;
在做這項工作時,我們也確保添加了更好的推斷能力。你可以看到這些功能的實際操作示例。
tsdeclare function foo<V extends string>(arg: `*${V}*`): V;function test<T extends string>(s: string, n: number, b: boolean, t: T) {let x1 = foo("*hello*"); // "hello"let x2 = foo("**hello**"); // "*hello*"let x3 = foo(`*${s}*` as const); // stringlet x4 = foo(`*${n}*` as const); // `${number}`let x5 = foo(`*${b}*` as const); // "true" | "false"let x6 = foo(`*${t}*` as const); // `${T}`let x7 = foo(`**${s}**` as const); // `*${string}*`}
更多資訊,請參閱 利用上下文型別的原始 PR,以及 改進模板型別之間推斷和檢查的 PR。
ECMAScript #private 類元素
TypeScript 4.3 擴充套件了類中哪些元素可以被賦予 #private #名稱,從而使它們在執行時真正變為私有。除了屬性之外,方法和訪問器也可以被賦予私有名稱。
tsclass Foo {#someMethod() {//...}get #someValue() {return 100;}publicMethod() {// These work.// We can access private-named members inside this class.this.#someMethod();return this.#someValue;}}new Foo().#someMethod();// ~~~~~~~~~~~// error!// Property '#someMethod' is not accessible// outside class 'Foo' because it has a private identifier.new Foo().#someValue;// ~~~~~~~~~~// error!// Property '#someValue' is not accessible// outside class 'Foo' because it has a private identifier.
更廣泛地說,靜態成員現在也可以擁有私有名稱。
tsclass Foo {static #someMethod() {// ...}}Foo.#someMethod();// ~~~~~~~~~~~// error!// Property '#someMethod' is not accessible// outside class 'Foo' because it has a private identifier.
此功能是由我們在 Bloomberg 的朋友們編寫的 PR 實現的,由 Titian Cernicova-Dragomir 和 Kubilay Kahveci 編寫,並得到了 Joey Watts、Rob Palmer 和 Tim McClure 的支援和專業建議。我們要向他們所有人表示感謝!
ConstructorParameters 適用於抽象類
在 TypeScript 4.3 中,ConstructorParameters 型別助手現在適用於 abstract 類。
tsabstract class C {constructor(a: string, b: number) {// ...}}// Has the type '[a: string, b: number]'.type CParams = ConstructorParameters<typeof C>;
這歸功於 TypeScript 4.2 中的工作,在該版本中,構造簽名可以被標記為抽象。
tstype MyConstructorOf<T> = {abstract new(...args: any[]): T;}// or using the shorthand syntax:type MyConstructorOf<T> = abstract new (...args: any[]) => T;
你可以在 GitHub 上 檢視該更改的更多詳細資訊。
泛型的上下文收窄
TypeScript 4.3 現在對泛型值包含了一些更智慧的型別收窄邏輯。這使得 TypeScript 能夠接受更多模式,有時甚至能捕捉到錯誤。
作為動機,假設我們正在嘗試編寫一個名為 makeUnique 的函式。它接收一個元素的 Set 或 Array,如果給定一個 Array,它將對該 Array 進行排序並根據某些比較函式移除重複項。最後,它將返回原始集合。
tsfunction makeUnique<T>(collection: Set<T> | T[],comparer: (x: T, y: T) => number): Set<T> | T[] {// Early bail-out if we have a Set.// We assume the elements are already unique.if (collection instanceof Set) {return collection;}// Sort the array, then remove consecutive duplicates.collection.sort(comparer);for (let i = 0; i < collection.length; i++) {let j = i;while (j < collection.length &&comparer(collection[i], collection[j + 1]) === 0) {j++;}collection.splice(i + 1, j - i);}return collection;}
讓我們暫時撇開該函式實現的問題,假設它源於更廣泛應用程式的需求。你可能會注意到的一點是,簽名沒有捕獲 collection 的原始型別。我們可以透過新增一個名為 C 的型別引數來代替我們寫 Set<T> | T[] 的地方來做到這一點。
diff- function makeUnique<T>(collection: Set<T> | T[], comparer: (x: T, y: T) => number): Set<T> | T[]+ function makeUnique<T, C extends Set<T> | T[]>(collection: C, comparer: (x: T, y: T) => number): C
在 TypeScript 4.2 及更早版本中,一旦你嘗試這樣做,就會遇到一堆錯誤。
tsfunction makeUnique<T, C extends Set<T> | T[]>(collection: C,comparer: (x: T, y: T) => number): C {// Early bail-out if we have a Set.// We assume the elements are already unique.if (collection instanceof Set) {return collection;}// Sort the array, then remove consecutive duplicates.collection.sort(comparer);// ~~~~// error: Property 'sort' does not exist on type 'C'.for (let i = 0; i < collection.length; i++) {// ~~~~~~// error: Property 'length' does not exist on type 'C'.let j = i;while (j < collection.length &&comparer(collection[i], collection[j + 1]) === 0) {// ~~~~~~// error: Property 'length' does not exist on type 'C'.// ~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~// error: Element implicitly has an 'any' type because expression of type 'number'// can't be used to index type 'Set<T> | T[]'.j++;}collection.splice(i + 1, j - i);// ~~~~~~// error: Property 'splice' does not exist on type 'C'.}return collection;}
呃,錯誤!為什麼 TypeScript 對我們這麼刻薄?
問題在於,當我們執行 collection instanceof Set 檢查時,我們期望它作為一個型別守衛,將型別從 Set<T> | T[] 收窄為 Set<T> 和 T[](取決於我們在哪個分支);然而,我們處理的不是 Set<T> | T[],我們正在嘗試收窄泛型值 collection,其型別是 C。
這是一個非常微妙的區別,但它很重要。TypeScript 不能直接獲取 C 的約束(即 Set<T> | T[])並對其進行收窄。如果 TypeScript 確實嘗試從 Set<T> | T[] 收窄,它會忘記 collection 在每個分支中也是一個 C,因為沒有簡單的方法來保留該資訊。如果假設 TypeScript 嘗試了這種方法,它會以另一種方式破壞上面的示例。在返回位置,函式期望型別為 C 的值,而我們會在每個分支中得到一個 Set<T> 和一個 T[],TypeScript 會拒絕這樣做。
tsfunction makeUnique<T>(collection: Set<T> | T[],comparer: (x: T, y: T) => number): Set<T> | T[] {// Early bail-out if we have a Set.// We assume the elements are already unique.if (collection instanceof Set) {return collection;// ~~~~~~~~~~// error: Type 'Set<T>' is not assignable to type 'C'.// 'Set<T>' is assignable to the constraint of type 'C', but// 'C' could be instantiated with a different subtype of constraint 'Set<T> | T[]'.}// ...return collection;// ~~~~~~~~~~// error: Type 'T[]' is not assignable to type 'C'.// 'T[]' is assignable to the constraint of type 'C', but// 'C' could be instantiated with a different subtype of constraint 'Set<T> | T[]'.}
那麼 TypeScript 4.3 是如何改變這種情況的呢?基本上,在編寫程式碼的一些關鍵位置,型別系統真正關心的只是型別的約束。例如,當我們寫 collection.length 時,TypeScript 不關心 collection 具有型別 C 的事實,它只關心可用的屬性,這些屬性由約束 T[] | Set<T> 決定。
在這種情況下,TypeScript 會獲取約束的收窄型別,因為它會給你你關心的資料;然而,在任何其他情況下,我們只會嘗試收窄原始泛型型別(通常最終得到原始泛型型別)。
換句話說,基於你如何使用泛型值,TypeScript 會以不同的方式收窄它。最終結果是,整個上面的示例在沒有型別檢查錯誤的情況下編譯透過。
更多詳細資訊,你可以 檢視 GitHub 上的原始 PR。
始終為真的 Promise 檢查
在 strictNullChecks 下,在條件判斷中檢查 Promise 是否為“真值”將觸發錯誤。
tsasync function foo(): Promise<boolean> {return false;}async function bar(): Promise<string> {if (foo()) {// ~~~~~// Error!// This condition will always return true since// this 'Promise<boolean>' appears to always be defined.// Did you forget to use 'await'?return "true";}return "false";}
此更改由 Jack Works 貢獻,我們對他們表示感謝!
static 索引簽名
索引簽名允許我們為值設定比型別顯式宣告更多的屬性。
tsclass Foo {hello = "hello";world = 1234;// This is an index signature:[propName: string]: string | number | undefined;}let instance = new Foo();// Valid assignmentinstance["whatever"] = 42;// Has type 'string | number | undefined'.let x = instance["something"];
到目前為止,索引簽名只能在類的例項端宣告。感謝 Wenlu Wang 的 PR,索引簽名現在可以宣告為 static。
tsclass Foo {static hello = "hello";static world = 1234;static [propName: string]: string | number | undefined;}// Valid.Foo["whatever"] = 42;// Has type 'string | number | undefined'let x = Foo["something"];
類靜態端的索引簽名適用與例項端相同的規則——即每個其他靜態屬性都必須與索引簽名相容。
tsclass Foo {static prop = true;// ~~~~// Error! Property 'prop' of type 'boolean'// is not assignable to string index type// 'string | number | undefined'.static [propName: string]: string | number | undefined;}
.tsbuildinfo 大小改進
在 TypeScript 4.3 中,作為 incremental 構建的一部分生成的 .tsbuildinfo 檔案應該會顯著變小。這歸功於內部格式的幾項最佳化,建立了帶有數字識別符號的表格以便在整個檔案中使用,而不是重複完整路徑和類似資訊。這項工作由 Tobias Koppers 在 他的 PR 中帶頭完成,並啟發了 隨後的 PR 和 進一步的最佳化。
我們已經看到 .tsbuildinfo 檔案大小顯著減小,包括:
- 1MB 減小到 411 KB
- 14.9MB 減小到 1MB
- 1345MB 減小到 467MB
毋庸置疑,這些大小方面的節省也意味著構建時間略有加快。
--incremental 和 --watch 編譯中更懶惰的計算
incremental 和 --watch 模式的問題之一是,雖然它們使後續編譯速度更快,但初始編譯可能會稍微慢一些——在某些情況下,會顯著變慢。這是因為這些模式必須執行大量的簿記工作,計算有關當前專案的資訊,有時還會將這些資料儲存到 .tsbuildinfo 檔案中供以後構建使用。
這就是為什麼除了 .tsbuildinfo 大小改進之外,TypeScript 4.3 還對 incremental 和 --watch 模式進行了一些更改,使得具有這些標誌的專案的第一次構建與普通構建一樣快!為了做到這一點,通常提前計算的許多資訊被改為在後續構建中按需計算。雖然這可能會給後續構建增加一些開銷,但 TypeScript 的 incremental 和 --watch 功能通常仍將在更小的檔案集上執行,並且任何需要的資訊隨後都會被儲存。從某種意義上說,incremental 和 --watch 構建會“預熱”,並在你更新幾次檔案後加快編譯速度。
在一個擁有 3000 個檔案的儲存庫中,這使初始構建時間縮短到了近三分之一!
這項工作由 Tobias Koppers 開始,其工作促成了 此功能的最終更改。我們非常感謝 Tobias 幫助我們找到這些改進機會!
Import 語句補全
使用者在使用 JavaScript 中的 import 和 export 語句時遇到的最大痛點之一是順序——特別是 imports 的書寫方式為:
tsimport { func } from "./module.js";
而不是:
tsfrom "./module.js" import { func };
當從零開始寫一個完整的 import 語句時,這會造成一些麻煩,因為自動補全無法正常工作。例如,如果你開始寫 import { 之類的東西,TypeScript 不知道你打算從哪個模組匯入,因此它無法提供任何縮小範圍的補全。
為了緩解這個問題,我們利用了自動匯入的功能!自動匯入已經解決了無法從特定模組縮小補全範圍的問題——它們的核心目的就是提供所有可能的匯出,並在檔案頂部自動插入一個 import 語句。
所以當你現在開始編寫一個沒有路徑的 import 語句時,我們將為你提供一個可能的匯入列表。當你確認補全時,我們將完成整個 import 語句,包括你原本打算寫的路徑。

這項工作需要專門支援該功能的編輯器。你可以透過使用最新的 Visual Studio Code Insiders 版本 來嘗試這個功能。
更多資訊,請檢視 實現此功能的 PR!
編輯器對 @link 標籤的支援
TypeScript 現在可以理解 @link 標籤,並會嘗試解析它們所連結的宣告。這意味著你可以懸停在 @link 標籤內的名稱上並獲得快速資訊,或者使用跳轉到定義或查詢所有引用等命令。
例如,在下面的示例中,你可以在 @link plantCarrot 中的 plantCarrot 上跳轉到定義,TypeScript 支援的編輯器將跳轉到 plantCarrot 的函式宣告。
ts/*** To be called 70 to 80 days after {@link plantCarrot}.*/function harvestCarrot(carrot: Carrot) {}/*** Call early in spring for best results. Added in v2.1.0.* @param seed Make sure it's a carrot seed!*/function plantCarrot(seed: Seed) {// TODO: some gardening}

更多資訊,請參見 GitHub 上的 PR!
跳轉到非 JavaScript 檔案路徑的定義
許多載入器允許使用者使用 JavaScript import 在應用程式中包含資產。它們通常寫成 import "./styles.css" 之類的形式。
到目前為止,TypeScript 的編輯器功能甚至不會嘗試讀取此檔案,因此跳轉到定義通常會失敗。充其量,跳轉到定義會跳轉到類似 declare module "*.css" 的宣告(如果它能找到類似的東西)。
TypeScript 的語言服務現在會在你對相對檔案路徑執行跳轉到定義時嘗試跳轉到正確的檔案,即使它們不是 JavaScript 或 TypeScript 檔案!嘗試使用 CSS、SVG、PNG、字型檔案、Vue 檔案等的 import 來體驗它。
更多資訊,你可以檢視 實現此功能的 PR。
破壞性變更
lib.d.ts 變更
與每個 TypeScript 版本一樣,lib.d.ts 的宣告(特別是為 Web 上下文生成的宣告)發生了變化。在此版本中,我們利用了 Mozilla 的瀏覽器相容性資料 來刪除沒有任何瀏覽器實現的 API。雖然你不太可能正在使用它們,但 Account、AssertionOptions、RTCStatsEventInit、MSGestureEvent、DeviceLightEvent、MSPointerEvent、ServiceWorkerMessageEvent 和 WebAuthentication 等 API 已從 lib.d.ts 中刪除。這在 此處 進行了詳細討論。
https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/991
useDefineForClassFields 現在在 esnext 上預設為 true,最終將在 es2022 上預設為 true
2021 年,類欄位功能被新增到 JavaScript 規範中,其行為與 TypeScript 的實現方式不同。為準備此更改,在 TypeScript 3.7 中添加了一個標誌(useDefineForClassFields),以將編譯出的 JavaScript 遷移到符合 JavaScript 標準行為。
現在該功能已在 JavaScript 中,我們將預設值更改為 ES2022 及更高版本(包括 ESNext)的 true。
始終為真的 Promise 檢查報錯
在 strictNullChecks 下,在條件檢查中使用看起來始終定義的 Promise 現在被視為錯誤。
tsdeclare var p: Promise<number>;if (p) {// ~// Error!// This condition will always return true since// this 'Promise<number>' appears to always be defined.//// Did you forget to use 'await'?}
更多詳細資訊,請參閱原始更改。
聯合列舉不能與任意數字進行比較
某些 enum 在其成員被自動填充或簡單書寫時被視為聯合 enum。在這種情況下,列舉可以回溯它可能表示的每個值。
在 TypeScript 4.3 中,如果具有聯合 enum 型別的值與一個它永遠不可能相等的數字字面量進行比較,型別檢查器將發出錯誤。
tsenum E {A = 0,B = 1,}function doSomething(x: E) {// Error! This condition will always return 'false' since the types 'E' and '-1' have no overlap.if (x === -1) {// ...}}
作為變通方法,你可以重寫註解以包含適當的字面量型別。
tsenum E {A = 0,B = 1,}// Include -1 in the type, if we're really certain that -1 can come through.function doSomething(x: E | -1) {if (x === -1) {// ...}}
你也可以對該值使用型別斷言。
tsenum E {A = 0,B = 1,}function doSomething(x: E) {// Use a type assertion on 'x' because we know we're not actually just dealing with values from 'E'.if ((x as number) === -1) {// ...}}
或者,你可以重新宣告你的列舉以具有非瑣碎的初始化程式,以便任何數字對該列舉都是可分配和可比較的。如果意圖是列舉指定幾個眾所周知的值,這可能會很有用。
tsenum E {// the leading + on 0 opts TypeScript out of inferring a union enum.A = +0,B = 1,}
更多詳細資訊,請參閱原始更改。