易混乱的类型
never vs void
只需要记住一个特点:返回never的函数 都必须存在无法到达的终点。如死循环、抛出异常。
function fn1():never{
while(true){}
}
function fn2():never{
throw new Error()
}
any vs unknown
- any 任何类型 会忽略语法检查
- unknown 不可预知的类型,不会忽略语法检查(这就是最大的区别)
const bar:any = 10
any.substr(1) //any 会忽略所有类型检查
const foo:unknown = "string"
foo.substr(1) //error 语法检查不通过
(foo as string).substr(1) //ok
if(typeof foo === 'string'){foo.substr(1)} //ok
@ts-ignore
增加@ts-ignore 的注释 会忽略下一行的语法检查
const num1:number = 100
num1.substr() //Error语法检查错误
const num2:number = 200
//@ts-ignore
num2.substr() //ok 语法检查通过
类型断言 as
ts只管编译时,不管运行时。as 就是典型的例子。
你用as 告诉编译器类型,编译器就听你的。但运行时 后果自负
function fn(a:string|null){
const length = (a as string).length
console.log(length);
}
fn('abc')//ok
fn(null) // Error js运行报错
非空断言操作符!
!用于排除null undefined,即告诉编译器:xx变量肯定不是null或undefined.
同理,运行时有可能出错。
//例子 1
function fn(a:string|null|undefined){
let s:string=""
s = a//Error 语法检查失败
s = a!//OK --如果a 真的是null或者undefined,那么s也会是null或者undefined,
//可能会带来bug
}
//fn(null)
//例子 2
type NumGenerator = ()=>number;
function myFunc(numGenerator:NumGenerator|undefined){
const num1 = numGenerator() //Error 语法检查失败
const num2 = numGenerator!() //OK
}
// myFunc(undefined) //如果真的传入undefined,也会去执行,当然执行会报错
// 例子3
let a:number
console.log(a);//Error -Variable “n” is used before being assigned
let b!:number
console.log(b);//ok !表示 你会给b一个赋值 不用编译器关心
type 和 interface
如果要定义一个静态的类型,仅有一些属性,没有方法 就用type
如果定义一种行为(行为肯定需要使用方法,仅属性是不够的),需要class实现,就用interface
可选链?.
?.遇到null或undefined就可以立即停止某些表达式的运行,并返回undefined,但是这里只针对null
和undefiend,对于0 false ""等falsely变量是不起作用的,这一点和&&不一样
这个运算符,看似是获取一个属性 其实它是有条件判断的。
即,它就是一个?: 三元表达式的语法糖。既然它有逻辑判断,那你考虑不到位,就有可能出错。
// 例子1-获取对象属性
interface IFoo{a:number}
function fn (obj:IFoo|null|undefined):number|undefined{
const a = obj?.a//?.可选链运算符
//第一 如果a是IFoo类型 则打印100
// 第二 如果a是null或者undefined 则打印undefined
console.log('a',a);
return a //100 或者undefined
}
fn({a:100})
// fn(null)
// fn(undefined)
// 例子二 获取数组元素
function tryGetArrayElement<T>(arr?:T[],index:number = 0){
return arr?.[index]
}
//编译产出:
// "use strict";
// function tryGetArrayElement(arr,index=0){
// return arr === null ||arr===void 0?void 0:arr[index]
// }
// 例子三:用于函数调用
type NumGenerator = ()=>number
function fn(numGenerator:NumGenerator|undefined|null){
const num = numGenerator?.();
console.log('num',num)//如果不是函数 则不调用,也不会报错 返回undefined
}
fn(null)
fn(undefined)
private 和 ‘#’
两者都表示私有属性。背景不同:
private 是ts中一开始就有的语法,而且目前只有ts有, ES规范 没有。
‘#’ 是ES目前的语法提案,然后被ts 3.8支持了,即ts和es都支持#
如果仅对于ts来说 用哪个都一样。
但是:ts只关注编译时,不关注运行时。所以,还得看两者的编译结果
private
private 编译之后就失去了私有的特点。即,如果你执行了(new Person()).name,虽然语法检查不通过,但是运行时是可以成功的。
即private仅仅是ts的语法 编译成js之后,就失效了。
class Person{
private name:string;
constructor(){
this.name = "zhangsan"
}
}
// 编译结果如下
"use strict";
class Person {
constructor(){
this.name = "zhangsan"
}
}
‘#’
'#'编译之后,依然具有稀有特点 而且用(new Person()).name,在运行时也是无法实现的,即 “#”是ts语法 但同时也是Es的提案语法,编译之后也不能失效
但是 编译结果中,私有是通过WeekMap来实现的 所以要确保你的运行环境支持ES6。WeekMap没有完美的Polyfill方案 强行Polyfill可能会发生内存泄漏
// ts 源码
class Person{
#name:string
constructor(){
this.#name = "zhangsan"
}
}
// 编译结果如下
// "use strict"
// var __classPrivateFieldSet = (this&&this.__classPrivateFieldSet)||function(receiver,privateMap,value){
// if(!privateMap.has(receiver)){
// throw new TypeError('attempted to set private filed on non-instance')
// }
// privateMap.set(receiver,value);
// return value
// };
// var _name;
// class Person{
// constructor(){
// _name.set(this,void 0)
// __classPrivateFieldSet(this,_name,'zhangsan')
// }
// }
// _name = new WeakMap()
ts中的函数重载
ts的函数重载,先把各个情况的函数头写出来,然后再写一个统一的、兼容上述所有情况的函数头。最后,函数体自行处理参数
class Person{
//第一,各个情况的函数头写出来
test():void
test(a:number,b:number):number
test(a:string,b:string):string
//第二。统一的,兼容上述所有情况的函数头(有一个不兼容,就报错)
test(a?:string|number,b?:string|number):void|string|number{
//第三 函数体自行处理参数
if(typeof a === “string” && typeof b === “string”){
return ‘string params’
}
if(typeof a === ‘number’ && typeof b === ‘number’){
return ‘number params’
}
console.log(‘no params’);
}
}
注意函数定义的顺序
参数越精准的,放在前面
// 错误:any 类型不精准 应该放到最后
declare function fn(x:any):any;
declare function fn(x:HTMLElement):number;
declare function fn(x:HTMLDivElement):string;
var myElem:HTMLDivElement;
var x = fn(myElem) // x:any,wat?
不要为仅在末尾参数不同时写不同的重载,应该尽可能使用可选参数
// 错误
interface Example1 {
diff(one:string):number;
diff(one:string,two:string):number;
diff(one:string,two:string,three:boolean):number;
}
// OK
interface Example2{
diff(one:string,two?:string,three?:boolean):number
}
能熟练使用 ts 已经是一名前端人员必备的技能