声明合并
如果定义了两个相同名字的函数(这有些像重载),枚举,接口,或类,那么它们会合并成一个类型
1. js中的同名函数
//如果是js,同名函数后者会覆盖前者
function f1(){
console.log("f1 top")
}
function f1(){
console.log("f1 bottom")
}
f1();//f1 bottom
2.1 基于参数个数的函数重载
ts支持函数重载(参数个数/参数类型/参数顺序/返回值,任意一个不同都可形成重载),值得注意的是,需要先声明,后实现
//对于重载的声明
function computed(m: number): number;
function computed(m: number,n:number): number;
//这里是具体实现
function computed(m: number ,n?: number): number {
if(n){
return m+n
}else{
return 2*m
}
}
console.log(computed(2))//4
console.log(computed(1,2))//3
2.2 基于参数类型的函数重载
对于不同类型的参数,在实现时候使用管道符|进行分隔
function computed(m: number,n:string): number;
function computed(m: number,n:number): number;
function computed(m: number ,n: number|string): number {
if(typeof(n)==="string"){
return +n+m
}else{
return m+n
}
}
console.log(computed(1,2))//3
console.log(computed(1,"2"))//3
2.3 基于参数顺序的函数重载
m,n参数类型的顺序不一致,也可形成重载
function computed(m: number,n:string): number;
function computed(m: string,n:number): number;
function computed(m: number|string ,n: number|string): number {
if(typeof(m)==="string"){
m=+m
}
if(typeof(n)==="string"){
n=+n
}
return m+n
}
console.log(computed("1",2))//3
console.log(computed(1,"2"))//3
2.4 基于返回值的函数重载
function computed(m: number): number;
function computed(m: number): string;
function computed(m: number): number|string {
return Math.random()>0.5?m:m.toString()
}
console.log(typeof computed(1))// number or string
3 枚举的声明合并
定义多个同名的枚举类型也会发生声明合并,但是要注意,只有第一个声明的枚举可以省略赋值,后边的不行
enum Language{
java,
php,
node,
}
//这里需要手动赋值
enum Language{
python=4
}
//编译后是这样的
"use strict";
var Language;
(function (Language) {
Language[Language["java"] = 0] = "java";
Language[Language["php"] = 1] = "php";
Language[Language["node"] = 2] = "node";
})(Language || (Language = {}));
(function (Language) {
Language[Language["python"] = 4] = "python";
})(Language || (Language = {}));
//# sourceMappingURL=index.js.map
4 接口的声明合并
interface Box{
width:string
}
interface Box{
height:string
}
//等价于
interface Box{
width:string
height:string
}
4.1 属性类型不同无法合并
//这两个无法合并,height类型不一致
interface Box{
height:string
}
interface Box{
color:string
height:number
}
4.2 接口中方法的合并,与函数的合并一样
interface Alarm {
price: number;
alert(s: string): string;
}
interface Alarm {
weight: number;
alert(s: string, n: number): string;
}
//上述写法等价于
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}
5 类的合并与接口的合并规则一致。
补充章节
1. never类型
never类型表示的是那些永不存在的值的类型,常在抛出错误时使用。特殊情况下,变量也可以使用never声明,比如一个永远为空的数组。never是任何类型的子类型,也可以赋值给任何类型,但是反过来不可以。(自身除外)
function error(message: string): never {
throw new Error(message);
}
const emptyArr: never[] = []
2. 常量枚举
与普通枚举相比,常量枚举多了一些限制:常量枚举会在编译阶段被删除,不可包含计算成员
const enum Language {
java,
node,
php
}
console.log([Language.java, Language.node, Language.php])
// 非常量枚举编译后是这样的
"use strict";
var Language;
(function (Language) {
Language[Language["java"] = 0] = "java";
Language[Language["node"] = 1] = "node";
Language[Language["php"] = 2] = "php";
})(Language || (Language = {}));
console.log([Language.java, Language.node, Language.php]);
//# sourceMappingURL=index.js.map
// 常量枚举编译后是这样的
"use strict";
console.log([0 /* java */, 1 /* node */, 2 /* php */]);
// 如果tsconfig.json中removeComments属性为true,就是下面这样,删除注释
console.log([0, 1, 2]);
//# sourceMappingURL=index.js.map
// 不可含有计算属性
// 这样会报错
const enum Color {Red, Yellow, Blue = "blue".length};
console.log(Color.Blue);
3 函数类型接口
接口可用于规范对象,而函数也是对象,所以接口也可以用于规范函数,但有些差别
//作为对象方法使用的普通函数
interface User{
name:string,
say:(msg:string)=>string
}
//对方法的入参和返回值进行约束的函数类型接口
interface Say{
(msg:string):string
}
// 所以上边的写法还可以这样
interface User{
name:string,
say:Say
}
const u:User={
name:"tom",
say:(msg:string):string=>{
return msg
}
}
console.log(u.say("hello"))//hello
泛型
泛型是指在定义函数/接口/类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,这在面向对象语言中很常见。
1. 初识
从概念出发,泛型可以抽象为一个表示某种特定类型的占位符。
//现在有这样一个需求,log函数传入一个数字作为参数,原样返回,代码如下:
function log1(m:number):number{
return m
}
//现在需求变了,传入的是个字符串,原样返回,代码如下:
function log2(str:string):string{
return str
}
// 很明显,log1和log2存在高冗余的代码
// 在不削弱ts静态类型检查能力的情况下,泛型是比any更好的选择
//不一定非要用T表示,这只是个占位符,换成ABCD都行
function log<T>(param:T):T{
return param
}
//注意看这里:log<T>,函数调用的时候我们并没有手动指定类型,但是不会报错
//这是因为ts的类型推论(合理使用该特性会让代码更简洁)
console.log(log("string"))//string
console.log(log(1024))//1024
console.log(log(true))//true
//也可以是这样的
console.log(log<string>("string"))//string
console.log(log<number>(1024))//1024
console.log(log<boolean>(true))//true
2. 感知
泛型可以使用多个,且具有关联性
//定义一个交换函数swap,入参为一个元组,返回值为交换后的元组
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
console.log(swap(['lengyuexin', '冷月心'])); // ['冷月心', 'lengyuexin']
//关联性指的是所有用同一个占位符(泛型)占位的,在类型确定时都会被统一替换成该类型
//当然,这可能看起来有点像废话...
//手动指定类型 <string,string>,第一个string替换所有的T,第二个string替换所有的U
console.log(swap<string,string>(['lengyuexin', '冷月心'])); // ['冷月心', 'lengyuexin']
3. 不惑
泛型还可以只用某一部分,或者称其为泛型变量
//现在有这样一个需求,输出某个数组的长度
//这样会报错,T身上没有length属性
function getLength<T>(arr:T):number{
return arr.length
}
//可以更精细一些,既然确定是数组,就将确定部分具体化
//Array<T>,这里泛型就不是完全的占位了,是部分占位
function getLength<T>(arr:Array<T>):number{
return arr.length
}
// 还可以更稳妥的加入断言,以下两种断言方式都ok
function getLength<T>(arr:Array<T>):number{
return (<Array<T>>arr).length
}
function getLength<T>(arr:Array<T>):number{
return (arr as Array<T>).length
}
4 洞玄
补充章节提到了函数类型接口,实际上,在接口中也可以接入泛型
//不接入泛型的函数接口
interface Say{
(msg:string):string
}
//接入泛型的函数接口
interface Say<T>{
(msg:T):T
}
//声明函数-此时需要手动指定类型
const say:Say<string>=(msg:string)=>msg
console.log(say("函数接口中的泛型..."))//函数接口中的泛型...
5 知命
泛型也可以用在类中,作用于类的成员属性和方法。
class Print<T> {
private store: Array<T> = [];
inner(msg: T) {
this.store.push(msg);
}
outer() {
console.log(this.store);
}
}
//使用类型推论可以不限制类型的插入 const p = new Print();
//一旦限定,就只能同类型的插入
const p = new Print<string>();
p.inner("吃饭");
p.inner("睡觉");
p.inner("打豆豆");
// p.inner(1024);
p.outer()//['吃饭','睡觉','打豆豆']
参考链接
- 掘金ts小册:https://juejin.im/book/5da08714518825520e6bb810
- ts中文手册:https://zhongsp.gitbooks.io/typescript-handbook
- ts入门教程:https://ts.xcatliu.com
- ts常见问题整理:https://juejin.im/post/5e33fcd06fb9a02fc767c427