类式继承2——借用构造函数

下面这种模式解决了从子对象传递参数到父对象的问题。它借用了父对象的构造函数,将子对象绑定到this,同时传入参数:

  1. function Child(a, c, b, d) {
  2. Parent.apply(this, arguments);
  3. }

使用这种模式时,只能继承在父对象的构造函数中添加到this的属性,不能继承原型上的成员。

使用借用构造函数的模式,子对象通过复制的方式继承父对象的成员,而不是像类式继承1中那样通过引用的方式。下面的例子展示了这两者的不同:

  1. //父构造函数
  2. function Article() {
  3. this.tags = ['js', 'css'];
  4. }
  5. var article = new Article();
  6. //BlogPost通过类式继承1(默认模式)从article继承
  7. function BlogPost() {}
  8. BlogPost.prototype = article;
  9. var blog = new BlogPost();
  10. //注意你不需要使用`new Article()`,因为已经有一个实例了
  11. //StaticPage通过借用构造函数的方式从Article继承
  12. function StaticPage() {
  13. Article.call(this);
  14. }
  15. var page = new StaticPage();
  16. alert(article.hasOwnProperty('tags')); // true
  17. alert(blog.hasOwnProperty('tags')); // false
  18. alert(page.hasOwnProperty('tags')); // true

在上面的代码片段中,Article()被用两种方式分别继承。默认模式使blog可以通过原型链访问到tags属性,所以它自己并没有tags属性,hasOwnProperty()返回falsepage对象有自己的tags属性,因为它是使用借用构造函数的方式继承,复制(而不是引用)了tags属性。

注意在修改继承后的tags属性时的不同表现:

  1. blog.tags.push('html');
  2. page.tags.push('php');
  3. alert(article.tags.join(', ')); // "js, css, html"

在这个例子中,blog对象修改了tags属性,同时,它也修改了父对象,因为实际上blog.tagsarticle.tags是引向同一个数组。而对pages.tags的修改并不影响父对象article,因为pages.tags在继承的时候是一份独立的拷贝。

原型链

我们来看一下当我们使用熟悉的Parent()和Child()构造函数和这种继承模式时原型链是什么样的。为了使用这种继承模式,Child()有明显变化:

  1. //父构造函数
  2. function Parent(name) {
  3. this.name = name || 'Adam';
  4. }
  5. //在原型上添加方法
  6. Parent.prototype.say = function () {
  7. return this.name;
  8. };
  9. //子构造函数
  10. function Child(name) {
  11. Parent.apply(this, arguments);
  12. }
  13. var kid = new Child("Patrick");
  14. kid.name; // "Patrick"
  15. typeof kid.say; // "undefined"

如果看一下图6-4,就能发现new Child()对象和Parent()之间不再有链接。这是因为Child.prototype根本就没有被使用,它指向一个空对象。使用这种模式,kid拥有了自有的name属性,但是并没有继承say()方法,如果尝试调用它的话会出错。这种继承方式只是一种一次性地将父对象的属性复制为子对象的属性,并没有__proto__链接。

图6-4 使用借用构造函数模式时没有被关联的原型链

图6-4 使用借用构造函数模式时没有被关联的原型链

利用借用构造函数模式实现多继承

使用借用构造函数模式,可以通过借用多个构造函数的方式来实现多继承:

  1. function Cat() {
  2. this.legs = 4;
  3. this.say = function () {
  4. return "meaowww";
  5. }
  6. }
  7. function Bird() {
  8. this.wings = 2;
  9. this.fly = true;
  10. }
  11. function CatWings() {
  12. Cat.apply(this);
  13. Bird.apply(this);
  14. }
  15. var jane = new CatWings();
  16. console.dir(jane);

结果如图6-5,任何重复的属性都会以最后的一个值为准。

图6-5 在Firebug中查看CatWings对象

图6-5 在Firebug中查看CatWings对象

借用构造函数的利与弊

这种模式的一个明显的弊端就是无法继承原型。如前面所说,原型往往是添加可复用的方法和属性的地方,这样就不用在每个实例中再创建一遍。

这种模式的一个好处是获得了父对象自有成员的拷贝,不存在子对象意外改写父对象属性的风险。

那么,在上一个例子中,怎样使一个子对象也能够继承原型属性呢?怎样能使kid可以访问到say()方法呢?下一种继承模式解决了这个问题。