构造函数的继承

本例主要介绍对象之间 继承 的五种方法

例如:

var Cat = {
    name  : '',
    color : ''
}

还有一个 猫 对象的构造函数

var cat1 = {};

cat1.name = '大毛';
cat1.color = '黄色';

var cat2 = {};

cat2.name = '二毛';
cat2.color = '黑色';

但是猫也属于动物,如何让猫来继承动物而不重写 Cat 函数呢?

方法一:构造函数绑定

function Cat(name,color){
    return {
        name  : name,
        color : color
    }
}

方法二:prototype 模式
如果 猫 的 prototype 对象,指向一个 Animal 的实例,那么所有 猫 的实例,都继承了 Animal 了。

var cat1 = Cat('大毛','黄色');
var cat2 = Cat('二毛','黑色');

代码的第一行,将 Cat 的 prototype 对象指向一个 Animal 实例,它相当于完全替换了 prototype 之前的值,所以,需要在第二行将 Cat 的 prototype 属性的 constructor 属性重置回来。因为 cat1 这种实例也有 constructor 属性,其 constructor 属性应该为 Cat(默认调用prototype对象的constructor属性), 但是如果不手动重置回来的话,Cat 丢失了自己的 constructor 属性,则会继续顺着原型链查找,找到 Animal ,则导致了 cat1 的 原型的 constructor 属性成为了 Animal 而不是原本的 Cat,所以会导致继承链的紊乱。

所以,在进行原型链继承的时候,如果重写了原型属性之后,则一定要重置回来。

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

方法三: 直接集成 prototype
这是对方法二的补充,由于 Animal 中,不变的方法,都可以写入 Animal.prototype 中,所以我们可以直接继承 Animal.prototype

var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');

由于不用建立和执行 Animal 的实例了,所以效率较高,也比较省内存,但是由于对象是引用的,所以 Cat.prototype 和 Animal.prototype 指向了同一个对象,所以,对两者任一进行就改,就会反映到另一者上。所以 对 Cat 的 constructor 的修改,其实也修改了 Animal 的 constructor 对象。

方法四:利用空对象作为中介

这也是集合了前两种方法的优点,也避免了缺点。

console.log(cat1 instanceof Cat); //true
console.log(cat2 instanceof Cat); //true

由于 F 是空对象,其本身和实例几乎不占内存,而 Cat.prototype 继承自 F 的实例,所以修改 Cat.prototype 对象,也不会影响到 Animal 的 prototype 对象

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.type = '猫科动物';
    this.eat = function(){console.log('吃老鼠');}
}

为了方便使用,将上面的方式,封装为一个通用函数。

var cat = Cat('大毛','黄色');
console.log(cat.type); // 猫科动物
cat.eat(); // 吃老鼠。

使用的时候,方法如下:

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

Cat.prototype.type = '猫科动物';
cat.prototype.eat = function(){console.log('吃老鼠');}

这个 extend 函数,就是 YUI 库使用的对象继承方法,貌似也是大神道格拉斯发明的。

必要说明的一点是此函数最后一句

var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');

等于给子对象,调用继承的父对象的方法开辟了一个快捷通道,同时,如果子对象有同名的方法覆盖了父对象的方法,通过这种方法也可以调用。

方法五:拷贝继承

以上集中方法都是采用原型链的方式实现的继承,那么如果把要继承的父对象的方法,都拷贝进子对象,那么也可以说实现了继承。

console.log(cat1.eat === cat2.eat); //true

再写一个函数,目的是实现对象属性和方法的拷贝。

isPrototypeOf()

对于此函数,即将父对象的属性,赋值给同名的子对象。

使用方法:

console.log(Cat.prototype.isPrototypeOf(cat1)); //true

其实对于此函数来说,这里如果 Parent 对象中的某个方法或属性也是一个对象的话(引用),则继承到的 Child 中的同名方法或属性,在内存中指向的是同一个位置,此时如果在一处修改了此属性或方法,则另一处也有变化。解决这种问题的方式是,深拷贝,及进行赋值的时候,判断父对象此属性或方法是否是引用了类型,如果是,则递归即可。

没有评论

  • :arrow:
  • :grin:
  • :???:
  • :cool:
  • :cry:
  • :shock:
  • :evil:
  • :!:
  • :idea:
  • :lol:
  • :mad:
  • :mrgreen:
  • :neutral:
  • :?:
  • :razz:
  • :oops:
  • :roll:
  • :sad:
  • :smile:
  • :eek:
  • :twisted:
  • :wink: