深入理解面向对象-ES6的类和类的继承
前言
ES6之前,按照构造函数的形式创建类的,不仅仅和编写普通函数过于相似,而且代码并容易理解。
- 在es6(ECMAScript2015)新的标准中,使用了class关键字来直接定义类 但是类本质上依然是构造函数、原型链的语法糖而已
使用class来定义一个类
定义一个类有两种方式:类声明和类表达式
//类声明
class Person{}
//类表达式
let student = class{}
类和构造函数的异同
类和构造函数的特性其实是一致的
let p = new Person()
console.log(p.__proto__ === Person.prototype) // true
console.log(Person) // [class Person]
console.log(Person.prptotype) // {}
console.log(Person.prototype.__proto__) //[Object: null prototype] {}
console.log(Person.prototype.constructor) // [class Person]
console.log(typeof Person) // function
类的构造函数
如果我们希望在创建对象的时候给类传递一些参数,这个时候该如何做呢?
- 每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor
- 当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor
- 每个类只能有一个构造函数,如果包含多个构造函数就会抛出异常 当我们通过new关键字操作类的时候,就会调用这个constructor函数
class person {
constructor(name,age,height){
this.name =name
this.age = age
this.height = height
}
}
类的实例方法
在上面我们定义的属性都是直接放到了this上,也就意味着他是放到创建出来的新对象中:
- 在前面我们对于实例的方法,希望是放到原型上卖弄,这样就可以被多个实例来共享
- 这个时候就可以直接在类里面定义
class person {
constructor(name,age,height){
this.name =name
this.age = age
this.height = height
}
running(){
console.log(this.name + "running~")
}
eating(){
console.log(this.name + "eating~")
}
}
类的访问器方法
对象可以添加getter和setter函数,类同样也可以添加
class Person {
contructor(name){
this._name = name
}
get name(){
console.log("调用了name的getter方法")
return this._name
}
set name(newName){
console.log("调用了name的setter方法")
this._name = newName
}
}
类的静态方法
静态方法常用于直接使用类来执行的方法,不需要类的实例,使用static关键字来定义
class Person {
contructor(age){
this.age = age
}
static create(){
return new Person(Math.floor(Math.random() * 100))
}
}
//使用类的静态方法
Person.create()
ES6类的继承-extends
ES5中实现的继承方法是寄生组合式继承,虽然实现的继承的效果,但过程是非常繁琐的,ES6中新增了使用extends关键字,可以方便我么能实现继承:
class Person{}
//继承
class Student extends Person {}
super 关键字
super关键字,既可以当作函数使用,也可以当作对象使用,在这两种情况下用法完全不相同
super当作函数调用
super当作函,数调用时,代表父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数
calss A{}
class B extends A {
constructor(){
super()
}
}
上面代码中,子类B的构造函数中的super(),代表父类A的构造函数。
注意:super虽然代表了父类A的构造函数,但返回的是子类B的实例,即super内部的this指向B的实例,因此super()在这里相当于A.prototype.constructor.apply(this)
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错
class A {}
class B extends A {
m() {
super(); // 报错
}
}
上面代码中,super()用在B类的m方法之中,就会造成语法错误。
super作为对象
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A{
p(){
return 2
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B()
上面代码中,子类B当中的super.p(),就是super作为对象使用。这时super在普通方法中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代码中,p是父类A实例的属性,super.p就引用不到它。
如果属性定义在父类的原型对象上,super就可以取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
上面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)。
由于this指向子类实例,所以如果通过super对某个属性赋值,这是super就是this,赋值的属性就会变成子类的实例的属性
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor(){
super()
this.x = 2
super.x = 3
console.log(super.x) //undefined
console.log(this.x) // 3
}
}
上面代码中,super.x赋值为3,这是等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined
如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg){
console.log('static',msg)
}
myMethod(msg){
console.log('instance',msg)
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1) // static 1
var child = new Child()
Child.myMethod(1) // instance 2
上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
另外,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面代码中,静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。
注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
ES6继承实现代码
//父类
class Person {
constructor(name,age){
this.name =name
this.age = age
}
running() {
console.log(this.name + " running~")
}
eating() {
console.log(this.name + " eating~")
}
personMethod() {
console.log("处理逻辑1")
console.log("处理逻辑2")
console.log("处理逻辑3")
}
static staticMethod() {
console.log("PersonStaticMethod")
}
}
//子类
class Student extends Person {
constructor(name,age,sno){
super(name,age)
this.sno = sno
}
studying() {
console.log(this.name + " studying~")
}
//类对父类方法的重写
running(){
console.log("student " + this.name + " running")
}
//对personMethod方法的重写,复用一部分父类的处理逻辑
personMethod() {
super.personMethod()
console.log("处理逻辑4")
console.log("处理逻辑5")
}
// 重写静态方法
static staticMethod() {
super.staticMethod()
console.log("StudentStaticMethod")
}
}
var stu = new Student("why", 18, 111)
console.log(stu) // Student { name: 'why', age: 18, sno: 111 }
stu.eating() // why eating~
stu.running() //student why running
stu.personMethod() // 处理逻辑1 ~ 处理逻辑5
Student.staticMethod() //PersonStaticMethod StudentStaticMethod