TypeScript 的 Substitutability

Substitutability 中文含義是 可代替性,這個詞我未在 TypeScript 的語言特性相關文檔上看到,百度、谷歌搜索也寥寥無幾。僅在TypeScript FAQ 找到相關描述。git

有關類型系統的許多答案都提到了可替代性。 這是一個原則,即若是可使用對象X代替某些對象Y,則XY的子類型。咱們一般也說X能夠分配給Y(這些術語在TypeScript中的含義略有不一樣,可是 區別在這裏並不重要)。github

這段描述很好理解,大致就是子類型能夠用在父類型出現的地方。但實際涉及的TypeScript 使用場景,和這個詞不是很契合,也許是語言的差別,中文含義不便於理解。typescript

實際 Substitutability 解決的場景是:TypeScript 容許 function 做爲回調函數時,入參個數、返回類型能夠不符合方法簽名。安全

回調 Function 入參比簽名少

fetchResults 有一個參數,即回調函數。 該方法從某處獲取數據,而後執行回調。 回調的方法簽名有兩個參數, statusCoderesultsdom

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

咱們用下面的方式調用 fetchResults,注意方法簽名是不一樣的,它沒有第二個參數 results函數

function handler(statusCode: number) {
  // 業務處理
  ...
}

fetchResults(handler); // ✔️

能夠正常編譯,沒有任何錯誤或警告。 看起來有點奇怪,但細想一下,你一直在這麼用。fetch

Array.prototype.forEach 方法簽名this

/**
     * 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

實際使用:url

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

在運行時,forEach使用三個參數(value、index、array)調用給定的回調函數,但大多數時候回調函數只使用其中的一個或兩個參數。.net

那爲何不乾脆將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); // ✔️
  console.log(didItWork); // ✔️
  console.log(didItWork.age) // ❌
  didItWork = {"age": 4} ; // ❌
}

// 注意雖然編譯報錯,但不影響最後js執行,Playground 運行結果
[LOG]: {
  "age": 4
} 
[LOG]: 4

可能有人困惑既然void類型沒法進行其餘操做,爲何要容許1)處賦值void類型給變量。TypeScript 1.4語言規範 給出了以下說明:

注意:咱們考慮過禁止聲明Void類型的變量,由於它們沒有用處。 可是,因爲容許將Void做爲泛型類型或函數的類型參數,所以不容許Void屬性或參數是不可行的。

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

這麼處理的合理性在於,回調函數的調用者經過聲明callback 返回void, 它最清楚也能夠保證返回值不會被使用。

示例Playground

相關文章
相關標籤/搜索