简介
TypeScript是什么
- 以js为基础构建的语言
- 一个js的超集
- 可以在任何支持js平台中支持
- ts扩展了js,并添加了类型
- ts不能被js解析器直接执行
编译ts文件:tsc
TypeScript增加了什么
- 类型
- 添加ES不具备的新特性
- 支持ES的新特性
- 丰富的配置选项
- 强大的开发工具
基本类型
=> 将js变成静态类型语言
let a:number; //声明一个变量a,同时指定它的类型为number
a = 'hello'; //此行代码会报错
类型声明
类型声明是TS非常重要的一个特点;通过类型声明可以指定TS中变量(参数、形参)的类型;
指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错(即使报错也会正常编译);
简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值;
let 变量: 类型;
let 变量: 类型 = 值;
//参数的类型以及返回值的类型
function fn(参数: 类型, 参数: 类型): 类型{
...
}
//如果变量的声明和赋值是同时进行的,ts可以自动对变量进行类型检测
let c = false;
自动类型判断
- TS拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
- 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明
let a = 1;
a = '123' //报错
字面量进行类型声明
let a: 10; //限制值只能是10
a = 11; //报错
//这样比较好用
let b: 'male' | 'female';
let c: boolean | string;
各种类型
一个变量设置为any后,相当于对该变量关闭了TS的类型检测;所以在使用中不建议用any。
声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式any)
any和unkown
any可以赋值给任意变量,unkown不可以随便赋值给其他变量(只霍霍自己)
let s:string;
let a; //默认声明不定义类型就是any
s = a; //不会报错
let e:unknown;
e = 10;let a = '234'
a = e; //报错
如果非要赋值,在赋值之前加个判断语句
let e:unknown;
e = 10;
let a = '234'
if(typeof e === 'string') {
s = e
}
类型断言
用来告诉解析器变量的实际类型
语法:
变量 as 变量
<类型>变量
s = e as string; //告诉编译器e就是字符串,别报错!
s = <string>e;
viod
void用来表示空,以函数为例,就表示没有返回值的函数
function fn():viod {
}
never
never表示永远不会返回结果
function fn():never {
throw new Error('报错了');
}
js中有一种函数就不返回结果,连undefined都不返回,是用来报错的。
程序报错=>代码结束=>没有返回值。
Object
表示一个js对象
let a: object;
a = {}
a = function() {}
{}用来指定对象中可以包含哪些属性
let b: {name: string};
b = {} //会报错,里边必须有name,且多了也不行
b = {name:'hui',age:19} //也会报错
b = {name:'sun'}
let c:{name:string,age?:number}; //问号代表属性可选,加不加都行
c = {name:'123'}
c = {name:'1233',age:17}
let d: {name:string,[propName:string]: any}
d = {name:'456',age:18,render:'男'}
中括号里边 : propName可以为任意字符串(js属性名都是字符串)
设置函数结构的类型声明
let e: Function
let f: (a,b) => number; //表示希望f是一个函数,后边是类型声明,但是只能是两个参数,三个会报错
Array
let e:string[]; //表示字符串数组
e = ['a','w']
let g:Array<number>
g=[1,2,3]
tuple
元组:固定长度的数组
let h:[string,string] //表示创建了一个元组,里边俩值都是string类型
enum
枚举:把所有可能的情况列出来
Enum Gender {
male = 0,
female = 1
}
let i: {name:string,gender:Gender};
i = {
name:'sun',gender:Gender.male //会直接存储0
}
表示类型的时候不仅有或(|),还有和(&)
let j:string & number ; //可以但是毫无意义
let i: {name: string} & {age: number}
类型别名
let k: 1 | 2 | 3;
let l : 1 | 2 | 3;
type myType = string;
let m:myType; //相当于let m:string
type yourType = 1 | 2 | 3;
let k :yourType
编译选项
tsc app.ts 改一次就要重新编译一次 => tsc app.ts -w 编译器会自动监视文件变化
根据配置文件 tsconfig.json 让tsc编译所有文件
tsconfig.json
是ts编译器的配置文件,可以根据它的信息来对代码进行编译,可以写注释
{
"include":[
//一个*表示任意文件,两个**表示任意目录
"./src/**/*"
],
"exclude":[
"./src/hello/**/*"
],
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
],
"compilerOptions": {
"allowJs": true,
"checkJs": true
}
}
include:包含
定义希望被编译文件所在的目录
exclude:不包含
定义需要排除在外的目录
默认值:["node_modules", "bower_components", "jspm_packages"]
extend:继承
定义被继承的配置文件
"extends": "./configs/base" //当前配置文件中会自动包含config目录下base.json中的所有配置信息
files:列表
指定被编译文件的列表,只有需要编译的文件少时才会用到
compilerOptions:编译器选项
编译选项是配置文件中非常重要也比较复杂的配置选项
在compilerOptions中包含多个子选项,用来完成对编译的配置
项目选项
target:设置ts代码编译的目标版本(ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext)
module:设置编译后代码使用的模块化系统(CommonJS、UMD、AMD、System、ES2020、ESNext、None)
lib:指定代码运行时所包含的库(宿主环境)(ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ) => 一般不动
outDir:编译后文件的所在目录;默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置
outFile:将所有的文件编译为一个js文件;默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果module制定了None、System或AMD则会将模块一起合并到文件之中
rootDir:指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录
allowJs:是否对js文件编译
checkJs:是否对js文件进行检查
removeComments:是否删除注释,默认false
noEmit:不生成编译后的文件,默认false
noEmitOnError:当有错误时不生成编译后的文件,默认false
sourceMap:是否生成sourceMap,默认false
alwaysStrict:设置编译后的文件是否使用严格模式,默认false
noImplicitAny:不允许隐式any类型
noImplicitThis:不允许隐式类型的this
strictNullChecks:严格的空值检查
strict:所有严格检查的总开关,为true则全开
使用webpack打包代码
export.modules = {
...
module:{
rules:[
{
test:/\.ts$/,
use:'ts-loader',
exclude:/node-modules/
}
]
}
}
面向对象
类
class 类名 {
属性名: 类型;
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
对象中包含属性和方法
class Person {
//定义实例属性:需要通过对象实例访问
//const per = new Person() per.name
name:string = 'sun';
//定义类属性(静态属性):无需创建对象,通过类访问
//Person.age
static age:number = 18;
}
构造函数
class C{
name: string;
age: number
constructor(name: string, age: number) {
//this就是当前对象
this.name = name;
this.age = age;
}
}
继承
使用继承后,子类将会拥有父类所有的方法和属性
通过继承可以将多个类中共有的方法和属性写在一个父类中
如果子类中添加了和父类相同的方法,则子类会覆盖掉父类的方法。这种形式称为方法的重写。
super关键字
父类也叫超类
在类的方法中,super表示父类
class Animal {
name:string;
constructor(name:string) {
this.name = name;
}
sayHello(){
console.log('动物在叫')
}
}
class Dog extends Animal {
sayHello(){
//就是在调用父类的sayHello
super.sayHello()
}
}
如果在子类中使用了构造函数,相当于把父类的构造函数给覆盖掉了,父类中的属性就无了。
在子类的构造函数中必须对父类的构造函数进行调用 => super()
抽象类
以abstract开头的类就是抽象类,和其他类差别不大,只是不能用来创建对象。
抽象类是专门用来继承的类,抽象类中可以添加抽象方法。
抽象方法使用abstract开头,没有方法体,只能定义在抽象类中,并且子类必须对抽象方法进行重写。
接口
用来定义一个类结构
type myType = {
name:string,
age:number
}
interface myInterface {
name:string;
age:number;
}
const obj:myInterface = {
//多一个少一个都报错,跟类型定义很像
name:'sss',
age:111
}
用来定义一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去使用。
和类型声明的区别:接口可以重复声明,最后是所有的接口的并集。
接口可以在定义类的时候去限制类的结构,在这一点有点像抽象类。
//接口中所有的属性都不能有实际的值,只定义对象结构不考虑实际值
interface myInter {
name:string;
sayHello():void;
}
在抽象类中可以有具体的方法也可以不,接口中都是抽象方法
使用implements实现接口
interface myInter {
name:string;
sayHello():void;
}
class MyClass implements myInterface {
//必须把接口里所有的属性和方法在这里实现
name:string;
constructor(name:string) {
this.name = name
}
sayHello() {
console.log('xxx')
}
}
接口实际上就是定义了一个规范,对类的限制
属性封装
属性可以任意修改将会导致对象中的数据变得不安全
public属性 => 可以在任意位置访问(修改) 默认值
private属性 => 私有属性只能在类内部进行访问(修改),子类也不行 => 通过在类中添加方法使得私有属性可以被外部访问
protect属性 => 受保护的属性,只能在当前类和当前类的子类中访问。
class Person {
private name:string;
private age:number;
constructor(name:string,age:number) {
this.name = name;
this.age = age;
}
//定义方法获取name属性
getName() {
return this.name
}
//定义方法设置name属性
setName(value:string) {
this.name = value
}
}
const per = new Person('sun',18)
//想要获取属性值需要调用getName方法
console.log(per.getName())
//想要修改属性值必须调用setName方法
per.setName('hui')
getter和setter被称为属性的存取器
Ts中设置getter方法方式
class Person {
private name:string;
private age:number;
constructor(name:string,age:number) {
this.name = name;
this.age = age;
}
get name() {
return this.name
}
set name(value:string) {
this.name = value
}
}
const per = new Person('sun',18)
//per.name不是寻找name属性,而是执行get name方法
console.log(per.name)
console.log(per.name('hui'))
定义类的简要写法,直接将属性定义在构造函数中
class C{
constructor(public name:stirng,public age:number) {
}
}
等价于
class C{
name:string;
age:number;
constructor(public name:stirng,public age:number) {
this.name = name;
this.age = age
}
}
泛型
在定义函数或者类时,如果遇到类型不明确的时候就可以使用泛型
要根据调用的情况得到结论
<T>就是泛型
function fn<T>(a:T):T {
return a
}
可以直接调用具有泛型的函数 =>
fn(10); //不指定泛型,Ts类型自动推断
fn<string>(‘hello') // 指定泛型
泛型可以指定多个
表示泛型T必须是Inter的实现类(子类)