名义上的类型别名,类型结构兼容的预防,函数参数的双向协变、及赋值
1. 什么是名义上的类型别名? (名义上的类型别名不兼容)
使用Type 来为类型创建一个别名从而使用一个间断的名字引用该类型
类型别名实质上与原来的类型一样,它仅仅是一个替代的名字,别名只是对类型的一个引用,并创建一个新的类型。类型别名可以让代码的可读性更高
let a: string = 'a'
type aaa = string
let b: aaa = 'b'
// type bbb extends aaa error
console.log(b)
// 类型别名不能被 extends 和implements
类型结构兼容的预防
基本类型
let str: string = 'Hello'
str = 123 // error TS2322: Type '123' is not assignable to type 'string'.
TS的结构类型兼容
TypeScript里的类型兼容性是基于结构子类型的,这是有别于传统的JAVA、C#基于名义类型的语言。
interface Named {
name: string
}
class Person {
name: string
}
let p: Named
p = new Person()
x要兼容y,那么y至少具有与x相同的属性
interface Named {
name: string
}
let x: Named
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' }
x = y
如何避免
添加差异性,如添加一个私有属性
interface ScreenCoordinate {
_screenCoordBrand: any
x: number
y: number
}
interface PrintCoordinate {
_printCoordBrand: any
x: number
y: number
}
function sendToPrinter(pt: PrintCoordinate) {
}
function getCursorPos(): ScreenCoordinate {
return <ScreenCoordinate>{ x: 0, y: 0 }
}
// getCursorPos的return值继承了ScreenCoordinate类的私有变量
sendToPrinter(getCursorPos())
PrintCoordinate要兼容ScreenCoordinate,那至少ScreenCoordinate要具有与PrintCoordinate相同的属性
显然,以上案列是错误的
// 正确写法
function sendToPrinter(pt: PrintCoordinate | ScreenCoordinate) {
}
function getCursorPos(): ScreenCoordinate {
return <ScreenCoordinate>{ x: 0, y: 0 }
}
sendToPrinter(getCursorPos())
如何将更少参数的函数能够赋值给具有更多参数的函数?(函数参数的类型结构兼容)
function handler(arg: string) {
// ....
}
function doSomething(callback: (arg1: string, arg2: number) => void) {
callback('hello', 42);
}
// Expected error because 'doSomething' wants a callback of
// 2 parameters, but 'handler' only accepts 1
doSomething(handler);
doSomething的回调函数,必需两个参数 handler只有一个参数
那如何让有更少参数的函数赋值给具有更多参数的函数
function handler(arg: string) {
// ....
}
function doSomething(callback: (arg1: string, arg2?: number) => void) {
callback('hello', 42);
}
doSomething(handler);
此时,arg2是一个可有可没有的参数,不会强制执行
跟forEach(callback(element, index, array))中的回调函数可选参数含义的区别
let items = [1, 2, 3];
items.forEach(arg => console.log(arg));
forEach循环数组,必定会产生三个参数传入callback,只是callback接受参数的个数是可选的
mozilla的forEach源码
function ArrayForEach(callbackfn/*, thisArg*/) {
var O = ToObject(this);
var len = ToLength(O.length);
if (arguments.length === 0)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "Array.prototype.forEach");
if (!IsCallable(callbackfn))
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
var T = arguments.length > 1 ? arguments[1] : void 0;
for (var k = 0; k < len; k++) {
if (k in O) {
callContentFunction(callbackfn, T, O[k], k, O);
}
}
return void 0;
}
从源码可以看出,callContentFunction(callbackfn, T, O[k], k, O); 并没有对forEach内的回调函数callback的参数做必需使用做判断, 但确实是给回调函数提供了三个参数
因此
[1, 2, 3].forEach(() => console.log('just counting'));
[1, 2, 3].forEach(x => console.log('just counting'));
以上两行代码没有本质区别
为什么函数参数是双向协变?(函数参数的类型)
-
换个角度说,协变保持了类型参数的继承关系
-
TypeScript 2.6新增了编译器选项--strictFunctionTypes,对函数参数进行严格逆变比较。
-
首先考虑数组类型构造器: 从Animal类型,可以得到Animal[](“animal数组”)。 是否可以把它当作
协变:一个Cat[] 也是一个Animal[] 逆变:一个Animal[] 也是一个Cat[] 以上二者均不是(不变)? 如果要避免类型错误,且数组支持对其元素的读、写操作,那么只有第3个选择是安全的。 Animal[] 并不是总能当作Cat[], 因为当一个客户读取数组并期望得到一个Cat,但Animal[]中包含的可能是个Dog。所以逆变规则是不安全的。
反之,一个Cat[] 也不能被当作一个Animal[]。 因为总是可以把一个Dog放到Animal[]中。 在协变数组,这就不能保证是安全的,因为背后的存储可以实际是Cat[]。因此协变规则也不是安全的—数组构造器应该是不变。
举个🌰
class Animal {
public shout() {
console.log('animal can shout')
}
}
class Dog implements Animal {
public eat() {
console.log('dog eat bone bone')
}
public shout() {
console.log('dog shout wang wang')
}
}
let animalShout = (animal: Animal) => { console.log(animal.shout()) }
let dogShout = (dog: Dog) => { console.log(dog.shout()) }
dogShout = animalShout
animalShout = dogShout
let funA = (arg: number | string): void => {}
let funB = (arg: number): void => {}
funA = funB
funB = funA
编译结果
$ tsc --strictFunctionTypes Bivariance.ts
Bivariance.ts:26:1 - error TS2322: Type '(dog: Dog) => void' is not assignable to type '(animal: Animal) => void'.
Types of parameters 'dog' and 'animal' are incompatible.
Property 'eat' is missing in type 'Animal' but required in type 'Dog'.
26 animalShout = dogShout
~~~~~~~~~~~
Bivariance.ts:12:10
12 public eat() {
~~~
'eat' is declared here.
Bivariance.ts:30:1 - error TS2322: Type '(arg: number) => void' is not assignable to type '(arg: string | number) => void'.
Types of parameters 'arg' and 'arg' are incompatible.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
30 funA = funB
~~~~
Found 2 errors.