目录
一、TypeScript是什么,为什么要使用它?:
产生背景:
为何使用:
二、TS与JS的区别:
表格总结:
三、TypeScript 开发环境搭建
四、基本类型:
TS中的所有数据类型:
字面量:
any类型:
unknown:
void和never:
object:
array:
tuple:
enum:
symbol:
五、编译选项
自动编译文件:
自动编译整个项目
include
exclude
extends
files
compilerOptions
strict
六 webpack打包
最基本配置
entry
output
module
rules
进阶配置
考虑浏览器兼容问题
七、面相对象
八、类(class)
九、构造函数(方法)
十、类的继承(面向对象重点)
十一、super关键字
十二、抽象类
十三、接口
接口和类的区别:
十四、属性的封装
public
private
protected
getter
setter
类的简写
十五、泛型
一、TypeScript是什么,为什么要使用它?:
TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。
产生背景:
TypeScript 起源于使用JavaScript开发的大型项目 。由于JavaScript语言本身的局限性,难以胜任大型项目的开发和维护。因此微软开发了TypeScript ,使得其能够胜任大型项目的开发。
为何使用:
从历史上看,JavaScript已经成为了在Internet上编写网页和应用程序脚本语言的主要语言。但是否能通过JavaScript创建大型复杂Web应用系统呢?可能那么容易。
不过值得庆幸的是,我们还有一个解决方案TypeScript。
在过去的几年中,TypeScript的受欢迎程度一直在增长。在2020年最有前途的五种语言中,它也是其中之一。目前最大的前端框架之一的Angular正在使用TypeScript,而在大约60%的前端程序员正在使用或曾使用过TypeScript,而另外22%的开发者希望尝试使用。
二、TS与JS的区别:
TypeScript 是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程
大白话解释:TS拓展了JS的一些功能,解决了JS的一些缺点
表格总结:
TS与JS的关系:
JS 有的, TS 都有, JS 没有的, TS 也有,毕竟 TS 是 JS 的超集
TS比JS增加了什么:
TS缺点:
不能被浏览器理解,需要被编译成 JS
有学习成本,写习惯了 JS 的我们要上手需要花时间去理解,而且 TS 中有一些概念还是有点难,比如泛型
三、TypeScript 开发环境搭建
1.下载node.js并安装
2.使用npm全局安装TS
可以在cmd或终端输入npm i -g typescript
完成后输入tsc查看是否安装完成
3.创建ts文件
4.执行命令:tsc 文件名.ts
四、基本类型:
自动安装config文件
npm --init
TS中对数据类型要求非常严格,定义数据类型后,不可赋予其它数据类型。
js中函数是不考虑参数的类型和个数的:
TS中函数会定义参数类型:
且形参和实参要一致:
TS中的所有数据类型:
前面三个和js中的一样,重点为以下数据类型
字面量:
字面量创建一但确定了数值,不可更改
可以使用| 来链接多个类型(联合类型),但可以使用为声明的值或类型
any类型:
any表示的是任意类型,一个变量设置类型为any后,相当于对该变量关闭了TS的类型检测
使用后会使得变量和js里一样,所以TS中,我们不建议使用any类型
声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式any)
并不是说不可以使用,不到万不得已,最好别用,能避免尽量避免
类型是any时 ,它可以赋值给任意变量,会影响其他变量
//d的类型是any ,它可以赋值给任意变量
// s = d;
unknown:
未知数据类型,简单来说,当不知道用的变量为何类型时,可用unknown,并且unknown不会影响其他变量,也不能直接赋值给其他变量
但它也可以用其他方式来赋值给其他变量,比如在赋值前进行类型判断
if(typeof e === "string"){
s = e
}
也可以进行类型断言,类型断言可以用来告诉解析器变量的实际类型
语法:
变量 as 类型
<类型>变量
s = e as string;
s = <string>e;
void和never:
设置函数返回值,在TS中函数的返回值类型我们是可以自己设置的,但当我们函数返回是空值时,就可以使用void ,表示没有返回值的函数,当确定为void时,函数里写返回值则会报错,但可以return后面什么也不跟,也可以返回undefinded和null
function fun (): void{
return ;
}
function fun (): void{
return null;
}
function fun (): void{
return undefined;
}
而never表示永远不会有返回值,连undefinded和null都不会返回,一般用来提示报错,使用较少
function fun (): never{
throw new Error('报错了!')
}
object:
object一般情况直接给是没有任何限制的,可以给
`let a : object;`
`a = {};`
`a = function(){}`
{}用来指定对象中可以包含的属性
语法:{属性名:属性值,属性名:属性值}
let b : {name:string};
b = {name:'孙悟空'}
但给几个属性,赋值就是几个属性,且属性值数据类型要和声明的一致,当需要多个属性值,后面跟“,“往后面继续加就好
那么当我声明了两个属性,但我只给了一个属性附了值,我不想给第二个属性赋值,直接写会报错,那我们应该怎么写呢
let b :{ name:string,age?:number};
b = {name:'孙悟空'}
在属性名加上?,表示可选属性
但当我们后面要有多个可选属性时,一个一个往后面加,又会变得很麻烦,此时就用到了
【变量名:string】:any 表示任意类型的属性
let c :{name:string,[propName:string]:any};
c = {name:'猪八戒',age:18,gender:''}
当然,中括号外面跟的是什么数据类型,那么可选属性就是什么数据类型,假如是number属性,那么后续的可选属性只能是number
设置函数结构的类型声明
语法:(形参:类型,形参:类型’...)=>返回值
let d: (a:number,b:number)=>number;
d = function(n1,n2):number{
return n1+n2
}
注意:所给的类型必须和语法中所给的参数类型一样,否则会报错
array:
数组的类型声明:
类型[]
Array<类型>
let e : string[];
e = ['a','b','c'];
let g :Array<number>;
g = [1,2,3]
tuple:
元组 ,就是固定长度的数组,当你数组长度值是固定时,用元组来定义效率会更好
语法:[类型,类型,类型]
let h : [string,number];
h = ['hello',123];
enum:
枚举
在处理数据时,在几个值之间要选的值,直接给值会占用内存,这时可以用到枚举,定义一个枚举类,把它所有的可能都写进去,然后进行判断时,直接用枚举去判断就可以了
enum Gender{
male = 0,
Female = 1
}
let i: {name:string,gender:Gender};
i = {
name:'孙悟空',
gender:Gender.Male
}
console.log(i.gender === Gender.Male )
&表示同时
let j: {name:string}&{age:number};
j = {name:'孙悟空',age:18}
可以固定传值的个数以及类型
类型的别名
type myTypr = 1|2|3|4|5;
let k = myTypr;
let l = myTypr;
let m = myTypr;
symbol:
symbol可以去这样理解,它就和身份证一样,哪怕名字一样,身份证号也是不一样的,也就是说它具有唯一性
主要分为以下几点:
-
使用关键字 symbol定义 Symbol 类型。
-
使用 Symbol()函数创建一个新的 Symbol。
-
每个通过 Symbol() 创建的 Symbol 都是独一无二的,即使描述相同,它们也是不相等的。
-
Symbol 可以用作对象的属性名,用来表示唯一的属性或方法。
但是,symbol在对象里面,由于是去堆里面拿数据的,所以在对象里哪怕是用symbol,最后的对比结果也依然是true
五、编译选项
自动编译文件:
编译文件时,使用 -w指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译
示例: tsc xxx.ts -w
自动编译整个项目
如果直接使用tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件
但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件tsconfig.json
tsconfig.json是一个JSON文件,添加配置文件后,只需要tsc命令即可完成对整个项目的编译
配置选项:
include
定义希望被编译文件所在的目录
默认值:["**/*"]
示例:
"include":["src/**/*","tests/**/*"]
上述示例中,所有src目录和tests目录下的文件都会被编译
exclude
定义需要排除在外的目录
默认值["node_modules","bower_components","jspm_packages"]
示例
"exclude":["./src/hello/**/*"]
上述示例中,src下hello目录下的文件都不会被编译
extends
定义被继承的配置文件
示例
"extends":"./configs/base"
上述示例中,当前配置文件中会自动包含config目录下base.json中的所有配置信息
files
指定被编译文件的列表,只有需要编译的文件少时才会用到
示例:
"files":[
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
compilerOptions
编译器选项,其中的target用来指定ts被编译为的ES的版本,默认为ES3
可以通过target来改变编译的ES版本,可写的版本有'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'.
module用来指定使用的模块化规范,可以写的有
'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node16', 'nodenext'.
lib用来指定项目中要使用的库,可用库为
'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2019.intl', 'es2020.bigint', 'es2020.date', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2020.number', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'es2022.array', 'es2022.error', 'es2022.intl', 'es2022.object', 'es2022.sharedmemory', 'es2022.string', 'es2022.regexp', 'es2023.array', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref', 'decorators', 'decorators.legacy'.
一般不用去设置,它是用来使用提示库的,什么都不写,代表什么提示都没有,甚至会报错,一般代码都会在浏览器中运行,除非是去node.js运行,或其他地方,才可能用到去编辑它的库
outDir用来指定编译后文件所在的目录
outFile将代码合并成一个文件
设置outFile后,所有的全局作用域中的代码会合并到同一个文件中,但其模块化必须是adm或system
allowJs 是否对js文件进行编译,默认是false,为true时,会将默认编译文件夹下的js文件也一同编译
checkJs是否检查js代码是否符合语法规范,默认是false,为true时,会检查js文件是否符合ts语法规范
removeComments是否移除注释, 默认是false,为true时,ts编译成js时,里面的注释会消失
noEmit不生成编译后的文件,默认是false,为true时,tsc命令将不会在编译js文件,一般不用
noEmitOnError当有错误时不生成编译后文件,默认是false,上面讲过哪怕ts写错也任然会编译成js文件,当这个属性为true时,ts写错将不会再编译成js文件
alwaysStrict编译后文件是否使用严格模式,默认为false
noImplicitAny不允许隐式的any类型,默认为false,当我们定义函数时可能会忘记给参数定义类型,参数就会变成隐式any,可ts中并不推荐使用any,所以当其为true时,可帮我们检测所有隐式any并提示我们
noImplicitThis不允许不明确类型的this,默认为false,当我们使用函数this时,却没有规定其类型,就可能导致this指向使用混乱,此时可以将其改为true来排除这种错误出现的可能
strictNullChecks严格检查空值,默认为false,当我们获取dom节点时,可能获取到空节点,但ts并不会报错,将其改为true,可排查该错误
strict
所有严格模式总开关
"compilerOptions": {
"target": "ES3",
"module": "system",
// "lib": [],
"outDir": "./dist",
//"outFile": "./dist/app.js",
"allowJs": true,
"checkJs": true,
"removeComments": true,
// "noEmit": true
"strict": true,
"noEmitOnError": true,
"alwaysStrict": false,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true
}
六 webpack打包
配置package.json命令:npm init -y
安装webpack命令(最初版): npm i -D webpack webpack-cli typescript ts-loader,其中ts-loader是为了将ts和webpack整合到一起的
全部安装完后需要配置一个webpack.config.js文件
最基本配置
webpack中所有的配置信息都应该写在module.exports中
entry
指定入口文件,一般入口文件为src文件下的index.ts
output
指定打包文件所在目录,其里面有很多配置
path指定打包文件目录,可以直接打路径,但最好用它自己的方法拼接来创造文件打包的文件夹
filename 打包后文件的名字,一般为bundle.js
module
指定webpack打包时要使用模块
rules
指定要加载的规则,因为我们要打包的文件类型很杂,可能是ts,js,图片等各种各样的,所有需要我们去指定这个加载的规则,下面代码块中有语法
test指定的是规则生效的文件
use要使用的loader
exclude要排除的文件,一般写node-modules,这样编译打包时就会排除这个文件
这样,我们的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:/\.ts$/,
//要使用的loader
use:'ts-loader',
//要排除的文件
exclude:/node-modules/
}
]
}
}
然后我们还需要配置一个tsconfig.json文件,上面我们都写过了,但我们可能用不到那么多配置,所以我们一般用下面这个配置,下面是一个最基础的tsconfig.json文件配置
//最基本的tsconfig.json文件,后面需要再进行配置
{
"compilerOptions": {
"module":"ES2015",
"target": "ES2015",
"strict": true
}
}
最后我们再去package.json的调试里面加一句 "bulid":"webpack",这样我们就可以用bulid命令去执行这个打包了
进阶配置
使用基础配置打包完后,只会有一个js文件,但我们最后是希望能够实现浏览器的页面效果,当然,我们可以一个个html文件去创建去引入,但是会很麻烦,所以便有了下面的进阶配置
首先我们要先安装一个插件命令为
npm i -D html-webpack-plugin
这个插件可以帮我们自动生成html文件
在package.json中可以查看插件是否安装完成
之后我们要在webpack.config.js中去引入这个插件:
const HTMLWebpackPlugin = require('html-webpack-plugin')
随后我们需要让这个插件去生效,在module后面去配置它
plugins:[
new HTMLWebpackPlugin({
// title:"这是一个自定义title"
template:'./src/index.html'
})
]
在执行我们的 npm run bulid 命令,此时我们就会发现在dist中会自动生成一个html文件
里面的titele可以自定义
我们也可以去规定html的模版,去设置一个h5文件,并把它的路径引入template中,那我们打包生成的html文件都会自动变为这个模版
当我们想要看到我们的网页时,就需要一次次去打开,刷新,就很繁琐,那么有没有让我们一打包完就自动显示页面的设置呢,当然是有的,这就要用到我们下面的插件了
npm i -D webpack-dev-server
它可以让打包完成后自动打开我们的页面,安装完后我们需要在package.json的调试下输入这样一句命令
"start": "webpack serve --open"
这句配置就是启动我们的webpack服务器并打开我们的网页
然后我们执行npm start 浏览器就会自动打开,并且对我们的文件进行监视,实现实时刷新
当我们进行编译时,dist中的文件只是替换旧文件,并不会清除旧文件,为了防止我们有旧文件,我们可以安装这样一个插件
npm i -D clean-webpack-plugin 它可以每次编译时清除dist中的就文件,这个插件也是需要去引用的,和html引用方法一样
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
再去plugins注册一下就好
plugins:[
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
// title:"这是一个自定义title"
template:'./src/index.html'
})
]
可在使用webpack时,我们要编译的肯定不止一个模块,当引入别的模块时,直接使用会报错,这时候我们就需要去设置引用模块,用resolve来设置
resolve:{
extensions:['.ts','.js']
}
之后再去使用就不会报错了
考虑浏览器兼容问题
浏览器的旧版本无法兼容ES6的新语法,这时我们就需要去安装babel去解决这个问题
安装命令:npm i -D @babel/core @babel/preset-env babel-loader core-js
安装完成后还是在老地方查看是否安装完成,然后再去webpack.config.js中的use去配置babel
use: [
//配置babel
{
//指定加载器
loader: 'babel-loader',
//设置babel
options: {
//设置babel预定环境
presets: [
[
//指定环境的插件
'@babel/preset-env',
//配置信息
{
//要兼容的目标浏览器
targets: {
},
//指定corejs版本
"corejs": "3",
//使用corejs的方式"usage",表示按需加载
"useBuiltIns": "usage"
}
]
]
}
},
'ts-loader'
],
如果要使IE浏览器去兼容的话,需要在output中配置
//告诉webpack不使用箭头函数
environment:{
arrowfunction:false
}
七、面相对象
面向对象是程序中一个非常重要的思想,它被很多同学理解成了一个比较难,比较深奥的问题,其实不然。面向对象很简单,简而言之就是程序之中所有的操作都需要通过对象来完成。 。举例来说: 。操作浏览器要使用window对象 。操作网页要使用document对象 。 操作控制台要使用console对象 一切操作都要通过对象,也就是所谓的面向对象,那么对象到底是什么呢?这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的挂、抽象等等。程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象
在程序中所有的对象都被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高、休重等属于数据,人可以说话、走路吃饭、睡觉这些属于人的功能,数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象。
八、类(class)
要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象。要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说: 可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。
要使用class关键字去创建一个类
class Person{
}
这样就创建好了一个类,接下来便去使用这个类
const per = new Person();
使用类,首先要定义它的属性
class Person{
//定义实例属性
name: string = "孙悟空";
age:number = 18;
}
像这样创建出来的属性,叫做实例属性,要先实例化后才能去通过实例化的类去访问属性,那我们可不可以通过类直接访问属性呢,当然可以,直接通过类访问的属性叫做静态属性,在属性前使用static关键字可以定义类属性(静态属性)
class Person{
//定义实例属性
name: string = "孙悟空";
//在属性前使用static关键字可以定义类属性(静态属性)
static age:number = 18;
}
这样就可以直接通过类去操作属性了
console.log(per.name);
console.log(Person.age);
注意:类属性无法再用实例化去访问,只能用类本身去操作
总结:直接定义属性是实例属性,需要通过对象的实例去访问,使用static开头的属性是静态属性(类属性),可以直接通过类去访问
访问的属性我们是可以去更改它的值的
per.name='tom'
console.log(per.name);
前面加上readonly后,就表示是一个只读属性,我们就不可以去更改这个属性的任何东西了,否则会报错
而static和readonly是可以一起用的,这样它就是一个静态只读属性
但使用时要注意顺序static在前
我们也可以去定义类的方法
// 定义方法
sayHello(){
console.log('hello 大家好');
}
同样,和上面一样,直接创建的方法属于实例方法,也需要实例化去访问它
这里就不过多赘述了
九、构造函数(方法)
constructor
类里面的属性确定以后,不管new几个实例化对象,它都只能调用那固定的属性值,但我们肯定不希望这样的,这就用到构造函数了,它就可以让我们不用去把属性值定死
class Dog{
name: string;
age :number ;
//constructor构造函数
// 构造函数会在函数创建时调用
constructor(name :string,age:number){
// this表示当前的实例
// 在构造函数中当前对象就是我们当前新建的那个对象
// 可以通过this向新建的对象中添加属性
this.name = name;
this.age =age;
}
bark(){
// alert('汪汪汪')
// 在方法中可以通过this来表示当前调用方法的对象
console.log(this.name)
}
}
const dog = new Dog('小黑',4);
const dog1 = new Dog('小白',4);
const dog2 = new Dog('小黄',4);
const dog3 = new Dog('小花',4);
console.log(dog);
console.log(dog2);
dog2.bark();
如代码所示,我们就可以通过传参去定义属性值
其中也涉及到了this的问题,this在实例方法中,就表示当前的实例,可以通过它去向新建的对象中添加属性,也可以通过它来调用实例化中的属性值,但要注意,这所有的前提是,在实例的方法下完成的
十、类的继承(面向对象重点)
extend
我们上面用的构造函数,虽然可以不用固定属性值了,同时也可以用多个构造函数去实例化出自己想要的对象,但还是会冗余很多,就像下面这样
(function(){
//定义一个狗的类
class Dog{
name: string;
age: number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHello(){
console.log('汪汪汪');
}
}
//定义一个猫的类
class Cat{
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHello(){
console.log('喵喵喵');
}
}
const dog = new Dog ('旺财',5);
const cat = new Cat ('咪咪',3)
console.log(dog);
dog.sayHello();
console.log(cat);
cat.sayHello()
})();
虽然我们确实达到了我们想要的效果,创建了猫和狗两个不同的类,但我们可以发现,里面除了最后的方法不一样,两个类除了名字不一样,其他的简直一毛一样,那我们有没有什么办法,让我们只写一次代码,就可以实现两个不同的类呢,这就要用到我们类的继承了,我们可以把猫和狗,都归为动物类,而它们的汪汪汪和喵喵喵,可以看做是它们的叫声,那么,我们的代码就可以这样去写了
//定义一个Animal的类
class Animal{
name: string;
age: number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHello(){
console.log('动物在叫~');
}
}
这时候有的人就发现,你这样只是新创了一个类,不还是一样吗?别着急,接下来就到我们的主角登场了,接下来我们猫和狗类的代码,就可以这样去写了,我们先把猫狗里面,和动物类里一样的的代码去删掉
//定义一个狗的类
class Dog {
}
//定义一个猫的类
class Cat {
}
然后我们就发现,嗯!报错了
别着急,接下来我们只要这样去操作就可以了
//定义一个狗的类
//使用Dog类继承Animal类
class Dog extends Animal{
}
//定义一个猫的类
//使用Cat类继承Animal类
class Cat extends Animal{
}
我们下面的代码,也就不会报错了
此时,Animal被称为父类,Dog被称为子类,就像现实生活中,儿子去继承爹的东西一样,使用extend后,子类将会去继承父类所有的方法和属性,就相当于,我们把Animal里的代码,复制粘贴给了狗类和猫类,唯一不同的,就是方法里,都变成了动物在叫
所有,通过继承,可以将多个类中共有的代码写在一个一个父类中,这样只需要写一次,即可让所有的子类都同时拥有父类中的属性和方法,但是这样就产生了一个问题,这样写,子类和父类不就一样了吗,子类能够有父类中没有的东西吗
这是当然可以的啦,如果希望在子类中添加一些父类中没有的属性或方法,直接加就行,就像下面这样
class Dog extends Animal{
run(){
console.log(`${this.name}在跑~~~`);
}
}
注:console.log里的写法为模版字符串
然后我们调用方法
继承的好处,就是可以在不动源码的基础上,去给原来的类添加新的方法
但目前的代码还是没有达到我们想要的效果,我们想要调用sayHello时,狗是汪汪汪,猫是喵喵,这就是继承的另一个好处了,也就是说,如果在子类中添加了和父类一样的方法,那么子类的方法就会覆盖掉父类的方法,这里要注意,不是说它改掉了父类里的方法,父类里还是没变的,只是子类方法去覆盖掉了
//定义一个狗的类
//使用Dog类继承Animal类
class Dog extends Animal{
run(){
console.log(`${this.name}在跑~~~`);
}
sayHello(){
console.log('汪汪汪');
}
}
//定义一个猫的类
//使用Cat类继承Animal类
class Cat extends Animal{
sayHello(){
console.log('喵喵喵');
}
}
这种子类覆盖掉父类方法的形式,我们叫做方法重写
这样,就达成了我们的最终目的,代码也没有那么冗余
十一、super关键字
父类还有一个名字叫超类,也就是说,在类的方法中,super就表示当前的父类,或者说当成父类的实例都行,所以说当我们在调用new Dog的时候,就是在调用 super.sayHello()
(function(){
class Animal {
name:string;
constructor(name:string){
this.name=name
}
sayHello(){
console.log('动物在叫~');
}
}
class Dog extends Animal{
sayHello(){
// 在类的方法中super就表示当前类的父类
super.sayHello()
}
}
const dog = new Dog("旺财")
dog.sayHello();
})()(function(){
class Animal {
name:string;
constructor(name:string){
this.name=name
}
sayHello(){
console.log('动物在叫~');
}
}
class Dog extends Animal{
sayHello(){
// 在类的方法中super就表示当前类的父类
super.sayHello()
}
}
})()
看着好像super没有多大用处,因为我们想调用父类方法,不写不就好了吗,而且想改方法也可以进行方法重写啊,但我们改方法时,不可能只会去改它的属性值,可能也会有要添加的新属性,如果我们直接去写新属性,那么就会直接报错,就像下面这样
所以,如果要在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用,也就是用我们的super关键字
可当我们写了super了,为什么还是会报错,因为我们父类的构造函数里,还有一个参数string 所有我们需要把这个参数给加上
class Dog extends Animal{
age :number;
constructor(name:string,age:number){
super(name);
this.age=age
}
}
这样就不会报错了
十二、抽象类
我们之前创建的父类,也是可以去实例化的,但它已经是个父类了,我们肯定是想让它被继承的,不希望它去被实例化,发生改变的,所以,我们可以在父类前面,以abstract开头,也就是我们的抽象类,抽象类他和其他类其实差别不大,只是不能创建对象,也就是专门用来被继承的类
abstract class Animal {
name:string;
constructor(name:string){
this.name=name
}
sayHello(){
console.log('动物在叫~');
}
}
当我们去创建抽象类的对象时,就会报错
抽象类除了这个作用,它还可以去添加抽象方法,那么什么是抽象方法呢,拿我们之前的例子来说,我们虽然进行了方法重写,但它里面始终有一个固定的动物在叫方法,当我们有时候忘了进行方法重写的时候,它就会调用这个默认方法,这当然不是我们希望的,这个时候,我们就希望能有子类来决定这个方法,而父类的方法时什么都没有点,那么,我们只需要,在方法前加上同样的abstract,它就是抽象方法我们就可以这样去写
abstract class Animal {
name:string;
constructor(name:string){
this.name=name
}
abstract sayHello():void;
}
抽象方法它是没有方法体的,并且,抽象方法只能定义在抽象类中,子类,必须对抽象方法进行重写,否则,就会报错
十三、接口
像我们之前去描述一个对象的类型,一般是这样去写的
(function(){
// 描述一个对象的类型
type myType = {
name: string,
age: number
};
const obj :myType={
name:'sss',
age:111
}
})
除了这样,我们还可以用接口去创建一个类结构,也就是,interface
interface myInterface{
name:string;
age:number;
}
我们会发现,它们长得很像,甚至我们可以用myInterface去替换掉myType
(function(){
// 描述一个对象的类型
type myType = {
name: string,
age: number
};
/**
* 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
* 同时接口也可以当成类型声明去使用
* */
interface myInterface{
name:string;
age:number;
}
const obj :myInterface={
name:'sss',
age:111
}
})
也就是说,接口就是用来定义一个类的结构,而且声明这个接口类,属性名和个数,也要和接口里面一样
那么接口又和类有什么区别呢,我们都知道,类是不能够去重复声明的,但是,接口,我们可以去进行重复声明
我们可以重复声明类,并去补充我们想要增加的参数,但要注意的是,补充的参数同样要在声明的类里面,并且,接口可以定义在定义类的时候,去限制类的结构,除了这些,还有些区别,就直接总结在下面了
接口和类的区别:
-
接口可以在定义类的时候去定义类的结构,
-
接口中的所有属性都不能有实际的值
-
接口只定义对象的结构,而不考虑实际值
-
在接口中所有的方法都是抽象方法
接口写完后,我们就可以去定义一个类,去实现这个接口,实现接口,就是使类满足接口的要求
那我们这么麻烦的弄这个接口是为什么呀,或者说,它到底是用来干什么的
因为去定义类的时候是没有限制的,我们的接口呢,就相当于是给类的一个规范,让类创建时,有了一个限制,当类满足接口的要求时,就可以去在特定的情况下使用这个接口
十四、属性的封装
public
public 修饰的属性可以在任意位置访问(修改)默认值
默然的public 是和正常的类没有区别的,也就是说,他也是能够让各个地方都访问的到
private
private 私有属性,私有属性只能在类的内部进行访问(修改)
private name:string;
private age :number;
constructor(name:string,age:number){
this.name=name;
this.age =age;
}
这个时候,外部是不能去访问这些属性的
那我们想要去访问到,要怎么做呢,这就需要在类中添加方法,才能够去访问私有属性,也就是我们的getter和setter方法
protected
protected 受包含的属性,只在当前类和它的子类中访问(修改)
class A {
protected num:number;
constructor(num:number){
this.num=num
}
}
class B extends A {
test(){
console.log(this.num);
}
}
也就是说,只有在继承它的类中,才能够去访问它的属性
getter
getter方法用来读取属性,也就是用来访问属性的,但是不能做修改,也就是只读属性
getName(){
return this.name;
}
console.log(per.getName());
setter
setter方法用来设置属性,也就是用来去修改我们类中的私有属性的
setName(value:string){
this.name=value
}
per.setName("猪八戒")
而它们两个被称为属性的存储器
类的简写
class C{
//可以直接将属性定义在构造函数中
constructor(public name :string,public age:number)
}
//相当于
class C{
name: string;
age:number
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
}
十五、泛型
在定义函数或是类时,如果遇到类型不明确就可以使用泛型
function fn<T>(a:T): T{
return a;
}
//定义一个类型为T的函数,只有在调用时才能知道这个函数的类型是什么
它比any更好,体现在避免了类型检查的跳过,并且它的第一个函数和它返回的类型是一致的
那么我们如何去用它呢
1.可以直接调用具有泛型的函数
let res1 = fn(10); //此时T的类型被定义为了number,利用了TS的自动类型推断
let res2 = fn<string>('hello'); //手动的去定义了T的类型为string
此时,res1返回的类型为number,而res2的返回类型为string
那么我们泛型是不是只能指定一个?并不是这样,我们可以去指定多个泛型
function fn2<T,K>(a:T,b:K){
console.log(b);
return a;
}
// fn2(123,'hello');
fn2<number,string>(123,'hello')
我们这样写也是可以的
那比如说我们想要去定义这个类型的范围,要怎么做呢,可以这样去写
interface Inter{
length:number;
}
// T extends Inter 表示泛型T必须是Inter实现类(子类)
function fn3<T extends Inter>(a:T):number{
return a.length
}
fn3("123")
// fn3({length:123})
可以看做是继承的关系,T必须是Inter的实现类,也就是说我们传的参要有父类的属性,这样就可以定义泛型的范围了
总而言之,泛型就是在我们无法确定类型的时候去定义一个变量,用变量去表示泛型