前言
先看两张图
一、 先说说ES5中如何定义一个类
function Animal(name) {
this.name = name
// 实例方法 需要new才能调用
this.eat = function () {
console.log(this.name + '在吃东西');
}
}
// 原型链上的属性 会被实例共享,构造函数上的属性不会
Animal.prototype.age = '3'
// 原型链上的方法 会被实例共享,构造函数上的方法不会
Animal.prototype.sleep = function() {
console.log(this.name + '在睡觉');
}
// 静态方法 可以直接调用
Animal.play = function () {
console.log('play');
}
let cat = new Animal('xiaomao')
cat.eat();
cat.sleep()
Animal.play();
二、再说说ES5中如何实现继承
1)构造函数继承
function Animal(name) {
this.name = name
this.eat = function() {
console.log(this.name + '吃东西');
}
}
Animal.prototype.play = function() {
console.log(this.name + '在玩');
}
function Dog(name) {
Animal.call(this, name)
}
let taidi = new Dog('taidi')
taidi.eat() // daidi 吃东西
taidi.play() // 报错 构造函数继承无法继承原型链上的属性和方法
2)原型链继承、
function Animal(name) {
this.name = name
this.colors = ['red', 'yellow']
this.eat = function() {
console.log(this.name + '吃东西');
}
}
Animal.prototype.play = function() {
console.log(this.name + '在玩');
}
function Dog() {
}
Dog.prototype = new Animal()
// 1.第一个问题:实例化子类的时候无法直接给父类传参
// 想要把name显示出来,需要在构造函数 Dog 中重新将name挂载到实例上
let taidi = new Dog('taidi')
taidi.eat() // undefined 在吃东西
taidi.play() // undefined 在玩
// 2.第二个问题:父类的属性被共享
let dog1 = new Dog()
dog1.colors.push('blue')
console.log(dog1.colors); // ['red', 'yellow', 'blue']
let dog2 = new Dog()
console.log(dog2.colors); // ['red', 'yellow', 'blue']
3)混合继承
function Teacher(name, age) {
this.name = name
this.age = age
}
Teacher.prototype.sayName = function() {
console.log(this.name)
}
Teacher.prototype.sayAge = function() {
console.log(this.age)
}
function Student() {
var args = arguments
// Student对象 继承 Teacher对象的属性
Teacher.call(this, args[0], args[1])
}
// 继承 Teacher 对象的方法(父类的属性和方法都会继承过来)
Student.prototype = new Teacher()
var students = new Student('xiaolan', 12)
student1.sayName() // xiaolan
student1.sayAge() // 12
缺点:调用了两次父类构造函数,造成性能浪费
4)寄生混合继承
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name) {
this.sex = 'boy'
Parent.call(this, name)
}
// 与组合继承的区别
Child.prototype = Parent.prototype
var child1 = new Child('child1')
console.log(child1) // Child{ sex: "boy", name: "child1" }
child1.getName() // "child1"
console.log(child1.__proto__ === Child.prototype) // true
console.log(Child.__proto__ === Function.prototype); // true
三、ES6 中如何定义一个类
class Info {}
const Info = class {}
ES6中类的两种写法
1)ES6中定义一个类
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
// 原型对象上的方法 Point.prototype
getPosition() {
return `${this.x}, ${this.y}`
}
}
let p1 = new Point(2, 3)
console.log(p1); // 实例对象
console.log(p1.getPosition()); // (2, 3)
console.log(p1.hasOwnProperty('x')); // true
console.log(p1.hasOwnProperty('getPosition')); // false
console.log(p1.__proto__.hasOwnProperty('getPosition'), p1.__proto__ === Point.prototype); // true
2)ES6可以使用set、get关键字,用法和对象中一样
class Info {
constructor(age) {
this._age = age
}
set age(newAge) {
console.log('年龄:', newAge);
this._age = newAge
}
get age() {
return this._age
}
}
let p1 = new Info(18)
console.log(p1.age);
p1.age = 17
3)ES6静态方法
类自身才能调用,不继承给实例
class Point {
z = 0
constructor(x, y) {
this.x = x
this.y = y
}
getPosition() {
return `${this.x}, ${this.y}`
}
static getClassName() {
return Point.name
}
}
let p1 = new Point(2, 3)
console.log(p1); // 实例对象
console.log(p1.getPosition()); // (2, 3)
console.log(p1.getClassName()); // 报错
console.log(Point.getClassName()); // Point (类名)
4)ES6 静态属性
ES6明确只有静态方法,没有静态属性。可以通过其他办法实现。
提案通过static设置静态属性,但是目前还没通过
class Point {
constructor() {
this.x = 0
}
}
Point.y = 2
const p = new Point()
console.log(p.x); // 0
console.log(p.y); // undefined
5)ES6私有方法
ES6暂不支持私有属性、私有方法,可以通过一些技巧实现。
// 方法一:通过命名区分
class Point {
func1() {}
_func2() {}
}
// 方法二:将私有方法溢出模块
const _func2 = () => {}
class Point {
func1() {
_func2.call(this)
}
}
const p = new Point()
p._func2()
// 方法三:利用Symbol值的唯一性
// a.js
const func1 = Symbol('func1')
export default class Point {
static [func1]() {
}
}
// b.js
import Point from 'a.js'
const p = new Point()
console.log(p);
6)ES6 私有属性
#号,提案中
class Point {
#ownProp = 12
}
new.target 当前构造函数类
function Point() {
console.log(new.target); // Point{}
}
class Point {
constructor() {
console.log(new.target); // Point{}
}
}
const p = new Point()
new.target 子类继承父类,new.target表示的是子类构造函数
class Parent {
constructor() {
console.log(new.target); // class Child extends Parent{}
if(new.target === Parent) {
throw new Error('不能实例化')
}
}
}
class Child extends Parent {
constructor() {
super()
}
}
const p = new Child()
四、ES6 继承
extends + super
class Parent {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
static getNames() {
return this.name
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
const p = new Child('aaa', 18)
console.log(p);
console.log(p.getName());
console.log(Child.getNames()); // Child
super 作为函数
super 作为对象:在普通方法中,指向父类原型对象;在静态方法(static修饰)中指向父类。
class Parent {
constructor() {
this.type = 'parent'
}
getName() {
return this.type
}
}
Parent.getType = () => {
return 'is parent'
}
class Child extends Parent {
constructor() {
super()
console.log('constructor', super.getName());
}
getParentName() {
console.log('getParentName', super.getName());
}
getParentType() {
console.log('getParentType', super.getType());
}
static getStaticParentType() {
console.log('getStaticParentType', super.getType());
}
}
const p = new Child()
p.getParentName()
p.getParentType() // 报错 super指向父类原型对象,父类原型对象上没有getType方法
Child.getStaticParentType() // 'is parent'
super的this指向,指向的是子类
class Parent {
constructor() {
this.name = 'parent'
}
print() {
console.log(this.name);
}
}
class Child extends Parent {
constructor() {
super()
this.name = 'child'
}
childPrint() {
super.print()
}
}
const p = new Child()
p.childPrint() // child super的this指向的是子类
prototype proto
① 子类的__proto__指向父类本身。
② 子类的prototype属性的__proto__指向父类的prototype属性。
③ 子类实例的__proto__属性的__proto__指向父类实例的__proto__。
原生构造函数的继承
ES5中是不能继承的,ES6允许继承原生构造函数。
ES5中原生构造函数有:Boolean、Number、String、Array、Date、Function、RegExp、Error、Object
class CustomerArray extends Array {
constructor(...args) {
super(...args)
}
}
const p = new CustomerArray(1, 2, 3)
console.log(p); // [1, 2, 3]
ES5和ES6实现继承在机制上的差异
ES5是先创建子构造函数的实例this,然后再把父构造函数的属性和方法添加到实例this上。
ES6是先从父类取到实例对象this,然后调用super函数之后,再将子类的属性和方法加到this上
五、TS实现继承
1.TS定义一个类
class Person {
name: string;
constructor(name: string) {
this.name = name
}
getName(): string {
return this.name
}
setName(name: string): void {
this.name = name
}
}
var p = new Person('xiaoming')
console.log(p.getName());
p.setName('xiaohong')
console.log(p.getName());
2.TS实现继承
extends + super
class Person {
name: string;
constructor(name: string) {
this.name = name
}
getName(): string {
return this.name
}
setName(name: string): void {
this.name = name
}
}
var p = new Person('xiaoming')
console.log(p.getName());
p.setName('xiaohong')
console.log(p.getName());
// 继承 extends super
class Student extends Person {
constructor(name: string) {
super(name)
}
}
var stu = new Student('student')
console.log(stu.getName());
3.类里的修饰符
- public:公有- 在类里面、子类、类外面都可以访问
- protected:保护类型 - 在类里面、子类里面可以访问,在类外面没法访问
- private:私有 - 在类里面可以访问,在子类、类外部没法访问
class Person {
public name: string;
constructor(name: string) {
this.name = name
}
run(): string {
return this.name
}
}
class Student extends Person {
constructor(name: string) {
super(name)
}
}
let pp = new Person('xiaoming')
let ppt = new Student('student')
console.log(pp.run()); // xiaoming -类中访问
console.log(ppt.run());// student -子类中也可以访问
console.log(pp.name); // xiaoming -外部访问,public可以,如果换做private,则编译失败
4.静态方法与实例方法
class Person {
public name: string;
static age: number = 20; // 静态属性
constructor(name: string) {
this.name = name
}
getName() {
return this.name
}
static getAge() { // 静态方法
return 'age:' + this.age
}
}
// 调用实例方法
let xiaoming = new Person('xiaoming')
console.log(xiaoming.getName()); // 小明
// 调用静态属性和静态方法
console.log(Person.age); // 20
console.log(Person.getAge()); // age: 20
5. 多态
父类定义一个方法不去实现, 让继承他的子类去实现,每个子类有不同的表现
class Animal {
name:string;
constructor(name:string) {
this.name=name
}
eat() {
}
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
eat() {
return this.name + '吃狗粮'
}
}
class Cat extends Animal {
constructor(name:string) {
super(name)
}
eat() {
return this.name + '吃鱼'
}
}
let taidi = new Dog('taidi')
console.log(taidi.eat()); // taidi吃狗粮
let tom = new Cat('tom')
console.log(tom.eat()); // tom吃鱼
6. 抽象类 抽象方法
typeScript中的抽象类,提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
abstract class Animal {
public name: string;
constructor(name: string) {
this.name = name
}
abstract eat(): any;
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
// 抽象类的子类必须实现抽象类里面的抽象方法
eat() {
return this.name + '吃狗粮';
}
}
class Cat extends Animal {
constructor(name:string) {
super(name)
}
// 抽象类的子类必须实现抽象类里面的抽象方法
eat() {
return this.name + '吃鱼';
}
}
let taidi = new Dog('taidi')
console.log(taidi.eat()); // taidi吃狗粮
let tom = new Cat('tom')
console.log(tom.eat()); // tom吃鱼