简介
是 JS 的升级版,借鉴静态强类型语言的特性,拥有类型检测、语法扩展、工具属性等,更适合大型企业级项目开发。
啥是强类型语言?
变量定义后,就不允许改变该变量数据类型
啥是静态类型语言?
在编译阶段就确定所有变量的数据类型
啥是动态类型语言?
在运行阶段才确定所有变量的数据类型
为何要使用
javascript是一门动态的、弱类型的编程语言,诞生时,只是为了解决网页一些简单的特效,而非当前复杂的大型企业应用,所以使用javascript开发存在以下问题
- 错误无法提早暴露,只有运行时才会暴露,因此需要测试的成本比强类型语言更高
- 开发工具无法有很好的智能提示,因此要求编码功底更高
- 重构不靠谱
- 给代码增加许多不必要的类型判断
typescript针对以上问题都进行了解决,还具有以下优势:
- 编译期进行类型检测,规避大量低级错误
- 引入泛型
- 强大的d.ts声明文件
- 兼顾灵活性
安装
全局安装
npm i typescript -g
一般只是学习时才会这样安装,真正开发项目时不会这样安装
局部安装
npm i typescript -D
开发项目时配合前端构建工具(Webpack)一起使用,更为常用
配置
tsconfig.json 是 TS编译器 的配置文件,TS编译器 根据 它的配置项 进行编译
如何快速生成
tsconfig.json配置文件
tsc --init
include
指定哪些.ts文件需要编译,这个配置项优先级高于compilerOptions.rootDir项
{
"include": "./src/**/*"
}
路径:
** 代表任意目录(有或没)
* 代表任意文件
exclude
指定哪些.ts文件不需要编译
注意:该配置项有默认值,默认node_modules,bower_components,jspm_packages 和 <outDir>目录
{
"include": [
"./src/**/*"
],
"exclude": [
"./src/home/**/*"
]
}
路径:
** 代表任意目录
* 代表任意文件
compilerOptions
- target 指定编译成ES的版本,默认
ES3
虽然TS编译器会根据target指定的ES版本进行编译,但这种仅限于转译语法,例如:箭头函数转普通函数,let/const声明转var声明等,但对于ES6新增的api无法转译,例如:Promise、Array.prototype.include()无法转译,因此,我们还需要使用babel对代码进行编译处理,才能够兼容IE8/9/10等旧版本浏览器
结论:TS编译器与Babel在语法转译上功能重复,但TS编辑器无法对ES6新的API进行转译,为了兼容老版本浏览器,只能使用Babel,就是说TS编译器无法完全替代Babel
- module 指定编译成哪种模块化规范,一般指定为
commonjs - outDir 指定编译后文件输出的目录
- lib 指定项目需要使用的库(环境),默认是ES5
- allowJs 是否允许对JS文件进行编译,
有时候项目使用到的第三方库是JS写的,而并非是TS写的,所以需要对JS进行编译输出
- checkJs 是否检查JS代码是否符合代码规范,
与 allowJs 配合使用,但如果是第三方代码,一般无须开启检查
- removeComments 是否移除注释
- onEmitOnError 是否在编译出错时不进行输出
- alwaysStrict 编译后的文件是否使用严格模式
代码使用了模块化规范后,此配置项无效
- noImplicitAny 不允许隐式的Any类型数据
- noImplicitThis 不允许不明确类型的This
{
"compilerOptions": {
"target": "es6",
"module": "es6",
"outDir": "./dist",
"allowJs": true,
"checkJs": true
}
}
- declaration 是否在编译
TS文件后生成相应.d.ts声明文件,一般开启,需要导出才会生成内容 - rootDir 指定源码的根目录,
TS编译器就会编译此目录下及其子目录下的TS文件 - esModuleInterop 设置
true表示允许依赖库中出现export =这种兼容规范导出格式,TS文件可以使用import from导入
类型
类型声明
- 使用场景
- 变量
- 函数参数
- 函数返回值
通过类型声明,可以指定 变量、函数入参、函数返回值 的类型,使其仅可以存储某类型的值
- 语法
let 变量名: 类型;
let 变量名: 类型 = 值;
function fn(参数名: 类型): 类型{}
类型推断
发生场景:变量声明
当变量的声明和赋值同时进行时,可以省略类型声明,TS编译器会根据赋值的类型给变量声明类型
const title = '123' // string
const num = 123 // number
any与unknown区别
1、any是任何类型的父类,也是任何类型的子类,不安全(能不用就不用) 2、unknown是任何类型的父类,但不是任何类型的子类,安全
数据类型
| 类型 | 说明 | 例子 |
|---|---|---|
| number | 任意数字 | let a: number; |
| string | 任意字符串 | let a: string; |
| boolean | 布尔值 | let a: boolean; |
| 字面量 | 本身 | let a: 10; |
| any | 任意类型 | let s: any; |
| unknown | 任意类型 | let s: unknown; |
| void | 无(undefined、null) | function test(): void{} |
| never | 抛出错误 | function test(): never{} |
| object | 对象 | let a: object; |
| array | 数组 | let a: string[]; 或 let t: Array<number>; |
| tuple | 固定长度数组 | let a: [string, number]; |
| enum | 枚举 |
// 枚举
enum Gender{
Male: 0,
Female: 1
}
let i: {name: string, sex: Gender};
i = {
name: 'wt',
sex: Gender.Male
}
类型别名
type mt = 1 | 2 | 3 | 4;
let d: mt;
联合类型
或
可以给变量声明多个类型,例如:
let num: number | string;
num = 10;
num = 'hello'
与
变量要同时满足,使用很少,例如:
let obj: {name: string} & {age: number};
obj = {
name: 'wt',
age: 18
};
面向对象
由于Javascript是一门基于对象的编程语言,而非纯面向对象,所以在ES6之前,使用面向对象思想编写代码是比较复杂而且有点不好理解的,但在ES6出现之后,使用面向对象编程就变得简单多了,现在TS出现之后,让面向对象编程更加完整(新增了接口,抽象类等),更加严谨(借鉴JAVA)
构造函数
当对象创建时,都会自动调用构造函数,有且只调一次,而且构造函数内this指向当前创建的对象实例
作用:初始化对象的属性(数据)
语法如下:
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
const p = new Person('小明', 18)
封装
为了让数据(属性)和 功能(方法)更安全,我们必须使用封装,这是面向对象的三大特征之一
- public 公有属性,可以任意位置修改与访问,没写默认就是公有,可继承
- protected 保护属性,只能在类内部进行修改与访问,可继承
- private 私有属性,只能在类内部进行修改与访问,不可继承
class Person{
name: string; // 默认是public
public age: number;
protected sex: string;
private hobby: Array<string>;
getHobby(){
return this.hobby
}
setHobby(val: Array<string>){
this.hobby = val
}
// TS提供改进版,使用时,就可以直接‘对象.属性名’
get name(){
return this.name
}
set name(val: string){
this.name = val
}
}
继承
也是面向对象的三大特征之一,用于提高代码的复用性和可扩展性
语法
使用关键字extends
class Animal{
name: string;
age: number;
sayHello(){
console.log("动物叫")
}
}
class Dog extends Animal{
sex: string;
// 重写
sayHello(){
console.log("汪汪")
}
}
如何扩展属性或方法
扩展属性,必须要用到super关键字,而扩展方法就不一定(可用可不用)
super代表父类对象
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = number;
}
sayHello(){
console.log("动物叫")
}
}
class Dog extends Animal{
sex: string;
constructor(name: string, age: number, sex: string){
super(name, age)
this.sex = sex;
}
// 重写方法时可以继承父类原本方法
sayHello(){
super.sayHello();
console.log('啦啦啦')
}
}
多态
也是面向对象的三大特征之一,利用方法重写原理,可配合 抽象类 和 接口 实现
class Animal{
say(): void{
console.log('叫')
}
}
class Dog extends Animal{
say(): void{
console.log("汪")
}
}
class Cat extends Animal{
say(): void{
console.log("喵")
}
}
抽象类
如果同时符合以下几点,就可以使用抽象类
1、定义专门作为父类使用的类
2、该类不能用来实例化
3、该类要对子类进行方法约束(抽象方法),但同时也可以提供公用方法实现
注意:抽象类定义时,需要用到abstract关键字
abstract class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = number;
}
// 抽象方法
abstract sayHello(msg: string): void;
}
class Dog extends Animal{
// 实现抽象方法
sayHello(msg: string){
console.log("汪汪")
}
}
接口
接口用于制订规范,对目标进行约束,接口总共有4种
对象属性接口
对对象属性进行约束,与type定义一样
interface Person{
firstName: string;
lastName: string;
}
function fn(name: Person): void{
console.log(`姓:${name.firstName},名:${name.lastName}`)
}
// 调用一,直接传对象,只能有2个属性
fn({
firstName: '李',
lastName: '四'
})
// 调用二,传对象的引用,除了包含2个属性,还可以有其他属性
const obj = {
firstName: '李',
lastName: '四',
fullName: '搜索'
}
fn(obj)
带有可选项属性的接口
interface Person{
firstName: string;
lastName: string;
age?: number;
}
函数接口
对函数进行约束,一般用于函数参数是函数时,指定函数结构的自定义类型
interface FnSum{
(a: number, b: number): number;
}
const sum: FnSum = function(x: number, y: number): number{
return x+y
}
console.log(sum(1, 4))
数组接口 (不常用)
对数组进行约束
interface ArrItf{
[index: number]: string;
}
const temp: ArrItf = ['zhangsan', 'sdfff', 'abc']
类接口
对类进行约束,非常常用
interface Animal{
name: string;
sayHello(msg: string): void;
}
class Dog implements Animal{
name: string;
constructor(name: string){
this.name = name
}
sayHello(msg: string): void {
console.log("xxx")
}
}
如果还需要继承的话,按下面例子
interface Animal{
name: string;
sayHello(msg: string): void;
}
class Test{
run(){
console.log("xxx")
}
}
class Dog extends Test implements Animal{
name: string;
constructor(name: string){
this.name = name
}
sayHello(msg: string): void {
console.log("xxx")
}
}
接口与抽象类区别
1、接口使用关键字是implements,而抽象类使用关键字是abstract与extends
2、抽象类可以有抽象方法和普通方法,而接口只能有抽象方法(类似)
接口的继承
接口与类相似,也可以继承
interface AnimalImpl{
eat(): void;
}
interface DogImpl extends AnimalImpl{
say(): void;
}
class Dog implements DogImpl{
eat(): void{
alert('吃');
}
say(): void{
alert('说');
}
}
泛型
是一种JS不具备,而TS才具备的语法特性,与 函数重载 功能相似,但实际应用上,两者并不同
应用场景
在定义 函数 、 类 、接口 时,如果遇到类型无法确定,而要到调用时才能确定的,就可以使用泛型
语法
// 单个泛型
function fn<T>(a: T): T{
return a;
}
// 多个泛型
function test<T, K>(a: T, b: K): T{
return a;
}
// 限定泛型的范围,T必须是Person的实现类(子类)
interface Person{
name: string;
}
function go<T extends Person>(a: T): string{
return a.name;
}
// 使用了TS编译器的自动类型推断
fn(10);
test(10, 'abc');
// 有时遇到复杂的类型时,TS编译器无法自动推断出来,此时需要手动指定(推荐)
fn<string>('wt')
test<number, string>(10, 'wt')
泛型函数
function test<T>(a: T): T{
return a;
}
泛型类
class Dog<T>{
name: T;
constructor(name: T){
this.name = name;
}
}
// 调用
const xb = new Dog<string>('小白')
泛型接口
interface Animal{
say<T>(p: T): T;
}
函数
函数类型声明
const fn: (a: number, b: string) => string;
fn = (x: number, y: string): string => x+y;
// 声明和赋值可以写在一起
const fn: (a: number, b: string) => string = (x: number, y: string): string => x+y;
其实这个声明已经限制函数的结构,所以形参和返回值类型都可以省略,如下
const fn: (a: number, b: string) => string;
fn = (x, y) => x+y;
参数个数保持一致
function test(a: number, b: boolean): string{
return '111'
}
test(12, true)
可选参数
可选参数 必须放在 必填参数 后面,可选参数使用?
function test(a: number, b?: boolean): string{
return '111'
}
test(12, true)
test(12)
默认参数
默认参数 必须放在 必填参数 的后面,但和 可选参数 的位置可前可后不规定
function test(a: number, b: number = 10, c?: boolean): string{
return '111'
}
test(12, 23, true)
test(23, 33)
test(23)
剩余参数
当函数参数个数不确定时,我们可以传入未知个数参数,我们需要通过 rest 参数获取剩余参数
function test(s: boolean, ...args: (string | number)[]): void{
console.log(123)
}
test(true, 'xxww', 123)
注意:rest参数必须是形参中最后一个
重载
函数名相同,参数(类型、个数)不同,ES原生无此功能,TS新增此功能
// 方式一
function demo(p: string): string
function demo(p: boolean): number
function demo(p: any): any{
if(typeof p === 'string'){
return 'wt'
}
if(typeof p === 'number'){
return 123
}
}
const a = demo('wt') //a:string
// 方式二
function demo(p: string|number): string|number{
if(typeof p === 'string'){
return 'wt'
}
if(typeof p === 'number'){
return 123
}
}
const b = demo(12) //b: string|number
注意:第一种方式相比第二种方式在返回值的类型更加精准