背景
- 以前也听过什么对象冒充继承,什么原型继承的,感觉还是太乱,特此总结一下。
ES5继承
一、私有属性继承
function Animal(){
this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
console.log('say');
}
function Tiger(){
Animal.call(this)//继承调用父类函数,把this指向改为子类
}
let babyTiger = new Tiger()
console.log(babyTiger.name);
//babyTiger.say()报错无法得到
- 先说一下私有属性继承,就是大家说的对象冒充继承,用New的话,就相当于copy一个对象,对象里也有类里的私有方法 ,那么如何让子类继承父类的私有属性?那就让子类也搞个跟父类一样的私有即可。子类用new Tiger,但Tiger里面不能直接调用Animal()因为,Animal里的this指向是global,如果这么调相当于把name挂到global上。
function Animal(){
this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
console.log('say');
}
function Tiger(){
Animal()//这么调用会挂到global上
}
let babyTiger = new Tiger()
console.log(name);//yehuozhili
console.log(babyTiger.name);//undefined
- 所以使用call改变this指向,Animal里的this就指向子类实例了。这样子类实例也能打印name了。
二、原型方法继承
第一种方法
- 再说下原型方法继承,就是前面在Animal.prototype上加的say方法。
- 这个原型方法继承本质就是沿着
__proto__
去找有没有这个方法。 - 而原来的Animal的实例本来也没这个方法,沿着
__proto__
找到了Animal.prototype找到了这个方法,所以才能调用。 - 那么我们只要让Tiger的实例沿着
__proto__
去寻找就行了,Tiger的实例的__proto__
本来是指向其类Tiger.prototype,但是Tiger.prototype是js引擎自己生成的,并没有和Animal有啥关系,所以需要让Tiger.prototype.__proto__=Animal.prototype
function Animal(){
this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
console.log('say');
}
function Tiger(){
}
let babyTiger = new Tiger()
Tiger.prototype.__proto__=Animal.prototype
babyTiger.say()
第二种方法
function Animal(){
this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
console.log('say');
}
function Tiger(){
}
Tiger.prototype=Object.create(Animal.prototype)//注意顺序必须在子类生成实例前
Tiger.prototype.constructor=Tiger
let babyTiger = new Tiger()
babyTiger.say();
- 这个Object.create方法实质是新建个function,然后js会生成这个function的prototype,让这个prototype等于传入参数,也就是Animal的prototype,然后返回这个function的实例,这个function的实例沿着
__proto__
就能找到Animal原型链上的方法,返回的这个实例等于Tiger.prototype,也就是说Tiger的实例也能沿__proto__
一直找到Animal上的方法了。最后还有个地方不一样,就是要改变这个function实例的constructor,因为Tiger的实例没有constructor,往上找是Tiger的prototype也就是我们做的这个function的实例,这个实例已经改掉了,就是function.prototype改给了Animal.prototype,所以本来指向fn.prototype的指向了Animal.prototype,于是需要让fn实例的constructor指向Tiger。
function Animal(){
this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
console.log('say');
}
function Tiger(){
}
function create(parentprototype){
let fn = function(){}
fn.prototype=parentprototype
let sfn = new fn;
sfn.constructor= Tiger//不加这句就是Animal
return sfn
}
Tiger.prototype=create(Animal.prototype)//注意顺序必须在子类生成实例前
let babyTiger = new Tiger()
babyTiger.say();
console.log(babyTiger.constructor);//Tiger
第三种方法
- 第三种方法就是网上常说的new一个父类实例,然后改造这个父类实例变为子类prototype
function Animal(){
this.name = 'yehuozhili'
}
Animal.prototype.say = function(){
console.log('say');
}
function Tiger(){
Animal.call(this)
}
Tiger.prototype=new Animal()
Tiger.prototype.constructor=Tiger
let babyTiger=new Tiger()
babyTiger.say()
console.log(babyTiger.constructor);
-
这个方法跟上面那个方法差不多,找了Animal的实例替代Tiger的prototype,所以还需要把constructor也替换掉,不然打印babyTiger.constructor打印的是Animal。
-
所以看来这三种方法里,最推荐的是第一种方法,因为第一种方法不需要设定constructor了,本来就是Tiger生成的。后面两种方法都需要再去设置下constructor。而且第一种方法是最符合大众对于继承的逻辑认知的。
三、静态方法继承 -
这个网上很多地方都没讲到,可能大家一开始不在意这个继承,es6语法子类extend原来父类,父类的静态方法一样被继承到子类的。
-
静态方法的本质是加在父类上,而不是加在父类原型上,所以根据原型链原则,只要把子类的
__proto__
变为父类即可继承。
function Animal(){
this.name = 'yehuozhili'
}
Animal.ppp=function(){
console.log('pp');
}
function Tiger(){
Animal.call(this)
}
Tiger.__proto__=Animal
Tiger.ppp()//pp
- 所以ES5要完美继承得把这3方法个继承全部写上才ok,网上很多地方就只继承私有属性和原型链。
ES6继承
一、普通继承
- es6继承比较简单,一个extend就可以继承到了:
class Animal{
constructor(){
this.name = 'yehuozhili'
}
say(){
console.log('say');
}
static ppp(){
console.log('ppp');
}
}
class Tiger extends Animal{
}
let babyTiger = new Tiger();
console.log(babyTiger.name);
babyTiger.say()
Tiger.ppp()
- 一个extend不管是私有还是原型还是静态全部搞定,真的是比es5省力太多。
二、传参继承
- es6的传参操作方法就跟es5有点不一样,主要是有个super的东西。
- 如果子类不写constructor,那么就会找父类的constructor。
- 但是如果子类写了constructor,那么如果需要传参,必须写super。不写constructor相当于它隐式调用了前面继承私有属性的
Animal.call(this)
,但是也无法在constructor里面加上Animal.call(this)
,会报错,只能用super。
class Animal{
constructor(name){
this.name = name
}
say(){
console.log('say');
}
static ppp(){
console.log('ppp');
}
}
class Tiger extends Animal{
constructor(name){
//this.name=name 错误写法,会报错
//Animal.call(this,name) 错误写法,会报错
super(name)
}
}
let babyTiger = new Tiger('yehuo');
console.log(babyTiger.name);
- 有人会问super到底是个啥?从源码里看会发现这段:
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ?
Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
_getPrototypeOf(Tiger).call(this, name)
- 这个_getPrototypeOf这个方法就是个寻找
__proto__
的函数,如果这个东西有Object.getPrototypeOf
方法,那么就通过这个方法去寻找,否则就直接去找它的__proto__
。貌似是因为现在浏览器一般访问原型速度都比较慢,从性能上看用Object.getPrototypeOf
方法去找原型性能比较好。 - 所以这个super本质是找传入的子类的
__proto__
然后进行调用,在这个地方就相当于替换成Animal.call(this)
- 但是实际上super位置不同,代表的也不一样。
- 如果能理解上面这个
_getPrototypeOf
相信下面的super指谁都能一眼看出。
class Animal{
constructor(name){
this.name = name
}
say(){
console.log('say');
return 300
}
static ppp(){
console.log('ppp');
return 400
}
}
class Tiger extends Animal{
constructor(name){
super(name)//指Animal
}
static a(){
super.ppp()//指Animal
}
say(){
super.say()//指Animal.prototype
}
static get fff(){
console.log(super.ppp());//指Animal,但是其中的this指向为Tiger
}
}
Tiger.a()//ppp
let babayTiger = new Tiger()
babayTiger.say()//say
Tiger.fff//ppp 400
- 最值得注意的就是这个
static get fff
函数,这个函数为什么this指向变了?源码是这么写的:
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
var base = _superPropBase(target, property);
if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) { return desc.get.call(receiver); } return desc.value;
};
} return _get(target, property, receiver || target);
}
_createClass(Tiger, [{
key: "say",
value: function say() {
_get(_getPrototypeOf(Tiger.prototype), "say", this).call(this);
}
}], [{
key: "a",
value: function a() {
_get(_getPrototypeOf(Tiger), "ppp", this).call(this);
}
}, {
key: "fff",
get: function get() {
console.log(_get(_getPrototypeOf(Tiger), "ppp", this).call(this))
}
}]);
- 这个_get函数,实际上就是看你有没有Reflect,然后调用
Reflect.get
,如果没有它就自己造一个。而_getPrototypeOf
前面已经解读过了,就是找__proto__
,而这里的this,因为是在Tiger里进行的,所以this是Tiger,实际上相当于console.log(Reflect.get(Animal,'ppp',Tiger))
。 - 所以,这时候super指的还是父类,但是this变了,所以取不到私有属性,但是静态方法跟this无关,所以可以拿到静态方法。
三、抽象类
- 抽象类就是不给new这个类,所有别的类继承它,继承的类可以new。
- 实际上就是写这个类的时候加一个判断。
- 上一篇写的是es5类表示es6里可以看到不给用类自己调用使用的是
instaceof
,取到this后然后instanceof
进行判断。 - 这里就不能用这个方法了,因为new出来的都是父类的实例,所以都是的,这里有2种方法:
class Animal{
constructor(name){
console.log(new.target);
console.log(this.constructor);
this.name = name
if(new.target===Animal){
throw new Error('forbid new')
}
}
}
class Tiger extends Animal{
}
new Animal;
new Tiger
- 一个就是打印
new.target
,一个就是打印this.constructor
解决。