我对as const强制转换感到困惑。我查看了一些文件和视频,但并没有完全理解。

我关心的是下面代码中的as const意味着什么,使用它有什么好处?

const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);

当前回答

如果你要写const args =[8,5],没有什么可以阻止你同时写args[0] = 23或args.push(30)或其他任何东西来修改该数组。你所做的只是告诉TS/JS名为args的变量指向特定的数组,所以你不能改变它引用的内容(例如,你不能做args = "something else")。你可以修改数组,但你不能改变它的变量指向什么。

另一方面,将const添加到声明中实际上使其成为常量。整个数组都是只读的,所以你根本不能修改数组。


澄清一下,如评论中所指出:

“真的使它恒定”可能意味着存在一些运行时效应,而实际上没有。在运行时,args.push(30)仍然会修改数组。const所做的一切就是让TypeScript编译器在看到你这样做时抱怨。——jcalz

因为const只影响编译器,而且它的只读效果有一个例外(见注释)。但总的来说,这仍然是const和as const使用上的主要区别。一个用于使引用不可变,另一个用于使被引用的东西不可变。

其他回答

这就是所谓的const断言。const断言告诉编译器为表达式推断最窄的*或最特定的类型。如果不启用它,编译器将使用其默认类型推断行为,这可能会导致更广泛或更通用的类型。

注意,它被称为“断言”而不是“强制转换”。在TypeScript中通常要避免使用术语“cast”;当人们说“cast”时,他们通常暗示某种可以在运行时观察到的效果,但TypeScript的类型系统,包括类型断言和const断言,完全从发出的JavaScript中删除。因此,在运行时,使用const和不使用const的程序之间绝对没有区别。


但是在编译时,有一个明显的区别。让我们看看在上面的例子中,当你省略const时会发生什么:

const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);

编译器看到const args = [8,5];并推断数字[]的类型。这是一个可变数组,包含0个或多个number类型的元素。编译器不知道有多少或哪些元素。这样的推论一般来说是合理的;通常,数组内容需要以某种方式进行修改。如果有人想要编写args.push(17)或args[0]++,他们将乐于使用数字类型[]。

不幸的是,下一行Math.atan2(…args)会导致一个错误。Math.atan2()函数恰好需要两个数值参数。但是编译器只知道args是一个数字数组。它完全忘记了有两个元素,因此编译器会报错,认为您调用Math.atan2()时参数为“0或更多”,而它需要的正是两个参数。


将其与as const代码进行比较:

const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);

现在编译器推断args的类型是readonly[8,5]…一个只读元组,其值恰好是数字8和5的顺序。具体来说,arg游戏。编译器知道Length恰好为2。

这就足够下一行使用Math.atan2()工作了。编译器知道Math.atan2(…args)与Math相同。Atan2(8,5),这是一个有效的调用。


同样,在运行时,两者没有任何区别。两个版本都将日志1.0121970114513341记录到控制台。但是,与静态类型系统的其他部分一样,const断言并不意味着在运行时具有效果。相反,它们可以让编译器更多地了解代码的意图,并且可以更准确地区分正确代码和错误代码。

游乐场链接到代码


*这并不严格适用于数组和元组类型;只读数组或元组在技术上比可变数组或元组宽。可变数组被认为是只读数组的子类型;前者不知道有像push()这样的突变方法,而后者有。

这是一个const断言。这里有一个关于它们的方便的帖子,这里是文档。

当我们用const断言构造新的文字表达式时,我们可以向语言发出信号 表达式中的任何文字类型都不应该被拓宽(例如,不要从“hello”变成字符串) 对象字面量获得只读属性 数组字面量变成只读元组

使用const args =[8,5]作为const;,则应用第三个项目,tsc将其理解为:

// Type: readonly [8, 5]
const args = [8, 5] as const;

// Ok
args[0];
args[1];

// Error: Tuple type 'readonly [8, 5]' of length '2' has no element at index '2'.
args[2];

没有断言:

// Type: number[]
const args = [8, 5];

// Ok
args[0];
args[1];

// Also Ok.
args[2];

简而言之,它让你创建完全只读的对象,这被称为const断言,在你的代码中,const意味着数组位置的值是只读的,这里有一个它如何工作的例子:

const args = [8, 5] as const;
args[0] = 3;  // throws "Cannot assign to '0' because it is a read-only     
args.push(3); // throws "Property 'push' does not exist on type 'readonly [8, 5]'"

你可以在最后一个抛出的错误中看到,args = [8,5] as const被解释为args: readonly[8,5],这是因为第一个声明相当于一个只读元组。

有一些例外的断言是“完全只读”,你可以检查他们在这里。但是,一般的好处是将只读行为添加到其所有对象属性中。

const args = [8, 5];

// Without `as const` assert; `args` stills a constant, but you can modify its attributes
args[0] = 3;  // -- WORKS
args.push(3); // -- WORKS

// You are only prevented from assigning values directly to your variable
args = 7;     // -- THROWS ERROR

要了解更多细节,这里是其他相关问题/答案的列表,帮助我理解const断言:

我如何在TypeScript中声明一个只读数组元组? 在TypeScript中,类型断言和更新的操作符之间有什么区别吗? Typescript只读元组

作为const应用于对象或数组时,它使它们不可变(即使它们只读)。对于其他字面量,它可以防止类型扩展。

const args = [8, 5] as const;

args[0] = 10; ❌ Cannot assign to '0' because it is a read-only property.

其他一些优点:

如果您没有强制转换为不同的类型,则可以在编译时捕获错误而无需运行程序。 编译器不允许你重新分配嵌套对象的属性。

如果你要写const args =[8,5],没有什么可以阻止你同时写args[0] = 23或args.push(30)或其他任何东西来修改该数组。你所做的只是告诉TS/JS名为args的变量指向特定的数组,所以你不能改变它引用的内容(例如,你不能做args = "something else")。你可以修改数组,但你不能改变它的变量指向什么。

另一方面,将const添加到声明中实际上使其成为常量。整个数组都是只读的,所以你根本不能修改数组。


澄清一下,如评论中所指出:

“真的使它恒定”可能意味着存在一些运行时效应,而实际上没有。在运行时,args.push(30)仍然会修改数组。const所做的一切就是让TypeScript编译器在看到你这样做时抱怨。——jcalz

因为const只影响编译器,而且它的只读效果有一个例外(见注释)。但总的来说,这仍然是const和as const使用上的主要区别。一个用于使引用不可变,另一个用于使被引用的东西不可变。