本文目录:
- 1.创建对象实例
- 2.继承
- 3.静态方法和属性
- 4.访问修饰符
- 5.readonly
- 6.抽象类
1.创建对象实例
在js中,生成实例对象的传统方法是通过构造函数
我们首先通过传统的构造函数和原型对象的方法来看一下对象实例的创建
function Greeter(message) {
this.msg = message;
}
Greeter.prototype.greeter = function() {
return 'hello: ' + this.msg;
};
let m1 = new Greeter('传统方式创建对象实例');
console.log(m1.msg);
console.log(m1.greeter());
接下来我们再通过类class的方式 生成一个对象实例
我们在ES6的时候,实例属性都是定义在constructor()方法里面, 在ES7里 我们可以直接将这个属性定义在类的最顶层,其它都不变,去掉this,msg和flag就是我们定义在最顶层的实例属性
constructor(构造函数)方法是类的默认方法
一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加
class Greeter {
msg: string;
flag: boolean = false;
// 关于构造函数:
constructor(message: string) {
this.msg = message;
}
greeter() {
console.log('这个是在constructor构造函数外部定义实例属性:', this.flag);
return 'hello: ' + this.msg;
}
}
let g2 = new Greeter('通过类创建的对象实例');
console.log(g2.msg);
console.log(g2.greeter());
接下来我们来分析一些,ES6新增的class语法糖,和构造函数的一些关系
类class的类型 本质上是一个函数; 类本身就指向自己的构造函数
console.log(typeof Greeter);
console.log(Greeter === Greeter.prototype.constructor);
console.log(g2.greeter === Greeter.prototype.greeter);
通过上面在这个代码我们也可以发现,new类的时候就相当于new构造函数
调用类上面的方法就是调用原型上的方法
在类的实例上面调用方法,其实就是调用原型上的方法
2.继承
- 使用继承来扩展现有的类,是面向对象的三大特性之一(封装,继承,多态)
- 基类,父类,超类是指被继承的类,派生类,子类是指继承于基类的类
- ts中类继承类似于传统面向对象编程语言中的继承体系 ,使用extends关键字继承,类中this表示此当前对象本身,super表父类对象。子类构造函数中第一行代码调用父类构造函数完成初始化,然后再进行子类的进一步初始化。子类中可以访问父类(public、protected)的成员属性、方法
- 派生类包含了constructor; ts 规定只要派生类里面自定义了一个constructor函数就必须在使用this前,调用一下super方法
- ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this));ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this
- 因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象
子类方法名和父类相同表示重写父类方法
业务需求:我们现在有两个类,一个动物类,一个狗类, 狗也是动物,所以会继承动物类的一些属性和方法
class Animal {
name: string;
constructor(param: string) {
this.name = param;
}
move(distance: number = 0) {
console.log(`${this.name} 移动了 ${distance}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('狗叫!');
}
}
const dog = new Dog('阿黄');
console.log(dog.name);
dog.bark();
dog.move(10);
dog.bark();
上面这个例子中 动物类是基类,也可以叫父类; 狗是子类也可以叫派生类, 继承自动物类,可以使用父类的任何方法和属性
我们将上面的代码稍微做一下修改
class Dog extends Animal {
dogName2: string;
constructor(name: string) {
// 派生类包含了一个构造函数,就必须首先调用super()方法,会调用基类的构造函数,然后构造子类自己的this
super(name);
this.dogName2 = name;
}
// 父类也有一个move方法,我们在子类例自定义move方法,就会重写从Animal继承来的move方法,从而使move方法根据不同的类而实现不同的功能
move(distanceInMeters: number = 5) {
console.log('重写了基类的move方法');
super.move(distanceInMeters);
}
bark() {
console.log('狗叫!');
}
}
let animal1: Animal = new Animal('赤兔');
let dog1: Dog = new Dog('阿黄');
animal1.move();
dog1.move(10);
这个dog1即使被声明为 Animal类型,也不会调用父类的move方法,因为它的值就是Dog实例
3.静态方法和属性
- ES6中提供了 静态方法, ES7中提供了静态属性; TS两者都有
- 我们可以认为类具有 实例部分与 静态部分这两个部分。定义静态属性和方法,只需要在对应的属性和方法前面加上static即可
class Animal {
static PI = 3.14159;
static isAnimal(param) {
return param instanceof Animal;
}
}
let cat = new Animal();
console.log(Animal.PI);
console.log(Animal.isAnimal(cat));
cat.isAnimal(cat);
cat.PI;
通过对象cat上来调用的属性和方法 叫做对象实例的属性和方法
通过类名Animal来调用的 叫静态属性和方法
4.访问修饰符
- ts类中修饰符分为3种; public : 公有(所有)默认;
protected:保护 (父类+子类);private: 私有(本类)
class Animal {
public name: string;
//修饰符还可以使用在构造函数参数中,等同于类中定义该属性,使代码更简洁
//下面的age属性就相当于定义在顶部的 一个实例属性,借助修饰符也可以定义
public constructor(theName: string, public age: number = 24) {
this.name = theName;
}
public move() {
console.log(123);
}
}
let a1 = new Animal('Lucy');
console.log(a1.name, a1.age);
上面的例子中,name 被设置为了 public,所以直接访问实例的 name 属性是允许的
在ts中,所有的类型默认都是public
- private: 当成员被标记成 private时,它就不能在声明它的类的外部访问
class Animal {
// 这个name属性就只能在这个类里面访问,类外部访问就会报错
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Dog extends Animal {
constructor(name) {
// 派生类的构造函数必须包含super函数的调用
// 因为父类的构造函数需要一个参数,所以这里我们需要将name参数传递进去
super(name);
// console.log(this.name); //属性“name”为私有属性,只能在类“Animal”中访问。所以在派生类里面访问也是不允许的
}
}
let a1 = new Animal('Lucy');
console.log(a1.name);
- protected: 属性和方法 如果是用 protected 修饰,则允许在派生类中访问, private是不允许的
class Animal {
// 这个name属性就只能在这个类里面访问,类外部访问就会报错
protected name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Dog extends Animal {
constructor(name) {
super(name);
// 这个基类的name属性是 protected受保护的,所以可以在派生类里面访问
console.log(this.name);
}
}
let a1 = new Animal('Lucy');
- 构造函数被private修饰, 该类不允许被继承或者实例化;只允许被继承
class Animal {
public name;
private constructor(name) {
this.name = name;
}
// protected constructor(name) {
// this.name = name;
// }
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal('Jack');
5.readonly
- 只读属性关键字,只允许出现在属性声明或索引签名中
- 可以使用 readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化
class Animal {
readonly name: string;
// 声明是初始化
readonly myName: string = '只读属性';
// 注意如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面
constructor(name: string, public readonly firstName: string) {
// 构造函数里面初始化
this.name = name;
}
}
let cat2 = new Animal('阿黄', '小白');
console.log(cat2.name, cat2.myName, cat2.firstName);
cat2.name = '张三'; // 编译报错,说不能给一个只读属性分配一个新值
6.抽象类
- 抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节
- abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法抽象成员
abstract class Animal {
name: string = '基类默认值';
abstract myName: string;
// 仅仅定义方法的签名,不包含方法体
abstract makeSound(): void;
move(): void {
console.log('动物行走');
}
}
下面这行代码就会报错, 无法创建抽象类的实例
抽象类不能被实例化, 只能作为基类使用,也就是只能给其他类继承
let aa2 = new Animal()
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符
class Dog extends Animal {
myName: string = '抽象成员';
// 编译报错:非抽象类“Dog”不会实现继承自“Animal”类的抽象成员“makeSound”
// 也就是说我们要将基类的抽象方法在派生类这里再实现一次
makeSound() {
console.log(`基类的抽象方法必须在派生类中实现--${this.name}--${this.myName}`);
}
}
let aa3 = new Dog();
console.log(aa3.makeSound());