简介
ES6中Class可以通过extends关机子实现继承,相比起ES5用修改原型链的方式来实现继承,要清晰方便很多。
class father {
}
class son extends father {
}
上面的例子中son通过extends关键字继承了father所有的属性和方法。(由于没有部署任何代码,所以这两个类是完全一样的,等于复制了一个father类到son中)
//定义父类
class father {
constructor (name) {
this.name = name;
}
sayName() {
return this.name;
}
}
//定义子类son继承于父类
class son extends father {
constructor(name, sex) {
super(name); //调用父类的constructor(name);
this.sex = sex; //给子类实例对象添加实例属性
}
sayName() {
return super.sayName() + ', ' + this.sex;
//调用了父类的sayName()方法
}
}
let man = new son('小明', '男');
son.sayName(); //"小明, 男" //调用子类的sayName()方法
上述例子中子类在constructor中先调用了super()方法,这实际上是在调用父类中的constructor方法,这是因为子类自己的this对象必须先通过父类的构造方法完成塑造,让它具备父类的实例属性和方法之后才能“改造它”,让它有自己的实例和方法。而不调用的话就得不到this对象。
super()调用会生成一个空对象,作为context来调用父类的constructor,返回this对象,作为子类constructor的context继续调用构造函数。
context:执行上下文 constructor:构造函数
另外,在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
super 关键字
super既可以当做函数使用,也可以当做对象使用。这两种情况下,用法完全不同。
当函数使用时
当作为函数调用时,表示父类的constructor构造方法。虽然此时代表父类的构造函数,但是super内的this指向的是子类(即子类的构造函数)。
同时,当它作为函数时,只能在子类的构造函数中使用(constructor中才能用),其他地方就会报错。
当对象使用时
- 在普通方法中使用,指向父类的原型对象(类的普通方法都添加在类的原型对象上)
- 在静态方法中使用,指向父类(类的静态方法都是通过类的直接调用)
普通方法中:
class Person {
//构造函数,定义实例属性
constructor() {
this.sex = '男';
}
//普通方法,定义在类的原型上,不懂看前篇博客
sayHi() {
return 'hi';
}
}
Person.prototype.age = '18';
class man extends Person {
constructor() {
super();
console.log(super.sayHi()); //调用父类的普通方法
console.log(super.age); //18
}
getSex() {
return super.sex; //获取父类的sex属性
}
}
let a = new man(); //"hi"
a.getSex(); //undefined
例子中,在子类的普通方法中调用super对象,因为sayHi()方法是父类中的普通方法,定义在父类的原型上,又普通方法中super对象指向父类的原型对象,所以可以通过super调用。同理,age定义字父类的原型对象上,所以可以通过super取到。而父类中sex为实例属性,定义在父类的实例对象上,而不在父类的原型对象上,所以用super对象取不到。
ES6规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。例如:
class father {
constructor() {
this.a = 1;
}
printA() {
console.log(this.a);
}
}
class son extends father {
constructor() {
super();
this.a = 2;
}
m() {
super.printA();
}
}
let b = new son();
b.m() // 2
上面的代码中,虽然在m中调用的是父类的printA()方法,但是输出的却是2,这证明了普通函数中用super调用父类的方法,其this指向为当前子类实例。也就是说此时调用的其实是super.printA.call(this)。
又因为this指向为实例,所以如果通过super对某个属性赋值,这时的super就是this,赋值的属性就会变成子类实例的属性。例如:
class father {
constructor() {
this.a = 1;
}
}
class son extends father {
constructor() {
super();
this.a = 2;
super.a = 3;
console.log(super.a); //undefined
console.log(this.a); //3
}
}
let b = new son();
可是如果当读取super.实例属性时,返回的是undefined,因为普通函数中的super指向为父类的原型,这里也就是读的father.prototype.x。
小总结:
- 子类普通函数中把super当作对象使用,super指向父类的原型
- 子类普通函数中利用super调用父类的方法时,this指向为当前子类实例
- 子类普通函数中利用super.x = y给实例属性x赋值,则该子类实例this.x = y
- 子类普通函数中读取super.x就是在读取父类原型的x属性(原因看第1条)
静态函数中 :
当super作为对象在静态方法中,指向父类。
class father {
static say(name) {
console.log('static:' + name);
}
say(name) {
console.log('instance:' +name );
}
}
class son extends father {
static say(name) {
super.say(name); //在静态方法用super对象
}
say(name) {
super.say(name); //在普通方法中用super对象
}
}
let man = new son('小明');
man.say(); //"instance:小明"
son.say(); //"static:小明"
例子中,man.say()是调用实例的say方法,即在子类普通方法中使用super,此时super指向父类原型,即通过super调用父类原型上的say()方法,即父类的普通方法say();而son.say()是通过son类直接调用son类上的静态方法,而其中的super指向父类,所以相当于调用father.say(),就是父类上的静态方法say()。
另外,在子类静态方法中通过super调用父类的方法时,内部的this指向当前的子类,而不是子类的实例。
class father {
constructor() {
this.a = 1;
}
static printA() {
console.log(this.a);
}
}
class son extends father {
constructor() {
super();
this.a = 2;
}
static printA() {
super.printA();
}
}
son.a = 233;
son.printA(); //233
例子中,直接调用子类的静态方法,其中的super指向父类,相当于调用父类的printA()方法,而此时的this指向子类,而不是子类的实例,所以输出是233而不是2。
另外,在使用super时,要显示的指定是作为函数还是作为对象使用,否则会报错。
类的prototype和__proto__
- 子类的__proto__属性,即构造函数的继承,指向父类。
child.__proto__ -> parent
(提示:class = class.prototype.constructor) - 子类的prototype属性的__proto__属性,表示子类方法的继承,总是指向父类的prototype属性.
child.prototype.__proto__ -> parent.prototype
实例的__proto__属性
子类实例的__proto__属性的__proto__属性指向父类实例的__proto__属性,也就是说子类实例的原型的原型就是父类的原型。
- 首先,子类的原型就是子类实例的原型(因为类其实就是一个构造函数,实例继承的所有方法都是在类的原型上创建的方法)。即子类实例.__proto__ -> 子类.prototype
- 然后,子类的prototype属性(子类原型)的__proto__指向父类prototype(父类原型)的__proto__,即子类.prototype.__proto__ -> 父类.prototype.__proto__
- 所以,子类实例.__proto__.__proto__ -> 父类.__proto__
用一张图表示如下:
- ES6中父类与子类之间和父类原型与子类原型之间用__proto__联系
- 类相当于构造函数,所以类的prototype指向类的原型,而相应的原型的constructor指向对应的类
- 实例是由类(构造函数)new出来的,所以实例的constructor指向类(构造函数)
- 子类实例的__proto__指向子类的原型