readonly
在Typescript 中的含义有一些细微的差别和复杂性.带有readonly
properties的对象被视为可与属性可变的相同形状的对象相互分配.因此,在TypeScrip中执行的任何操作都不会要求或禁止将具有或不具有readonly
属性的对象作为参数传递:
const xr: { readonly a: string } = { a: "" };
const xm: { a: string } = { a: "" };
let r = xr; r = xm; // okay
let m = xm; m = xr; // okay
function foo(x: { readonly a: string }) { }
foo(xr); // okay
foo(xm); // still okay
function bar(x: { a: string }) { }
bar(xm); // okay
bar(xr); // still okay
在microsoft/TypeScript#13347岁时,有一个长期存在的要求以某种方式改变这种行为,但在实现之前,这就是它的方式.
当然,在您的示例代码中,您谈论的不仅仅是一些具有readonly
个属性的对象;您具体地谈论的是readonly
arrays, a.k.a., ReadonlyArray<T>
or readonly T[]
个属性.严格来说,它们是TypeScrip中可变的Array<T>
或T[]
类型的超类型.除了具有readonly
个元素之外,readonly
数组还不具有像push()
或shift()
这样的变异数组方法.所以你可以把一个可变数组赋给一个readonly
,but not vice versa:
let rArr: readonly string[] = ["a"];
let mArr: string[] = ["a"];
rArr = mArr; // okay
mArr = rArr; // error
这往往会让人感到困惑;也许不是ReadonlyArray
和Array
,而是ReadableArray
和ReadableWritableArray
……但他们不能真正做到这一点,因为Array
已经作为JavaScript存在,而TypeScrip需要符合这个命名.因此,命名就是它的本质.
还值得注意的是,在实践中,所有ReadonlyArray<T>
个类型在运行时都只是常规的读写Array<T>
对象.这并不是说有一个ReadonlyArray
构造函数的实例缺少push()
和shift()
.因此,即使编译器试图阻止您将readonly
数组赋给可变数组,但如果您成功做到了这一点,也不太可能导致运行时错误.
有了这一点,让我们来看看所提出的问题.为了安全起见,应该只允许您用比父方法的参数大wider的参数重写方法.也就是说,函数类型的参数应为contravariant(请参见reasons for that15/2887218">Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript).但是,TypeScrip不会对方法强制执行这一规则.相反,方法被认为是bivariant个参数;除了允许您(安全地)扩大参数之外,TypeScrip还允许您narrow个参数(不安全).有reasons for that个,但这意味着TypeScrip会很乐意让你做一些不安全的事情,如下所示:
class X {
method1(x: string) { }
method2(x: string) { }
}
class Y extends X {
method1(x: string | number) { // safe
console.log(typeof x === "string" ? x.toUpperCase() : x.toFixed(1))
}
method2(x: "a" | "b") { // unsafe
console.log({ a: "hello", b: "goodbye" }[x].toUpperCase())
}
}
const x: X = new Y();
x.method1("abc"); // "ABC"
x.method2("abc"); // ? RUNTIME ERROR!
这就是为什么允许World
在其hello()
方法中接受可变string[]
的原因.子类"应该"抱怨说,要使World
成为有效的Base
,它不能安全地假定hello()
的参数将是可变array.事实并非如此,因为两者之间存在着差异.但当然,如上所述,在实践中不会因此而出现运行时错误,因为"readonly
"数组就是array.
无论如何,原来打字脚本中的所有函数参数都是双向判断的.但现在,如果您打开the --strict
suite of compiler options或明确地说只打开the --strictFunctionTypes
compiler option,则将相反地判断non-method个函数参数.不过,方法仍然是两种不同的.
因此,更改行为的一种可能方法是用函数值属性替换您的方法.这还有其他值得注意的影响,从methods are defined on the class prototype
and not the instance开始,所以它可能不合适.但它强制实施了你所期待的限制:
abstract class Base {
abstract hello(ids: readonly string[]): void;
abstract goodbye: (ids: readonly string[]) => void;
}
class World extends Base {
hello(ids: string[]): void { // okay
ids.push('hello world')
console.log(ids);
}
goodbye = (ids: string[]) => { // error as desired
ids.push('hello world')
console.log(ids);
}
}
Playground link to code个