本篇文章记录了学习typeScript的学习过程,如果有不足的地方希望大家能够补充。参考文档
目录
TypeScript是以JavaScript为基础构建的语言,它是一个JavaScript超集,可以在任何支持JavaScript的平台中执行,它扩展了JavaScript,并且添加了类型。TS不能被JS解析器直接执行
1.typeScript开发环境搭建
①下载安装node.js
②使用npm全局安装typescript
-进入命令行
-输入:npm i-g typescript
③创建一个js文件夹
④使用tsc对ts文件进行编译
-进入命令行
-进入ts文件所在目录
-执行命令 : tsc xxx.ts
ts文件在浏览器中是不能执行的,所以说要将其转化为js文件,也就是上面的第四步
2.ts的类型声明
注意在编辑器中,如果ts文件编译转换成js文件报错,可以在控制台输入 tsc --init生成ts.config即可
类型声明是TS非常重要的一个特点
- 通过类型声明可以指定TS中变量(参数、形参)的类型
- 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则 报错
- 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
ts可以对变量进行声明,基本数据类型都可以,例如number,string,boolean,如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测。
在js中是不对变量的个数进行考虑的,但是在ts中需要,一旦多输入参数或者是少输入参数,都会报错,必须要按照要求的参数个数以及类型输入。
语法
let 变量: 类型;
let 变量: 类型 = 值;
function fn(参数: 类型, 参数: 类型): 类型{
...
}
类型 | 例子 | 描述 |
number | 1,-33,0 | 任意数字 |
string | “hello” , 'hello',hello | 任意字符串 |
boolean | true,false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型(不建议使用) |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或者undefined) |
never | 没有值 | 不是任何值 |
object | {name:"猪八戒"} | 任意js对象 |
array | [1,2,3] | 任意js数组 |
tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
enum | enum{A,B} | 枚举,TS中新增的类型 |
可以使用 | , & 连接多种类型
2.1 any与unknown
any表示的是任意类型,一个变量设置类型any后相当于对该变量关闭了TS的类型检测,使用TS时,不建议使用any类型。
声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)
unknown和any的区别
any类型的变量可以赋值给任何变量,并且不会报错。
unknown类型的变量赋值给其他类型的值就会报错,解决报错方法的话需要进行判断
遇到类型不确定的变量优先使用unknown
类型断言,有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:
语法:
变量 as 类型
<类型>变量
//告诉浏览器,e是字符串
s=e as string
s=<string>e
2.2 void与never
void用来表示空,以函数为例,就表示没有返回值的函数。
never表示永远不会返回结果。例如报错的函数就可以使用never。
function fn2():never{
throw new Error("报错了!!")
}
2.3 object和function
(1)object
object在开发的时候一般不直接使用,因为在js中万物皆对象,起不到一个限制作用
一般指定对象有以下方式:
①{}用来指定对象中包含哪些属性,在属性名后面加上?,表示属性是可选的,这样子少写一个属性就不会报错。
语法:{属性名:属性值,属性名:属性值}
let b:{name:string,age?:number}
b={name:'水果',age:18}
②[propName:string]:any表示任意类型的属性。这表示c中必须要有name属性,其他的任意
let c:{name:string,[propName:string]:any}
c={name:'孙悟空',age:18,}
(2)function
function也不会直接使用,经常会限制函数的结构,使用类似于箭头函数的形式声明
语法:(形参:类型,形参:类型.....)=>返回值
let d:Function //不常用
以下写法就相当于希望d是一个函数,并且有两个参数,类型为number,返回值也为number。既然限定了类型和数量,那就应该遵守,不然的话就会报错
let c :(a:number,b:number)=>number
c=function(n1:number,n2:number):number{
return n1+n2
}
2.4数组
数组的类型声明
类型[ ]
Array<类型>
string[]表示字符串数组
number[]表示数值数组
let e:string[]
e=['a','b','c']
let f:number[]
let g:Array<number>
g=[1,2,3]
2.5元组
元组:固定长度的数组。与数组相比,元组类型效率更高
语法:[类型,类型,......]
let h:[string,number]
h=['hello',123]
2.6枚举
设置多个值,最后的结果需要在多个值之间选择的时候就需要使用枚举
enum Gender{
Male=0,
Female=1
}
let i:{name:string,gender:Gender}
i={
name:'孙悟空',
gender:Gender.Male
}
console.log(i.gender===Gender.Male) //true
类型的别名
给所需要指定的类型起一个别名,然后这样就可以减少代码的书写量
type myType=1|2|3|4|5
let k:myType
let l:myType
3.TS编译
想要实时监控代码的变化,可以输入命令行tsc 文件名 -w
如果想要编译整个项目,那么就利用命令行tsc来去编译
tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
(1) “include”用来指定那些文件需要编译
路径:**表示任意目录
*表示任意文件
(2) "exclude" 定义不需要被编译的文件目录
默认值:["node_modules","bower_components","jspm_packages"]
(3)"extends":定义被继承的配置文件
(4)“files”:指定被编译文件的列表,只有需要编译的文件少时才会用到
3.1compilerOptions编译器的选项
"compilerOptions": {
// target 用来指定ts被编译为ES的版本
"target": "ES6",
// module指定要使用的模块化的规范
// none,commonjs,amd,system,umd,es6,es2015
"module": "system",
// lib用来制定项目中使用的库,一般情况中不需要修改
// outDir用来指定编译后文件所在的目录
"outDir": "./dist",
// outFile将代码合并为一个文件
"outFile": "./dist/app.js",
// 是否对js文件进行编译,默认值为false
"allowJs": false,
// 是否检查js代码是否符合语法规范,默认值为false
"checkJs": false,
// 是否移除注释
"removeComments": true,
// 不生成编译后的文件
// "noEmit": false,
// 当有错误时不生成编译后的文件
"noEmitOnError": true,
// 所有严格检查的总开关,这个一开,其他的都为true
"strict": true,
// 用来设置编译后的文件是否使用严格模式,默认为false
"alwaysStrict": false,
//是否允许隐式隐式的any类型
"noImplicitAny": false,
// 是否允许不明确类型的this
"noImplicitThis": true,
// 是否严格检查空值
"strictNullChecks": false,
}
4.webpack打包ts代码
步骤
(1)输入命令行npm init -y 进行初始化
(2)输入命令行npm i -D webpack webpack-cli typescript ts-loader安装这四个依赖
这四个包分别的作用是构建工具webpack,webpack的命令行工具,ts编译器,ts加载器,用于在webpack中编译ts文件
(3)在根目录下面创建一个webpack.config.js文件配置以下内容
// 配置文件
// 引入一个包
const path=require('path')
// webpack中所有的配置信息都应该写在module.exports中
module.exports={
// 指定入口文件
entry:"./src/index.ts",
// 指定打包文件所在的目录
output:{
// 指定打包文件的目录
path:path.resolve(__dirname,'dist'),
// 打包后文件的名字
filename:'bundle.js'
},
// 指定webpack打包时要使用的模块
module:{
// 指定要加载的规则
rules:[
{
// test指定规则生效的文件
test:/\.ts$/,
// 要使用的loader
use:'ts-loader',
// 要排除的文件
exclude:/node-modules/
}
]
}
}
(4)在根目录下面创建tsconfig.json配置以下内容
{
"compilerOptions": {
"module": "ES2015",
"target": "ES6",
"strict": false
}
}
(5)在package.json文件夹中添加内容。第七行代码后面添加"build": "webpack --mode development"
(6)命令行输入npm i -D html-webpack-plugin ,它是webpack中html插件,用来自动创建html文 件
在webpack.config,js中配置添加一下代码
// 引入html插件
const HTMLWebpackPlugin=require('html-webpack-plugin')
modules.export={
// 配置webpack插件
plugins:[
new HTMLWebpackPlugin({
//里面配置title,可以为HTML文件重新命名title
title:'这是一个新建的文件'
//配置HTML的模板,按照自己设置的模板生成
template:'./src/index.html'
})
]
}
(7)命令行输入npm i-D webpack-dev-server 开发服务器,这就相当于在项目当中安装一个内置服务器,将其装上之后可以让项目直接自动运行
在安装完成之后在package.json配置文件中添加以下代码,它是在之前添加build代码的后面添加。"start": "webpack serve --open --mode production"
(8)命令行输入npm i -D clean-webpack-plugin, 它是webpack中的清除插件,每次构建都会先清除目录。它的使用与上面的HTML插件的使用一样
// 引入clean插件
const {CleanWebpackPlugin}=require('clean-webpack-plugin')
plugins:[
new CleanWebpackPlugin(),
]
(9)此时想要创建一个新的ts文件,然后运行,这个时候就需要将其引入到index.ts文件当中,但是在这个时候运行的时候会报错,没有指定文件的类型。所以说这个时候就需要在webpack.config.js中配置以下代码
module exports{
// 用来设置引用模块
resolve:{
extensions:['.ts','.js']
}
}
5.babel
如果直接完成上述配置的话,是处理不了代码在各个浏览器中的兼容性问题的,所以说这个时候就需要用到babel。babel 的作用是将新语法转换为旧语法,将一些新的技术新的类在旧的浏览器中不支持的可以通过一些方式来使其支持。
输入命令行npm i -D @babel/core @babel/preset-env babel-loader core-js 安装四个依赖
这四个依赖分别是babel的核心工具,babel的预定义环境,babel在webpack中的加载器,core-js用来是老版本的浏览器支持新版ES语法。
在module中添加以下代码
// 指定webpack打包时要使用的模块
module:{
// 指定要加载的规则
rules:[
{
// test指定规则生效的文件
test:/\.ts$/,
// 要使用的loader
use:[
{
// 指定加载器
loader:'babel-loader',
// 设置babel
options:{
// 设置预定义的环境
presets:[
[
// 指定环境的插件
"@babel/preset-env",
// 配置信息
{
// 要兼容的目标浏览器
targets:{
"chrome":'87',
"ie":11
},
// 指定corejs的版本
"corejs":"3",
// 使用corejs的方式“usage”,表示按需加载
"useBuiltIns":"usage"
}
]
]
}
},
'ts-loader'],
// 要排除的文件
exclude:/node-modules/
}
]
},
6.面向对象
程序之中所有的操作都需要通过对象完成,也就是所谓的面向对象。
这一部分与es6语法密切相关ES6语法核心基础知识(1)_.luluHoney的博客-CSDN博客
6.1类
类可以理解为对象的模型,程序中可以根据类创建指定类型的对象
使用class定义类
class 类名{
属性名:类型
constructor(参数:类型){
this.属性名=参数
}
方法名(){
....
}
}
在属性前面加上static关键字可以定义类属性
在属性前面加上readonly关键字代表只读属性,就不能够对其进行赋值,否则就会报错
注意 如果这两个关键字一起使用的话就需要将readonly放在static后面。
class Person{
/*
直接定义的属性是实例属性,需要通过对象的实例去访问
const per=new Person()
per.name
使用static开头的属性是静态属性(类属性),可以直接通过类去访问
Person.age
*/
// 定义实例属性
name:string="孙悟空"
// 在属性前使用static关键字可以定义类属性(静态属性)
static age:number=18
//定义方法
//如果方法以static开头则方法就是类方法,可以直接通过类去调用
sayHello(){
console.log('hihi')
}
}
const per=new Person()
console.log(per);
console.log(Person.age);
6.2构造函数
在实例方法中,this指的就是当前所创建的实例,this.name中的this指的就是dog实例
class Dog{
name:string;
age:number;
// constructor 被称为构造函数
// 构造函数会在对象创建时调用
constructor(name:string,age:number){
// 在实例方法中,this就表示当前的实例
// 在构造函数中当前对象就是当前新建的那个对象
this.name=name
this.age=age
// console.log('构造函数执行了');
}
bark(){
// 在方法中可以通过this来表示当前调用方法的对象
console.log(this.name);
}
}
const dog=new Dog("hello",4)
const dog1=new Dog("小黑",8)
const dog2=new Dog("花花",8)
console.log(dog);
console.log(dog1);
console.log(dog2);
6.3继承
之所以将执行的函数放在匿名函数当中是因为多个文件夹有类名冲突,通过匿名函数可以形成一个作用域避免冲突
使用extends关键字
通过继承可以将多个类中共有的代码写在一个父类中,这样只需要写一次就可以让所有的子类同时拥有父类中的属性和方法,如果希望在子类中添加一些父类中没有的属性或方法就直接添加在子类中。
如果在子类中添加了与父类一样的方法,那么子类中的方法会覆盖父类中的方法,这种子类覆盖掉父类方法的形式,就称为方法重写
(function(){
// 定义一个Animal类
class Animal{
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age
}
sayHello(){
console.log("动物在叫");
}
}
// 定义一个表示狗的的类
class Dog extends Animal{
run(){
console.log(`${this.name}在跑`);
}
}
// 定义一个表示猫的类
class Cat extends Animal{
}
const dog=new Dog("旺财",5)
const cat=new Cat("花花",3)
console.log(dog.run());
console.log(cat);
dog.sayHello()
})()
使用super关键字
super关键字就把它当做父类。如果在子类中写了构造函数,那么在子类的构造函数中必须对父类的构造函数进行调用
(function () {
class Animal{
name:string;
constructor(name:string){
this.name=name
}
sayHello(){
console.log('动物在叫');
}
}
class Dog extends Animal{
age:number
constructor(name:string,age:number){
// 如果在子类中写了构造函数,在子类构造函数中必须对父类构造函数进行调用
super(name)//调用父类
this.age=age
}
sayHello(){
// 在类的方法中,super就可以当做当前类的父类
super.sayHello();
}
}
const dog=new Dog("旺财",3)
dog.sayHello()
})()
7.抽象类
以abstract开头的类是抽象类。抽象类和其他类的区别不大,只是不能创建对象,抽象类是用来专门做继承的类。
抽象类中可以用来添加抽象方法, 抽象方法使用abstract开头,没有方法体。 抽象方法只能定义在抽象类中,子类必须要对抽象方法重写。如果在子类中继承抽象父类就会报错。
(function () {
abstract class Animal{
name:string;
constructor(name:string){
this.name=name
}
// 定义一个抽象方法
// 抽象方法使用abstract开头,没有方法体
// 抽象方法只能定义在抽象类中,子类必须要对抽象方法重写
abstract sayHello():void;
}
class Dog extends Animal{
// 此处不能没有内容,不然就会报错。因为它是继承不了抽象类中的方法的
sayHello(){
console.log('汪汪汪');
}
}
const dog=new Dog("旺财")
dog.sayHello()
})()
8.接口
接口与type和抽象类相似,用interface开头。
接口用来定义一个类的结构,定义一个类中包含哪些属性和方法。 同时接口也可以当成类型声明去使用。注意接口是可以重复声明的,但是type不行,type重复声明就会报错。
接口有什么用:接口定义了一个规范,只要实现了接口,那就意味着满足了规范,就可以在规定的场景中去使用,所以说接口是对类的限制
接口与抽象类的区别
1.在抽象类中可以用普通的方法,也可以用抽象类的方法。在接口中都是抽象类的方法。
2.在抽象类中使用的是继承extends,在接口中使用的是implements
3.接口是定义一个类,规定某个标准。
(function(){
// 描述对象的类型
type myType={
name:string,
age:number
}
/*
接口用来定义一个类的结构,定义一个类中包含哪些属性和方法
同时接口也可以当成类型声明去使用
*/
interface myInterface{
name:string
age:number
}
interface myInterface{
gender:string
}
/* const obj:myInterface={
name:'麻子',
age:11,
gender:'男'
} */
/*
接口可以在定义类的时候去限制类的结构
接口中所有的属性都不能有实际的值
接口只定义对象的结构,而不考虑实际值,在接口中所有的方法都是抽象方法
*/
interface myInter{
name:string;
sayHello():void;
}
/*
定义类时,可以使类去实现一个接口,实现接口就是使类满足接口的需求
*/
class MyClass implements myInter{
name:string
constructor(name:string){
this.name=name
}
sayHello(): void {
console.log('大家好');
}
}
})()
9.属性封装
在js中属性是直接在对象中设置的,属性可以被任意的修改,属性会被任意修改将会导致对象中的数据变得非常不安全,所以说在TS中就涉及到以下一些属性的修饰符
private 私有属性,只能在类的内部进行修改,外部访问不了。设置为私有变量依旧会编译,可以在配置文件中添加 "noEmitOnError": true
public 公共属性,他修饰的属性可以在任意位置修改(默认值)。在Ts中是可以直接将属性设置在构造函数当中的,使用public属性
protected 受保护的属性,只能在当前类和当前类的子类中访问(修改),不能在类之外访问
可以通过在类中添加私有属性可以被外部访问。getter方法用来读取属性,setter方法用来设置属性
TS中设置getter方法的方式
(function(){
// 定义一个表示人的类
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
} */
// TS中设置getter方法的方式
get name(){
return this.name
}
set name(value:string){
this.name=value
}
}
// const per=new Person('孙悟空',18)
// per.setName('猪八戒')
// console.log(per.getName());
// console.log(per.name);
class C{
// 可以直接将属性定义在构造函数中
constructor(public name:string,public age:number){
}
}
const c=new C('孙悟空',18)
}())
10.泛型
在定义函数或者类时,如果遇到类型不明确的时候就可以使用泛型。使用方法,在函数后面使用<X> 这里的X就代表一个泛型,这个X在执行的时候才可以确定。那么既然是这样的话为什么不使用any,因为使用any的话就会避开TS的严格检查,就会毫无意义。
泛型可以指定也可以不指定,同时可以指定多个泛型
function fn<T>(a:T):T{
return a
}
//可以直接调用具有泛型的函数
fn(a:10);//不指定泛型,TS可以自动对类型进行判断
fn<string>(a:'hello')//指定泛型
function fn2<T,K>(a:T,b:K):T{
console.log(b);
return a
}
fn2<number,string>(123,'hello')