我想在Array对象上创建包装器,以更轻松地处理二维数组。一切都很好,但我也想覆盖Symbol.iterator
,以简化与2D数组有关的嵌套循环。
我希望基本上能够像这样循环数组
const m = new Matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
for (let { c, r, value } = m) {
console.log(`Column: %s, Row: %s, Value: %s`, c, r, value);
}
我已经为此编写了代码,并且可以正常工作。但是打字稿抱怨迭代器签名,因为它与Array应该具有的不匹配。
这是我的代码
class Matrix<T=number> extends Array<Array<T>> {
// Other methods...
*[Symbol.iterator]() {
for (let r = 0; r < this.length; r++) {
for (let c = 0; c < this[r].length; c++) {
yield { c, r, value: this[r][c] as T };
}
}
}
}
Typescript抱怨Type '() => IterableIterator<[number, number, T]>' is not assignable to type '() => IterableIterator<T[]>'.
You can see it live here
我的问题是:如何在没有Typescript抱怨的情况下编写此代码?我可以通过将值设置为Any
来停止编译器,但是在那种情况下,我失去了使用Typescript的所有优势。
答案 0 :(得分:1)
尝试去做您要问的事情会很头疼,因为它违背了类型系统的精神。有一个名为Liskov Substitution Principle的想法,它表示h
意味着应该允许您在需要A extends B
实例的任何地方使用A
实例。或者,换句话说,每个B
实例也是一个A
的实例。
您说B
就是说Matrix<T> extends Array<Array<T>>
是一个Matrix<T>
。但是,如果我在for...of
loop中的Array<Array<T>>
上进行迭代,那么我希望遍历Array<Array<T>>
元素。这是Array<T>
的接口协定的一部分。如果出现Array<Array<T>>
类型的元素,则说明出现了问题:[number, number, T]
不是 和Matrix<T>
。违反了《里斯科夫换人原则》。
解决此问题的一种简单而推荐的方法是通过单独保留迭代器方法来使Array<Array<T>>
为真,而只需向Matrix<T> extends Array<Array<T>>
添加一个unroll()
方法即可生成所需的迭代器。此添加的方法没有违反替换原理,因为“不具有Matrix<T>
方法”不是unroll()
合同的一部分。
类似这样的东西:
Array<Array<T>>
但是,如果您真的想用自定义实现覆盖迭代器,可以这样做吗?是的,我猜。您无法违反class Matrix<T> extends Array<Array<T>> {
constructor(data: T[][] = []) {
super();
// Fill given data to matrix;
for (let r = 0; r < data.length; r++) {
this[r] = [];
for (let c = 0; c < data[r].length; c++) {
this[r][c] = data[r][c];
}
}
}
*unroll(): IterableIterator<[number, number, T]> {
for (let r = 0; r < this.length; r++) {
for (let c = 0; c < this[r].length; c++) {
yield [c, r, this[r][c]];
}
}
}
}
const m = new Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
for (let [c, r, value] of m.unroll()) {
console.log(`Column: %s, Row: %s, Value: %s`, c, r, value);
}
合同,因此必须编写新合同。您可以尝试使用mapped和conditional类型之类的类型操作来表示“ Array<Array<T>>
但没有定义的迭代器方法”,然后assert表示{{1 }}构造函数也是该类型事物的构造函数,然后扩展 that :
Array<Array<T>>
一切正常,万岁?好吧,还不够:
Array
请参阅type _SortOfArray<T> = Pick<
Array<Array<T>>,
Exclude<keyof Array<any>, keyof IterableIterator<any>>
>;
interface SortOfArray<T> extends _SortOfArray<T> {}
interface SortOfArrayConstructor {
new <T>(): SortOfArray<T>;
}
const SortOfArray = Array as SortOfArrayConstructor;
class Matrix<T> extends SortOfArray<T> {
constructor(data: T[][] = []) {
super();
// Fill given data to matrix;
for (let r = 0; r < data.length; r++) {
this[r] = [];
for (let c = 0; c < data[r].length; c++) {
this[r][c] = data[r][c];
}
}
}
// Other helper methods...
*[Symbol.iterator](): IterableIterator<[number, number, T]> {
for (let r = 0; r < this.length; r++) {
for (let c = 0; c < this[r].length; c++) {
yield [c, r, this[r][c]];
}
}
}
}
const m = new Matrix([['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]);
for (let [c, r, value] of m) {
// c is a number, r is a number, value is a string
console.log(`Column: %s, Row: %s, Value: %s`, c, r, value);
}
扩展的工作方式,默认情况下,返回新数组的方法实际上将返回该数组的扩展版本(称为数组的species)。而且,如果const filteredM = m.filter(row => row[0]!=='4');
// filteredM is a string[][] at compile time, but Matrix<string> at runtime!
for (let hmm of filteredM) {
// compiler thinks hmm[0] is a string, but it's really a number
console.log(hmm[0].toUpperCase()); // no compiler error, runtime error!!
}
确实是Array
,则该亚种替代将是可以的。但是我们将其更改为其他内容,因此现在Matrix<T>
的所有返回新数组的方法都被键入错误。
实际上,要使其“正确”,我们必须完全手动签出新合同:
Array<Array<T>>
呃,我什至不知道我是对的还是犯了一些错误。对我来说真的不值得。更不用说,如果您将Matrix<T>
交给interface SortOfArray<T> {
[n: number]: Array<T>;
length: number;
toString(): string;
toLocaleString(): string;
pop(): T[] | undefined;
push(...items: T[][]): number;
concat(...items: ConcatArray<T[]>[]): SortOfArray<T>;
concat(...items: (T[] | ConcatArray<T[]>)[]): SortOfArray<T>;
join(separator?: string): string;
reverse(): SortOfArray<T>;
shift(): T[] | undefined;
slice(start?: number, end?: number): SortOfArray<T>[];
sort(compareFn?: (a: T[], b: T[]) => number): this;
splice(start: number, deleteCount?: number): SortOfArray<T>;
splice(start: number, deleteCount: number, ...items: T[]): SortOfArray<T>;
// ... and on and on and on ...
,那么很多期望Array<Array<T>>
的事情都会开始引起编译器错误。
当然,您只能写出您所关心的方法和属性,但是对于我来说,这仍然是个麻烦,
好的,希望能有所帮助;祝你好运。