简介
TypeScript是Javascript的超集- 他对
javascript进行了扩展,向Js添加了类型的概念,并增加了许多的特性 TS代码需要由编译器编译为JS,然后由JS解析器执行TS完全兼容JSTS拥有静态类型,有更加严格的语法,更强大的功能TS可以在代码执行之前就完成对代码的检查,减少运行时异常的出现TS代码可以翻译为任意版本的JS代码,可以解决不同JS运行环境的兼容性问题
基本类型
类型声明
-
类型声明是
TS非常重要的一个特点 -
当你为一个变量(形参,实参)声明了类型时,当你赋值时,TS就会检查这个参数是不是满足你刚刚声明的类型
-
类型声明给变量声明了类型,那么以后变量就只可以用这个一种类型
let 变量:类型; let 变量 = 值;//会根据你的值的类型,来判断你的变量的类型 function fn(参数:类型,参数:类型): 类型{ }
| 类型 | 例子 | 描述 |
|---|---|---|
number | 1,2,-1,0,2.5 | 任意数字 |
string | "hi","hello" | 任意字符串 |
boolean | true,false | 布尔值true和false |
字面量 | 就为设置的字面量 | 限制了这个值就只可以是这个值 |
any | 任意类型,TS就不会管这个变量了就和 JS的变量一样 | |
unknown | 他可以理解为是类型安全的any | |
void | 函数的返回值为空值 | |
never | 没有值 | 不可以是任何值 |
object | {name:'summer'} | 任意的js对象 |
array | [1,2,3] | 任意js数组 |
tuple | [4,5] | 固定好长度的数组 |
enum | enum{A,B} | 枚举,TS中新增类型 |
number类型
let num : number;//声明一个变量,同时指定他为Number
//此时你num的类型就已经固定好了,那么就不允许你再去搞其他的类型
let num = 123;//那么他就会直接认为num时number类型的
//当变量的声明和赋值同时进行时,TS会自动检测类型
string类型
let name:string;
boolean类型
let sex:boolean;
字面量
// 可以使用字面量进行类型声明
// 他只是一种声明,他并不是赋值
// 也就是他赋值只可以为10
let a: 10;
console.log("a",a);//undefined
//联合类型
let sex:'male'|'female'
//也就是sex1的值只可以在这两者之间选择,不可以赋值为其他值
any任何类型
- 显式
any
let sth:any;//sth可以为任何类型,也就是这个变量之后的赋值,TS都不会再管他了(类型检查)
- 隐式
any
let sth;//当我们进行声明,但是没有指定类型时,我们会认为这个变量类型为any
unknown类型
//unknown可以理解为类型安全的any
//他和any一样可以赋值为任何类型,但是唯一的不同时,unkown类型不可以赋值给别人
let e:unknown;
let test:string;
//下面会发生报错
test = e;//但是我们就不可以对unknown直接进行赋值
//意思就是,你unknown因为类型不会受到限制,但他只允许你一个人不受到显示,unknown不准你去霍霍其他人
//原因是你赋值的时候,e还是不知道你的类型是什么
//但是any不一样,any可以霍霍别人
let f:any;
test = f;//这边就不会发生报错
//所以正对这种情况,我们需要使用类型断言
-
类型断言(针对
unknown类型不能进行赋值问题)变量
as类型<类型>变量
let e:unknown;
e="summer";
let name:string;
//意思就是明确告诉TS编辑器,我这个e就是string类型的
name = e as string//这样就不会报错
//或者
name = <string>e
void类型
当我们函数没有返回值时,我们最好使用void
只要使用了void,就不允许你再有返回值了
注意ts认为,return null 和 return nudefined ,都是空的
function plus(a:number,b:number):void{
console.log(a+b)
}
// 注意ts认为,return null 和 return nudefined ,都是空的
function add(a, b):void {
// return 'hello';//报错
// return null//就是认为空的
// return undefined//认为是空的
}
假如我们的函数没有写返回值类型,那么TS编辑器会根据你的return来推测你的返回值类型
假如你没有写return,那么TS编辑器会认为你的返回值类型为void
function fn() {
// return "hello"
// return null
}
never类型
表示他永远不会有返回结果,一般用来抛出异常之类的
function fn2(): never{
throw new Error("报错")
}
object类型
object类型包含了太多的类型,Array,function等
所以我们对于object类型一般写的更加仔细一点
- 普通对象类型
注意顺序
let obj1 :{name:string}
//意思就是:我们声明了一个变量obj1,他是一个对象,且他只包含一个属性name,且属性值类型为string
obj={name:"summer"}
//如果你想要更多的属性,就需要扩充类型限制
let obj2 :{name:string,age:number,hobby:object}
obj2 = { name: 'summer', age: 18, hobby: { a: 2 } }
//简便方法
//[propName:string]:any,这样就可以增加很多的属性了
//含义就是属性名为字符串类型,属性值类型可以是任意
let obj3: { name: string, [propName: string]: any }//obj3一定要有name属性,其他都可以
//而且一定要注意顺序
// 当我们有两个必须使用的属性,可以写为&
// obj4,有且仅有name和age属性
let obj4: { name: string } & { age: number }
obj4 = {name:"summer",age:12}
-
函数类型
我们使用一种类似于箭头函数的类型声明方式
注意,你的参数在类型声明时写了几个,那么你后续也只可以使用这几个,且类型保持相同
// 意思就是,fun3是一个函数,且,函数有两个参数,且参数的返回值必须是number
let fun3: (a: number, b: number) => number
// 上面的其实就是一种声明了,你下面进行的就是赋值操作而已
fun3 = (n1, n2) => {
// 函数必须和刚刚声明的类型保持一致
return n1+n2
}
console.log(fun3(2,3));
-
数组类型
声明方式
类型[]Array<类型>
//1.
let arr:number[]//表明arr只能是一个number类型的数组,里面的数据类型只可以是number,不可以是其他的
arr=[1,2,3]
//2.
let arr1:Array<number>
arr1 = [1,2,3]
元组类型:TS新增
元组其实就是固定长度的数组
let t:[number,string]//表示声明一个元组,元组里两个,一个是number,一个是string,且注意书写顺序
h = ['hello', 12]
// h = [111,'summer'],这样就会报错
enum枚举类型
一般用于值相对固定
enum Gender{
male,
female
}
// 限制类型
// i是一个对象,含有name属性和sex属性,其中sex属性是一个枚举
let i: { name: string, sex: Gender }
// 因为在实际的开发中,我们的性别这种一般都是用,0,1表示的
i = {
name: 'summer',
sex:Gender.male
}
console.log(i.sex === Gender.male);//true
类型别名
// 类型别名
type myType = 1 | 2 | 3 | 4;
let m: myType
let n: myType
类型断言
有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型
-
第一种
-
let someValue: unknown = "this is a string"; let strLength: number = (someValue as string).length;
-
-
第二种
-
let someValue: unknown = "this is a string"; let strLength: number = (<string>someValue).length;
-
编译选项
-
自动编译文件
只需要在全局添加-w
tsc ./xxx.ts -w //这个时候我们就可以监听这个xxx.ts文件的变化了,只要发生了变化,那么js对应文件也会发生变化 -
自动编译整个项目
只输入tsc,那么就会编译当前所有的ts文件
要是有
tsc的前提,就是需要配置一个tsconfig.json文件夹{ // ts编译器的配置文件 // includes,用来指定哪些文件需要tsc进行编译 "include": [ // "./src/**/*",意思是,src文件夹下的任意目录的任意文件 // 一个*表示任意文件,两个*表示任意目录 "./src/**/*" ], //exclude表示这里面的文件是不需要编译的 // exclude含有默认值:['node_modules','bower_components','jspm_packages'] "exclude": [ ], // 一些配置选项 "compilerOptions": { // 用来指定会被编译为什么版本的语言 "target": "ES6", // 模块化,使用的是那个模块化 // "module": "ES6", // 用来指定库,一般我们不去设置 // "lib": [] // outDir,指定文件所在的目录,一般我们放在根目录下的dist文件里 "outDir": "./dist", // outFile,将编译好的js代码放在一个js文件里面 // 这个不可以和module一起使用,除非你的modile应该是amd和system // 注意好变量名字不要相同 "outFile": "./dist/index.js", // 是否对我们的js文件进行编译,默认为false "allowJs": false, // 是否检查js,以ts的标准去检查js "checkJs": false, // 是否删除注释,true:就是删除注释 "removeComments": false, // 他就是严格检查的总开关,假如true,那么才会进行下面的检查,如果是false,那么直接就是不进行任何的严格检查 "strict": false, // 不生产编译后的文件(js)文件 "noEmit": false, // 当有错误不生成编译后的文件 // 只要是发生了错误,那么就不会生成js文件 "noEmitOnError": true, // 是否开启严格模式,默认是不开启严格模式的 "alwaysStrict":true, // 当我们没有指定变量的类型时,我们是否使用any类型 // 就是不允许隐式的any,类如函数传参没有写类型 "noImplicitAny": true, // 不允许有指定不明确的this "noImplicitThis": true, // 严格的检查空值,对于一些值,他们可能为空 "strictNullChecks": false } }配置项解读:
-
include定义希望被编译的文件所在目录
"include":['./src/**/*'];//意思就是,会编译src文件夹下的任意文件夹里面的任意文件 -
exclude定义不希望被编译的文件所在目录
默认值:
["node_modules", "bower_components", "jspm_packages"]"exclude":['node_modules']//不会编译node-modules文件夹 -
extends定义被继承的配置文件
"entends":['./config/base']//当前配置文件中会自动包含`config`文件夹下base.json文件里面的所有配置信息 -
compilerOptions(重要)他里面有很多的子项,里面定义的是一些配置选项
-
target:用来指定会编译为何种版本,一般我们使用ES6"compileOptions":{ "target":'ES6';//我们指定转化为ES6这种版本 } -
module:模块化,用于import,export的格式"compileOptions":{ "module":'ES6';//我们按照ES6的格式进行模块化的引用等 } -
lib:指定库,我们一般不会去动 -
outDir:指定文件所在的目录,一般我们放在dist文件夹下"compileOptions":{ "outDir":"./dist" } -
outFile:将编译好的js文件都放在一个js文件里面方便管理"compileOptions":{ "outFile":'./dist/index.js';//我们将编译好的js文件全部放在dist文加夹下的index.js文件里 } -
checkJs:是否以TS的标准去检查JS,默认为false"compileOptions":{ "checkJS":true } -
removeComment:是否删除注释,就是在TS转化为JS过程中,我们是不是将注释也保留"compileOptions":{ "removeComment":true;//确定删除注释 } -
strict:严格模式的总开关,假如他是关着的,那么后面的所有严格模式都会关闭"compileOptions":{ "strict":true;//严格模式开启 } -
noEmit:编译后不产生js文件,默认为false -
noEmitOnError:只要产生了错误,那么我们就不编译产生js文件 -
noImplicitAny:当我们没有指定类型时,是不是使用Any"compileOptions":{ "noImplicitAny": true, // 就是不允许隐式的any,类如函数传参没有写类型 }
-
-
webpack配置
步骤:
-
初始化项目
npm init -y:在创建package.json文件 -
下载构建工具
npm i -D webpack webpack-cli typescript ts-loader -
构建一个
webpack的配置文件webpack.config.js文件// 引入一个包 // 这个包主要是让和我们来管理路径,路径拼接什么 const path = require('path') // 引入wenpack插件 const HTMLWebpackPlugin = require('html-webpack-plugin') // webpack所有的配置信息都应该写在里面 module.exports = { // 指定入口文件 entry: './src/index.ts', // 指定打包文件的所在目录 output: { // 指定到文件的目录 path: path.resolve(__dirname, 'dist/assets'), // 指定打包好的文件名字 filename: 'bundle.js', clean: true }, // webpack打包时要使用的模块 module: { // 指定加载的规则 rules: [ { // tset指定的是规则生效的文件 test: /\.ts$/,//匹配所有以.ts结尾的文件 use: [ // 配置babel // 指定加载器 'babel-loader', 'ts-loader' ],//使用ts-loader去匹配所有以.ts结尾的文件 // 指定要排除的文件 exclude: /node_modules/ } ] }, // 配置webpack插件 plugins: [ new HTMLWebpackPlugin({ // title:'webpack配置' // 设置一个模板 template: './src/index.html' }),//效果就是自动的帮我们创建一个Html文件,并且将相关的资源引入进来 ], devServer: { static: { directory: path.join(__dirname, 'dist') }, compress: true, port: 9000 }, // 用来设置引用模块 // 意思就是后续我们再次进行import的时候,因为import不可以时以ts结尾的,有了这个,我们就可以不写后缀名 resolve: { extensions: ['.ts', '.js'] } } -
随后我们继续安装一个插件
html-webpack-plugin效果就是帮助我们自动生成一些
html文件,安装好了以后我们就不需要自己去进行引入他需要进行在
webpack.config.js文件中引用// 引入wenpack插件 const HTMLWebpackPlugin = require('html-webpack-plugin') module.export = { // 配置webpack插件 plugins: [ new HTMLWebpackPlugin({ // title:'webpack配置' // 设置一个模板 template: './src/index.html' }),//效果就是自动的帮我们创建一个Html文件,并且将相关的资源引入进来 ], } -
安装一个
webpack的服务器插件,用于实时的看到页面的变化同时也需要在
package.json里面进行配置{ ...., "scripts": { "start": "webpack serve --mode production" } }并且在
webpack.config.js里面进行配置端口号之类的module.export = { ..., devServer: { static: { directory: path.join(__dirname, 'dist') }, compress: true, //规定端口号为9000 port: 9000 } } -
在根目录下创建
tscofig.json他也是进行对TS编辑器的一些配置
{ "include": ["./src/**/*"], "compilerOptions": { "target": "ES6", // 所有编译的ts文件放在dist文件夹里 // "outDir": "./dist", "strict": true } } -
在
src下创建ts文件,并执行命令,npm start来开启服务器,或者npm run build对代码进行编译
babel
步骤:
-
安装依赖包npm install -D babel-loader @babel/core @babel/preset-env -
loader配置直接去修改
webpack.config.js// webpack打包时要使用的模块 module: { // 指定加载的规则 rules: [ { // tset指定的是规则生效的文件 test: /\.ts$/,//匹配所有以.ts结尾的文件 use: [ // 配置babel // 指定加载器 'babel-loader', 'ts-loader' ],//使用ts-loader去匹配所有以.ts结尾的文件 // 指定要排除的文件 exclude: /node_modules/ } ] }, -
安装依赖
core-jsnpm i -D core-js@3 -
babel配置一般babel配置,我们都是放在
babel.config.json文件里{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", // 按需引入 corejs 中的模块 "corejs": 3, // 核心 js 版本 "targets": "> 0.25%, not dead" // 浏览器支持范围 }] ] }
面向对象
类(class)
//使用class关键字来定义一个类
class Person{
//定义实例属性
name:string = 'summer';
//一定需要赋值,如果不赋值,那么就需要使用构造函数,就是传了值
//定义static属性,这是类的的属性,所以不需要新建对象也可以访问
static sex:string = "male"
//设置只读实例属性,那么他就是可以被访问,但是不可以被修改了
readonly age:number = 12
//定义方法,如果是以static开头的,那么就是类方法,同样对象无法访问
sayHello() {
console.log("hello");
}
static eat() {
console.log("I can eat");
}
}
//新建一个对象
const person = new Person();
console.log(person);
console.log(person.age, person.name);
//因为age使实例属性,所以可以直接访问和修改
person.age = 14
console.log(person.age);
// 因为name设置了readonly,所以我们只可以通过类本身去访问
console.log(Person.sex);//这种属于静态属性,也就是类上面的属性,不是对象身上的
// person.age = 13,就会报错,因为age是只读属性
// 实例方法的调用
person.sayHello()
// 静态方法的调用
Person.eat()
构造函数(constructor)
// 定义构造函数
class Student{
// 也可以理解为,你的声明是声明在外边的,但是你的赋值,是在构造函数里面进行的
name: string;
age:number
// 在定义类的时候,这个值是不可以不赋值的
constructor(name:string,age:number) {
// 构造函数,在对象创建时,调用constructor
// 每次调用,都会调用一次构造函数
// 构造函数里的this,是指向那么对象,谁调用了,那么就指向谁
console.log("this", this);
// console.log("性别",this.sex);
this.name = name;
this.age = age;
}
// 定义一个方法
study() {
console.log("studying");
// this说到底就是一句话,谁调用他,他就指向谁
// 在构造函数里,new的时候会调用this,那么这个this,就指向了那个类
// 但是现在是student.study(),是student这个对象去调用的,所以this,就是student这个对象
console.log("study方法里面的this",this);
}
}
const student = new Student("summer",13)
console.log(student);
student.study()
继承(extends)
我们使用继承来尽可能的增加复用
class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age
}
say() {
console.log("动物在叫");
}
}
// Dog类继承了Animal类
// 使用继承以后,子类将会拥有父类所有的方法和属性
class Dog extends Animal {
bark() {
console.log("汪汪哇");
}
// 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法
// 这种子类覆盖掉父类方法的形式,我们称为方法重写
say(): void {
console.log(`${this.name} 在叫`);
}
}
class Cat extends Animal {
miao() {
console.log("miao");
}
}
const dog = new Dog("旺财", 1);
const cat = new Cat("咪咪", 2);
console.log(dog);
dog.say()
cat.say()
console.log(cat);
方法重写
子类继承了父类的方法,但是对方法进行重写
那么子类会覆盖掉父类的方法
抽象类(abstract)
一般我们会使用一个类来进行继承,但是我们不希望这个父类可以使用,所以我们定义了抽象类
当我们不希望这个类不用于创建对象,只用于继承
super
super:super指向的就是他的父类
abstruct
abstruct:我们里面的方法只进行定义,不写里面的函数体,到时候,子类里进行方法重写
抽象方法只写关于定义,但是不写方法体,需要对象进行重写
abstract class OneAnimal{
name: string;
constructor(name:string) {
this.name = name;
}
// 定义了抽象方法,抽象方法使用abstract开头,没有方法体
// 抽象犯法只能定义在抽象类中,子类必须对抽象方法进行重写
abstract say(): void;
}
// 继承,并且对方法进行重写
class OneDog extends OneAnimal{
// 如果在子类里面写了构造函数,那么就需要使用super,对父类的构造函数进行调用
// 并且父类中有哪些参数,我们子类的构造函数也需要调用,并且super里面也要写明哪些参数是来自父组件的
age: number;
constructor(name:string,age:number) {
super(name);
this.age = age
}
say(): void {
// super,就是当前OneDog类的父类,他就会直接调用父类的say方法
// super.say()
console.log(`${this.name}在说话`);
}
}
const onedog = new OneDog('旺财',12)
onedog.say()
当我们使用继承,并且使用构造函数时,我们需要使用super,并且里面传入参数,表明哪些是来自父组件的
接口(interface)
接口可以定义类的时候去限制类里面的结构
接口中所有的属性都不能赋实际的值
接口里面只是定义了结构,但是不考虑实际的值
实现接口使用的时
implements
接口就是来定义一个类的结构,它也可以当作类型声明来使用,就是那个type来使用
// 接口属于ts新增的
interface myInterface{
// 里面不写值,只是去定义类型
name: string;
age: number;
gender:string
}
// 定义类时,可以使类去实现一个接口,使用的使implements
class People implements myInterface{
name: string;
age: number;
gender: string;
constructor(name: string, age: number, gender: string) {
this.gender = gender;
this.age = age
this.name = name
}
}
抽象类VS接口
- 抽象类和接口一样,都只可以定义,但是不可以进行赋值
- 抽象类和接口都不可以直接使用,抽象类需要继承(extends),接口需要实现(implements)
- 不同:抽象类里可以定义普通方法也可以定义抽象方法,但是接口里面值只可以定义抽象方法
属性封装
| 修饰符 | 描述 |
|---|---|
public | 修饰符的属性可以在任意位置访问(修改)默认值 |
private | 私有属性,私有属性只能在类内部进行访问(修改) 只能在类中get和set,才可以被访问 |
protect | 受保护属性,这个只有当前类和其类的子类可以访问 |
class Person{
private _name: string;
private _age: number;
constructor(name: string, age: number) {
//设置了private,那么他只可以在类的内部进行访问,但是如果不在这个类里面,变为了一个对象去访问这个私有属性,那么就访问不到页修改不了
this._name = name;
this._age = age;
}
// TS中设置getter方法的方式
// 获取属性值使用
get name(){
// console.log('get name()执行了!!');
return this._name;
}
//设置属性值时使用
set name(value){
this._name = value;
}
get age(){
return this._age;
}
set age(value){
if(value >= 0){
this._age = value
}
}
}
const per = new Person('孙悟空', 18);
// 因为设置了set和get,所以对于私有属性才可以访问
// 因为但是我们访问的就算不是_name,和_age这个,而是get,set的哪些方法名
per.name = '猪八戒';
per.age = -33;
console.log(pre)
protect只可以被子类和自己使用
class A{
protected num: number;
constructor(num: number) {
this.num = num;
}
}
class B extends A{
test(){
console.log(this.num);
}
}
const b = new B(123);
b.num = 11;//报错,因为他只可以在A类和B类使用,不可以再其他地方
简单写属性
/* class C{
name: string;
age: number
// 可以直接将属性定义在构造函数中
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}*/
class C{
// 可以直接将属性定义在构造函数中
//这是简写构造函数
constructor(public name: string, public age: number) {
}
}
const c = new C('xxx', 111);
console.log(c);
泛型
在定义函数或者类时,如果遇到类型不明确就可以使用泛型
泛型其实就可以理解为一种类型,只是目前我们不知道,并且它是以<>括起来的
function fn<T>(a: T): T{
return a;
}
// 可以直接调用具有泛型的函数
let result = fn(10); // 不指定泛型,TS可以自动对类型进行推断
let result2 = fn<string>('hello'); // 指定泛型
多个泛型参数
// 泛型可以同时指定多个
//中间使用,隔开
function fn2<T, K>(a: T, b: K):T{
console.log(b);
return a;
}
fn2<number, string>(123, 'hello');
对泛型进行限制
interface Inter{
length: number;
}
// T extends Inter 表示泛型T必须时Inter实现类(子类)
// 泛型限制类型,都是使用entends进行继承,无论是接口还是类
function fn3<T extends Inter>(a: T): number{
return a.length;
}
泛型可以在类里面使用
class MyClass<T>{
name: T;
constructor(name: T) {
this.name = name;
}
}
const mc = new MyClass<string>('孙悟空');