TypeScript的基本使用(2)
面向对象
- 程序是对现实生活的一种抽象,对象是对现实生活的一种模拟;
- 要对什么进行操作,就找什么对象。比如你要操作浏览器窗口,就需要使用window对象
- 事物要在程序中的描述,是以属性(数据)和方法(功能)
- 类是对象的模型,可以通过类来创建对象
为了方便后面代码的测试,简单的实现对文件内容进行监测后自动编译
- 创建
tsconfig.json
文件配置如下
{
"include":["./src/**/*"],
"compilerOptions":{
"module": "es2015",
"target": "es2015",
"strict": true,
"outDir": "./dist",
"noEmitOnError":true
}
}
- 执行
tsc -w
命令
类
- 类是创建对象的模板
- 对象包含属性和方法
class Person{
// 实例属性
name:string = '唐僧';
// 静态属性
static country:string = 'China';
// 只读属性
readonly sex:string = 'male';
static readonly color:string = 'yellow':
// 静态方法
static sayHi(){
console.log('say hi')
}
// 实例方法
sayName(){
console.log('okkk')
}
}
let p1 = new Person()
console.log(p1.name);
// p1.sex = 'update'
console.log(Person.country);
//Person.color = 'pink'
p1.sayHi()
注意:
- 实例属性,只能通过对象的实例去访问
- 静态属性,只能通过类去访问
- 只读属性,无法修改
- staic修饰符与readonly一起使用时,必须将static放置在最前面
构造函数
- 一般通过类创建多个不同的对象,对象中拥有不同的属性值,所以在类中不能写固定的值;
- 什么时候进行对象的属性赋值?对象在创建的时候,由外界传值。构造函数会在创建对象的时候调用,即当
new 类名()
时会调用类中的constructor方法 - 实例方法中this指向是的调用的对象(新创建的对象或者调用的对象)
- 为了实现不同的对象中属性值不同,可以在构造函数中定义形参来接收传递的参数值
class Dog{
name:string;
constructor(name:string){
this.name = name;
}
bark(){
console.log(this.name+' is barking...')
}
}
let d1 = new Dog('旺财')
d1.bark()
继承
- 可以将子类中共有的代码都写到父类中,使用继承后,子类将会拥有父类所有的方法和属性。
- 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法,这种称之为方法重写
class Animal{
name:string;
constructor(name:string){
this.name = name;
}
shout(){
console.log("动物在叫唤")
}
}
class Cat extends Animal{
shout(){
console.log(this.name+'在叫')
}
}
在开发中我们会经常使用到别人的类(比如:框架的),但是别人的类并不是完全满足我们的需求,我们不会轻易的改动别人的类,如果改动了别人的类可能会导致在其他地方使用到这个类时报错的问题。比如:我们需要4个功能,但是A类只有3个功能,我们可以创建新类去继承这个类,然后在新类中添加新的功能----继承可以对新的类进行扩展。简而言之,可以新增原来不存在的功能,或者覆盖父类中不满足子类要求的方法
super
- 使用
super.
方式访问的话,会调用父类实例中的属性或方法 - 不管父类是否有重写构造方法,子类如果重写构造方法则必须在构造方法的第一行调用
super
,否则报错
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() {
console.log('喵喵叫')
}
}
let d1 = new Dog('旺财', 12)
d1.sayHello()
抽象类
- 不希望调用者通过这个类创建对象,这个类是基类,可以让其他类进行继承;
- 使用
abstract
关键字定义抽象类,没有方法体 - 抽象类的作用:可以让其他类进行继承
- 抽象方法只能出现在抽象类中;继承了抽象类的类必须实现抽象类中定义的抽象方法
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract sayHello(): void;
}
class Dog extends Animal {
sayHello() {
console.log('旺旺旺')
}
}
let d1 = new Dog('阿久')
d1.sayHello()
接口
- 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法,这些属性和方法不能有值
- 接口可以重复声明,即可以重复使用这个接口名进行定义,在使用时必须对接口中定义的属性和方法都要进行实现
- 使用类去实现接口,则必须实现接口中定义的属性和方法;
- 接口和抽象类的差异:抽象类可以定义抽象方法,也可以实现具体的方法;而接口中定义的只能是抽象方法
- 注意:接口和抽象类这些都是在ts中有的,而在js中没有
interface Person {
name: string;
age: number;
}
interface Person {
weight: number;
}
const obj: Person = {
name: '如沐春风',
age: 20,
weight: 1.88
}
interface myInter{
name: string;
sayHello():void;
}
// 类实现接口
class MyClass implements myInter {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log('大家好~~');
}
}
封装
- 前面对象的属性值可以任意的修改,会导致对象中的数据变得非常不安全;使用属性的封装可以让数据变得更安全
- 可以在属性前添加属性修饰符
- public 修饰的属性可以在任意位置访问(修改) 默认值。 【大家共享】
- private 私有属性,私有属性只能在类内部进行访问(修改) 【我自己珍藏的】
- 通过在类中添加方法使得私有属性可以被外部访问
- 只能在当前类中访问,子类不行
- protected 受包含的属性,只能在当前类和当前类的子类中访问(修改),不能在类外访问 【同一种族共享】
- 想要在类外获取private修饰的属性,则需要在类中定义其他的方法内部访问这个私有属性,然后暴露给外部;我们现在可以通过控制getter和setter的方法来对属性进行控制,可以保证程序更加的健壮
- getter方法用来读取属性
- setter方法用来设置属性
- 它们被称为属性的存取器
class Person{
private _age:number;
constructor(age:number){
this._age = age;
}
// 获取
getAge(){
return this._age
}
// 设置
setAge(age:number){
//只有在age合法的情况下才进行操作
if(age>=0){
this._age = age
}
}
}
const p1 = new Person(20)
//p1._age = -30
p1.setAge(-30)
console.log(p1)
ts为了简化程序员频繁的编写getter和setter代码,允许使用下面的简便方式
class Person{
private _age:number;
constructor(age:number){
this._age = age;
}
get age(){
return this._age;
}
set age(value:number){
if(value >=0){
this._age = value
}
}
}
const p2= new Person(20)
console.log(p2)
p2.age = -33 //这里会调用"set age"
console.log(p2)
注意:
- 不建议外部轻易更改的属性名一般以"_"开头
- 属性存取器一般在开发中当属性容易被修改错或者对属性值要求比较高时建议使用;
属性修饰符
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 = 33; 报错,不能是类外修改
可以直接将属性定义在构造函数中
class C{
public name:string;
constructor(name:string){
this.name = name;
}
}
等价于下面
class C{
constructor(public name:string){
this.name = name;
}
}
泛型
- 表示某个类型
- 定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),但是能确定的时其返回值的类型和参数的类型是相同的,这种就需要使用到泛型
- 泛型的作用:在类型数据类型不明确的时候,通过一个变量来保存变量的数据类型
// 泛型定义
function fn<T>(a: T): T {
return a
}
// 调用时可以指定泛型或者不指定泛型
let n = fn<number>(10)
let n1 = fn(10)
// 泛型可以同时指定多个
function fn2<M, N>(a: M, b: N): N {
console.log(a);
return b
}
fn2(10, 'hello')
泛型与继承结合
interface Inter {
length: number;
}
// 指定泛型T必须是Inter的子类或实现类 【可以是包含有Inter接口规范的类】
function fn3<T extends Inter>(a: T): number {
return a.length
}
f3("hello") //这里可以执行,因为字符串中含有length属性
f3(123) //报错
f3({length:10})
泛型与类结合
class myClass<T>{
name: T;
constructor(name: T) {
this.name = name
}
}
let m = new myClass('hello')