JavaScript面向对象之二(构造函数继承)

1,162 阅读3分钟

学习一波阮一峰的博客 戳这里

博客中是自己的理解,以及对大佬描述不清楚的地方进行了修正,也算是自己的一个再(xiao)产(tu)出(cao)吧

上一篇:JavaScript面向对象之一(封装)

构造函数进行继承


先来看个简单的:

function Animal(){
    this.type = 'animal;'
}

function Cat(name, color){
    this.name = name
    this.color = color
    //这里用call,个人觉得更好些
    Animall.call(this)
}

var cat = new Cat('po', 'orange')
console.log(cat.type) //animal

创建了一个Animal和Cat构造函数,然后在Cat里面调用Animal的构造函数,在将Cat实例化,就可以访问到Animal的属性了。 这个例子显然有问题,放在第一个就是用来找茬的。

什么问题呢?假如我想使用在Animal上的公共方法,像这样Animal.prototype.eat = function(){ console.log('animal eat') },用cat.eat()是访问不到的。

为什么呢?因为我们Cat和Animal的原型根本就没有关联起来呀。你看看咱们上面的代码,那个地方关联过?

使用原型进行继承


那下面我们就将两者的原型关联起来试试看

function Animal(){
    this.type = 'animal;'
}
Animal.prototype.eat = function(){ console.log('animal eat') }

function Cat(name, color){
    this.name = name
    this.color = color
}

Cat.prototype = new Animal()

var cat = new Cat('po', 'orange')
console.log(cat.type) //animal
console.log(cat.eat()) //animal eat

这种方法好!可以拿到属性和方法,一举两得。但是,这里有个陷阱(keng)!!!Cat.prototype = new Animal()之后,我们的Cat.prototype里面的所有方法都消失了!这是怎么回事?因为new,new做了四件事,这里再次回顾一下:

var temp = {}
temp.__proto__ = Animal.prototype
Animal.call(temp)
return temp

看到了吗?new会使用一个空对象并且将其返回。这样一来我们的Cat.prototype就被清空了(包括自身的constructor)。所以我们需要自己多做一件事Cat.prototype.constructor = Cat

如果我们不这样做,那么Cat.prototype的构造函数会是什么?我们在去看看new做的第二件事,这个空对象指向了Animal.prototype,所以Cat.prototype自身如果没有constructor属性的话就会去Animal.prototype上面去找。Cat.prototype.constructor === Animal //true

所以,如果我们替换了prototype,需要手动去纠正它。

直接继承prototype


既然上面这种方法有坑,而且的的确确让你很容易漏掉,那我们改进一下:

function Animal(){}
Animal.prototype.eat = function(){ console.log('animal eat') }

Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat

var cat = new Cat('po', 'orange')
console.log(cat.eat()) //animal eat

既然我们想从Animal.prototype上面那东西,直接从上面拿不就行了?而且我还机智的填了上面会出现的坑。同时结果也是我想要的。

但是!!我们的Cat.prototype和Animal.prototype指向的是同一个原型,这会导致我在Cat.prototype上做了什么事,会同时发生在Animal.prototype上。这是为什么呢?MDZZ,这两个就是同一个东西呀,原型是堆中一块内存,Cat和Animal都指向这块内存,操作的是同一个东西,怎么会不影响?

与此同时,自以为聪明的填坑Cat.prototype.constructor = Cat,此时的Cat和Animal的原型是同一个,修改了constructor之后,导致Animal的constructor变成了Cat。这种方法果断PASS。。。

利用空对象作为中介


var F = function(){}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat

我们使用中间对象进行过度,巧妙的将Cat.prototype和Animal.prototype解耦,这样就不会出现问题了。仔细观察会发现这个和new做的事情有异曲同工之处。

我们进行封装一下在使用

function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
}

extend(Cat,Animal);
var cat1 = new Cat('po' 'orange');
alert(cat1.eat()); // animal eat

这里Child.uber = Parent.prototype的意思类似于__proto__

拷贝继承


这里还有一种简单粗暴的方式

function extend2(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
}

function Animal(){}
Animal.prototype.eat = function(){ console.log('animal eat') }

extend2(Cat, Animal);
var cat1 = new Cat('po' 'orange');
alert(cat1.eat()); // animal eat

直接遍历父类的原型,一个个复制到子类原型上即可。

感慨一下,大佬还是大佬呀。。。