原始类型
目前JavaScript 中6中原始数据类型:string,number,boolean,null,undefined,symbol。
1.string,number,boolean 这三种类型在非严格模式下或者tsconfig.json配置文件"strictNullChecks"等于 true,的时候允许为空(可以存放null或者undefined)
//字符串类型
const a : string = "string"
//number类型的变量可以用来存放数字,NaN,infinity(无穷大),
const b : number = Infinity //NaN //100
//boolean类型只能存放true,false
const c : boolean = true //false
//注意:这三种类型在非严格模式下可以存放undefined,null
2.void类型(空值),一般在函数没有返回值的时候使用void标记返回值类型。
//想要给一个变量存放undefined,要把它的类型标记为void
const e : void = undefined; //null非严格模式下
//注意:非严格模式下void类型可以存放null
3.null类型和undefined类型 没有什么特殊情况。
//null本身
const d : null = null
//undefined本身
const d : undefined= undefined
4.symbol 类型只能用来存放symbol 类型的值。
但是当使用symbol去创建symbol的值后代码会报错(Symbol函数创建symbol值的时候)
//symbol 类型
const f : symbol = Symbol()
typeScript标准库声明
刚才使用Symbol函数去创建symbol值的时候,报出了错误,解决这个问题有两个办法,第一个是配置选项中的target修改为'es5'就好了。但有时候我们编译后的的js代码需要兼容ES6以下的语法,所以就要用到第二种方法。
Symbol(类)是JavaScript中内置的标准对象,就跟Array,Object这些性质是相同的,不过Symbol是ES2015中新增的,对于这种内置对象,其实它自身也是有类型的,而且这些内置对象的类型在TypeScript当中已经帮我们定义好了。
可以先去使用一些Array,在右键转到类型定义,就可以找到这个类型的声明文件,在这个文件当中声明了所有内置对象的类型声明,按道理Symbol也应该有自己的类型声明。
不过此时的类型声明文件是ES5标准库所对应的声明文件,(lib.es5.d.ts)。而Symbol内置对象是在ES2015当中所定义的,自然不会在这个文件当中定义它对应的类型。
回到tsconfig配置文件,target属性值是es5,这种情况下对标准库的引用默认只会引用ES5所对应的标准库,所以代码中使用Symbol就会报错,不仅仅是Symbol,任何在ES2015中新增的内置对象(promise等)直接使用都会报错。
我们可以使用配置文件中的 lib 选项去指定引用的标准库。
这里的DOM库是浏览器的的所有Bom对象和Dom对象的类型声明文件,它是默认添加的,但我们开启了lib选项后也要把它添加进去,否则像console这些浏览器对象都不能使用了。
typeScript标准库:内置对象所对应的声明文件,我们在代码中使用内置对象,就必须要引用标准库,否则TypeScript就找不到所对应的类型,就会报错。
作用域问题
每个文件都是全局作用域,所以在不同文件中定义同名变量会报错,解决方案:
-
使用立即执行函数,产生单独的函数作用域
(function () {
const a = 123
} )()
- 使用export,文件就会作为一个模块,模块是有单独作用域的。
const a = 11
export {} // 确保跟其他实例没有成员冲突
object类型
数组类型
元组类型
枚举类型
函数类型
任意类型
隐式类型推断
类型断言
接口 Interface
类 class
TypeScript中除了可以使用JavaScript中类的所有功能,还添加了额外的功能和语法。
基本使用:
TypeScript类的属性在使用之前必须在类型当中去声明,就是为了给属性做类型注解。
class Person{
// TypeScript需要在 类 中明确声明它所拥有的属性,而不是在构造函数中动态添加
// 在类中声明属性的方式就是直接在类中定义
name:string
private age:number = 18
// 声明的属性必须要有一个初始值,可以在声明的时候赋值,也可以在构造函数中去初始化它,否则会报语法错误.
constructor(name:string,age:number){
this.name = 'wjp'
}
}
类的访问修饰符:
作用:控制类当中属性的可访问级别。
class Person{
name:string
//私有属性:只能在类的内部使用
private age:number = 18
// public修饰符修饰的成员是公有成员,不过在TypeScript中类成员的访问修饰符默认就是public
// 加不加都一样,还是建议加
public position: string = "杭州"
// protected修饰符: 受保护的
protected readonly gender = true;
constructor(name:string,age:number){
this.name = 'wjp';
}
say(){
console.log(this.gender)
}
}
const tom = new Person('wjp',18);
console.log(tom.name)
console.log(tom.age) //会报错,因为age属性是私有属性,只能在Person类型内部访问到
console.log(tom.gender) //会报错,因为age属性是受保护成员性,只能Person类型内部及其子类中访问到
class Student extends Person {
constructor(name: string, age: number) {
super(name, age)
// 父类的protected属性子类可以访问。
console.log(this.gender)
}
}
1.私有属性(private):只能在类的内部去访问;
2.公有成员(public):TypeScript中类成员的访问修饰符默认就是public;
3.受保护的成员(protected):只能在类的内部去访问,但可以被子类继承。
构造函数的访问修饰符:
构造函数被设置为private,那这个类就不能在外部被new 关键字实例化了。就只能在类的内部添加静态方法,然后再通过类调用它的静态方法去实例化。
class Student extends Person {
private constructor(name: string, age: number) {
super(name, age)
console.log(this.gender)
}
static create(name: string, age: number) {
return new Student(name, age)
}
}
const jack = Student.create('jack', 18)
类的只读属性:
对于属性成员,除了使用修饰符去控制它的访问级别,还可以 使用 readonly 的关键词把这个成员设置为只读的。
protected readonly gender: boolean
如果属性已经有了访问修饰符,那readonly应该跟在访问修饰符的后面。
只读属性可以在声明的时候去初始化,也可以在构造函数中去初始化,两者只能选其一。
类与接口
Person类和Animal类都有同一个方法,但却是不同的实现方式,因此这个时候就不能用父类的方式去继承,一般是用接口来约束这些类的公共方法,由这些类去实现这个接口。
interface EatAndRun{
// 在接口中添加eat和run这两个方法的约束
// 使用函数签名的方式去约束这两个方法的类型。不做具体的实现!
eat(food:string):void;
run(run:string):void
}
// 这两个类当中必须要有这两个成员方法,因为它们都实现了EatAndRun接口就必须要有接口对应的成员
class Person implements EatAndRun{
eat(food:string){
console.log('优雅的进餐:'+food)
}
run(run:string){
console.log('直立行走:'+run)
}
}
class Animal implements EatAndRun{
eat(food:string){
console.log('呼噜呼噜的吃:'+food)
}
run(run:string){
console.log('爬行:'+run)
}
}
更合理的是 一般由一个接口去实现一个能力,然后让一个类型去实现多个接口,会更灵活多变。
interface Eat{
eat(food:string):void;
}
interface Run{
run(run:string):void
}
class Person implements Eat,Run{
eat(food:string){
console.log('优雅的进餐:'+food)
}
run(run:string){
console.log('直立行走:'+run)
}
}
class Animal implements Eat{
eat(food:string){
console.log('优雅的进餐:'+food)
}
}
以上就是用接口对类进行抽象。
抽象类
抽象类在某种程度上来说和接口有些类似,它也可以来约束子类中必须要有某些成员。不同与接口的是,抽象类可以包含具体的实现,而接口只能是成员的抽象,不包含具体的实现。
一般比较大的类就适合使用抽象类,例如动物,它只是一个泛指,在它的下面会有更细分的类。
定义抽象类的方式就是在class 关键词前面加一个abstract关键词。这个类被定义后就只能被继承,不能再使用new关键词去实例化对象了。
abstract class Animal{
eat(food:string){
console.log('呼噜呼噜的吃:'+food)
}
// 在抽象类中可以定义抽象方法,同样使用abstract关键词来定义
// 抽象方法也不需要方法体
abstract run(distance:number):void
}
class Dog extends Animal{
// 当父类中有抽象方法时,子类就必须去实现这个方法
run(distance:number){
console.log(distance)
}
}
const dog = new Dog();
dog.eat("骨头");
dog.run(5);
泛型
泛型:在定义函数,接口,类的时候没有指定具体的类型,等到使用的时候才去指定具体的类型。
泛型函数:
//使用泛型就是函数名后面使用尖括号<>,在<>里面定义泛型参数
//一般以大写T作为参数,然后把函数中不明确的类型改用T去代表
function log<T>(value:T):T{
console.log(value);
return value;
}
// 调用时使用<>的方式传递 泛型参数,指明T的类型
log<string []>(["wjp","zj"]);
// 类型推断
log(["wjp","zj"])
定义泛型函数类型 (类型别名)
type Log = <T>(value:T) => T;
// 完成泛型函数的实现
let myLog:Log = log
泛型 接口,类在我这篇文章中有简单的例子:https://blog.csdn.net/shuzhong_wjp/article/details/115250397
总的来说,泛型就是把我们定义时不能够明确的类型变成参数,在使用时传递的类型参数
类型声明
在实际项目开发中,会用到一些第三方的npm模块。而这些npm模块都不一定是用TypeScript编写的。所以它所提供的成员就不会有强类型的体验。
这里使用之前学习函数式编程所使用到的lodash库,它内部就没有提供的类型声明文件。
import {camelCase} from 'lodash'
// 自己写declare语句声明类型
declare function camelCase (input: string): string
const res = camelCase('zjal')
说白了就是一个成员在定义的时候因为种种原因没有声明一个明确的类型,在使用的时候可以为它在再做一个明确的声明。这种用法存在的原因就是为了要兼容一些普通的js模块。
由于TypeScript社区非常强大,目前绝大多少常用的npm模块都已经提供了对应的声明,我们是需要安装一下它所对应的类型声明模块就可以了。
这里我们安装一下它的类型声明模块
yarn add @type/lodash
类型声明模块就是一个开发依赖,它里面不会提供任何具体代码,只是对一个模块做一些对应的类型声明。我们可以去node_modules文件夹中找到刚安装的 @type\lodash模块,可以看到这个模块中都是一些.d.ts后缀文件,这些文件就是TypeScript当中专门用来做类型声明的文件。
除了类型声明模块。很多npm模块已经在内部实现了类型声明模块,很多时候甚至都不需要安装这种单独的类型声明模块了。直接就能在npm模块中找到它的类型声明模块。
如果我们所使用的npm模块内部没有提供类型声明文件,就需要去安装它所对应的声明模块,一般就是@type\模块名。如果说也没有这样的类型声明模块,这种情况我们就只能自己去使用declare语句去声明所对应的模块类型。