H5W3
当前位置:H5W3 > JavaScript > 正文

【JS】TypeScript 的 Substitutability

TypeScript 的 Substitutability

泡泡机不冒泡了发布于 2 月 2 日

Substitutability 中文含义是 可代替性,这个词我未在 TypeScript 的语言特性相关文档上看到,百度、谷歌搜索也寥寥无几。仅在TypeScript FAQ 找到相关描述。

这段描述很好理解,大体就是子类型可以用在父类型出现的地方。但实际涉及的TypeScript 使用场景,和这个词不是很契合,也许是语言的差异,中文含义不便于理解。

实际 Substitutability 解决的场景是:TypeScript 允许 function 作为回调函数时,入参个数、返回类型可以不符合方法签名。

回调 Function 入参比签名少

fetchResults 有一个参数,即回调函数。 该方法从某处获取数据,然后执行回调。 回调的方法签名有两个参数, statusCoderesults

function fetchResults(callback: (statusCode: number, results: number[]) => void) {
const results = [1,2,3];
...
callback(200, results);
}

我们用下面的方式调用 fetchResults,注意方法签名是不同的,它没有第二个参数 results

function handler(statusCode: number) {
// 业务处理
...
}
fetchResults(handler); // ✔️

可以正常编译,没有任何错误或警告。 看起来有点奇怪,但细想一下,你一直在这么用。

Array.prototype.forEach 方法签名

    /**
* Performs the specified action for each element in an array.
* @param callbackfn  A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg  An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void

实际使用:

let items = [1, 2, 3];
items.forEach(arg => console.log(arg));

在运行时,forEach使用三个参数(value、index、array)调用给定的回调函数,但大多数时候回调函数只使用其中的一个或两个参数。

那为什么不干脆将forEach 参数声明为可选。

forEach(callback: (element?: T, index?: number, array?: T[]))

如果声明为可选,由于回调的提供者不知道调用方何时会传递多少参数,将不得不检查各个参数,这显然不是你想要的。

function maybeCallWithArg(callback: (x?: number) => void) {
if (Math.random() > 0.5) {
callback();
} else {
callback(42);
}
}

声明非可选,是站在调用者的角度,保证按声明传递参数,可以兼容需要不同个数参数的回调函数;

这么处理的合理性在于,回调函数自身是最了解如何处理入参的,如果它不关心某些入参,它可以安全的忽略。

回调返回类型不匹配签名 return void

如果函数类型指定返回类型 void,则也接受具有不同的、更具体的返回类型的函数。同样,用前面的例子,这次增加handle的返回类型声明。

function handler(statusCode: number): {age:number}{
//  业务处理
...
return {"age": 4};
}
fetchResults(handler); // ✔️

fetchResults 接受的回调函数返回类型是void,而这次的handler 返回{age:number}类型,依然正常编译。

你依然可以将callback结果赋值给一个变量,但仅仅限于声明语句,其他操作都将编译失败。

function fetchResults(callback: (statusCode: number, results: number[]) => void) {
const results = [1,2,3];
...
let didItWork = callback(200, results); // ✔️  1)
console.log(didItWork); // ✔️
console.log(didItWork.age) // ❌
didItWork = {"age": 4} ; // ❌
}
// 注意虽然编译报错,但不影响最后js执行,Playground 运行结果
[LOG]: {
"age": 4
}
[LOG]: 4 

可能有人困惑既然void类型无法进行其他操作,为什么要允许1)处赋值void类型给变量。TypeScript 1.4语言规范 给出了如下说明:

function foo<T>(param:T) {
let localParam:T = param;
}
foo<number>(3); // ✔️
foo<void>(undefined); // ✔️

这么处理的合理性在于,回调函数的调用者通过声明callback 返回void, 它最清楚也可以保证返回值不会被使用。

示例Playground

javascript前端typescript
阅读 44更新于 2 月 2 日
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar

泡泡机不冒泡了
3 声望
0 粉丝

0 条评论
得票时间

avatar

泡泡机不冒泡了
3 声望
0 粉丝

宣传栏

Substitutability 中文含义是 可代替性,这个词我未在 TypeScript 的语言特性相关文档上看到,百度、谷歌搜索也寥寥无几。仅在TypeScript FAQ 找到相关描述。

这段描述很好理解,大体就是子类型可以用在父类型出现的地方。但实际涉及的TypeScript 使用场景,和这个词不是很契合,也许是语言的差异,中文含义不便于理解。

实际 Substitutability 解决的场景是:TypeScript 允许 function 作为回调函数时,入参个数、返回类型可以不符合方法签名。

回调 Function 入参比签名少

fetchResults 有一个参数,即回调函数。 该方法从某处获取数据,然后执行回调。 回调的方法签名有两个参数, statusCoderesults

function fetchResults(callback: (statusCode: number, results: number[]) => void) {
const results = [1,2,3];
...
callback(200, results);
}

我们用下面的方式调用 fetchResults,注意方法签名是不同的,它没有第二个参数 results

function handler(statusCode: number) {
// 业务处理
...
}
fetchResults(handler); // ✔️

可以正常编译,没有任何错误或警告。 看起来有点奇怪,但细想一下,你一直在这么用。

Array.prototype.forEach 方法签名

    /**
* Performs the specified action for each element in an array.
* @param callbackfn  A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg  An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void

实际使用:

let items = [1, 2, 3];
items.forEach(arg => console.log(arg));

在运行时,forEach使用三个参数(value、index、array)调用给定的回调函数,但大多数时候回调函数只使用其中的一个或两个参数。

那为什么不干脆将forEach 参数声明为可选。

forEach(callback: (element?: T, index?: number, array?: T[]))

如果声明为可选,由于回调的提供者不知道调用方何时会传递多少参数,将不得不检查各个参数,这显然不是你想要的。

function maybeCallWithArg(callback: (x?: number) => void) {
if (Math.random() > 0.5) {
callback();
} else {
callback(42);
}
}

声明非可选,是站在调用者的角度,保证按声明传递参数,可以兼容需要不同个数参数的回调函数;

这么处理的合理性在于,回调函数自身是最了解如何处理入参的,如果它不关心某些入参,它可以安全的忽略。

回调返回类型不匹配签名 return void

如果函数类型指定返回类型 void,则也接受具有不同的、更具体的返回类型的函数。同样,用前面的例子,这次增加handle的返回类型声明。

function handler(statusCode: number): {age:number}{
//  业务处理
...
return {"age": 4};
}
fetchResults(handler); // ✔️

fetchResults 接受的回调函数返回类型是void,而这次的handler 返回{age:number}类型,依然正常编译。

你依然可以将callback结果赋值给一个变量,但仅仅限于声明语句,其他操作都将编译失败。

function fetchResults(callback: (statusCode: number, results: number[]) => void) {
const results = [1,2,3];
...
let didItWork = callback(200, results); // ✔️  1)
console.log(didItWork); // ✔️
console.log(didItWork.age) // ❌
didItWork = {"age": 4} ; // ❌
}
// 注意虽然编译报错,但不影响最后js执行,Playground 运行结果
[LOG]: {
"age": 4
}
[LOG]: 4 

可能有人困惑既然void类型无法进行其他操作,为什么要允许1)处赋值void类型给变量。TypeScript 1.4语言规范 给出了如下说明:

function foo<T>(param:T) {
let localParam:T = param;
}
foo<number>(3); // ✔️
foo<void>(undefined); // ✔️

这么处理的合理性在于,回调函数的调用者通过声明callback 返回void, 它最清楚也可以保证返回值不会被使用。

示例Playground

本文地址:H5W3 » 【JS】TypeScript 的 Substitutability

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址