你不知道的JS读书笔记5—原型继承

查阅了挺多资料,也看了很久。网上列了好些继承的方法,各种方法,比如原型式继承,原型链继承,组合继承,寄生式继承,寄生组合式继承等。我挺不喜欢这样的方法的,感觉像是背答案一下,我也不知道这名称是谁取。猜猜应该来源某本权威的书,网上有很多类似的博文。我就说说我能理解的部分,以及整理成我理解的方式。不当之处,欢迎指出!

类继承

面向对象设计语言中比较经典的概念就是继承,那继承是什么?讲个通俗易懂的小故事:

突然你变成上帝了,跟传说一样,你一个人呆太久呆地太无聊了,你准备造动物了。你拿出一张神纸(类),在纸上写出动物具备的所有特征,然后对着纸一吹,吹(实例化)出了好多活生生的动物(实例);你看到一只你特别喜欢的动物——比如说它是猫,想多造一些;于是你又拿出来了一张神纸(类) ,写上这只猫区别于一般动物的特别之处,但是你在第一张纸上写的那些特征怎么办呢?如果再全部写一遍会让凡人嘲笑你这个笨上帝的,所以你灵机一动,写上了一句神奇的话:“凡动物有的特征猫也通通有”(继承)。然后有一口气,就出来了很多可爱的小猫咪(实例)。

这就是我理解的传统面向对象语言的类继承。需要特别指出的是,那张神纸(类)是抽象的存在,相当于动物实例的DNA。

原型继承

那JS的原型继承是什么呢?再接着这个故事讲:

突然我又变成了上帝,感觉你创造动物的方法太笨了,借用了你的一只动物A过来。然后(优雅地)挥一挥衣袖,就按照你的动物样子变出了另一只动物。你一看,大惊:“怎么你的动物只有脸,其他部位都是黑乎乎的一片?”我得意地说:“你仔细看,那部分写着,这里长得跟A一模一样。所以你参照你的A,就知道我的动物长什么样了。反正一模一样,我为什么要费劲再创造一遍呢?”你用异样的眼神看着我,心里想着“我今天怎么遇到奇葩了?”。
故事讲完了。你的动物A可以类比看作传统概念中的类,而我从中创造出来的动物可以看作实例,或者类。因为在JavaScript中无论是类,还是实例,本质上都是对象。我们又怎么去区分这个对象是实例,还是类(除了构造函数充当了类的角色)。所以上面第二个小故事,可以比作传统概念中类的实例化,或者类的继承都可以。

二者区别

在传统的面向对象语言中,类实例化是一个抽象概念具体化的过程,而类继承是抽象概念继续生成另一个抽象概念的过程。而在JS中,没有抽象的东西,只有具体的对象。所有这些看起来像类,实例,继承的东西,本质上是将不同的对象之间建立关联,这也就是JS特有的原型链。之所以称为原型继承,可能是想向面对对象语言靠近吧,有人提出行为委托的叫法更加合适。

也许你可能还对我的说法有所疑问,拿出我前一篇博文的例子来说事。你看Animal是个抽象构造函数(类),它不是对象。

function Animal(name, species, n){
    this.name = name;
    this.species = species || 'animal';
    this.legsNum = n;
}

Animal.prototype.sayHello = function(){
    console.log('Hello, I am a ' + this.species + ', my name is ' + this.name);
}

var dog = new Animal('Belly', 'dog', 4);

那你是否记得new操作具体做了什么吗?我们再来回顾一下。

function New( P ){
     function F(){}
     F.__proto__ = P.prototype;
     return function(){
          P.call(F, arguments);
          return F;
     }
}

它分成了两个步骤,将新创建对象与构造函数的原型对象建立关联,用新创建对象作为this上下文执行一次构造函数内的代码。所以,我们可以看到dog与Animal的联系更像是一种行为的委托。当访问dog上没有的属性或方法时,它会去Animal上查找。这看起来,很像类的继承。其实,它是属于原型继承。

原型继承实现方法

了解了类继承与原型继承之间的区别之后,我们还是沿用老的叫法。我们来看一下原型继承有哪些方法。

对于类来说,重要的是它的原型对象prototype,因为它是被实例多共享的属性。至于怎么建立两个类之间的继承关系呢,将子类的原型对象的原型链指向父类的原型对象 。 从语法实现方式上来说,有以下几种方式

  • B.prototype = new A()
  • B.prototype = Object.create( A.prototype )
  • Object.setPrototypeOf(B.prototype , A.prototype ),ES6的新方法

顺便提一句,类的实例化与类的继承其实挺像的,只是将一个实例的原型链指向类的原型对象。

  • a = new A()
  • a = Object.create( A.prototype )
  • Object.setPrototypeOf(a , A.prototype )

顺便,我们看下Object.create到底做了什么。以下是ES5之前的polyfill版本。

if(typeof Object.create !== 'function'){
     Object.create = function(o){
          function F(){}
          F.prototype = o;
          return new F();
     }
}

下面我们拿具体一点的例子来说明。

// 父类
function Animal(){
     this.type = 'animal'; 
}
Animal.prototype.sayHello = function(){
     console.log('Hi, human!');
}
//子类 
function Cat( name ){
     Animal.call(this, arguments);
     this.name = name;
}
Cat.prototype = new Animal();
// 或者   Cat.prototype = Object.create(Animal.prototype);
// 或者 Object.setPrototypeOf(Cat.prototype, Animal.prototype);
Cat.prototype.miao = function(){
     console.log('Miao~W~');
}
var cat = new Cat('kitty');
cat.sayHello(); // 'Hi, human!'

Cat上没有sayHello的方法,但由于它继承了Animal类,自然有个Animal原型对象上的方法了。当然,没有人会用这种方式Cat.prototype = Animal.prototype;来实现继承吧。

行为委托出场

上面的实现方式还是遵守了传统类继承的方式,让我们抛开类啊,继承啊,实例化啊,用JS的建立关联本质来实现一次。

// 一个对象
var Animal = {
     init: function(){
          this.type = 'animal'; 
     },
     sayHello: function(){
          console.log('Hi, human!');
    }
}

//  另一个对象
var Cat = Object.create( Animal);
Cat.setup = function( name ){
     this.init();
     this.name = name;
} 
Cat.miao = function(){
     console.log('Miao~W~');
}
var cat = Object.create(Cat);
cat.setup();
cat.sayHello(); // 'Hi, human!'

这里不再出现new,prototype,构造函数,是不是整个世界都清净了~~~我们只用了Object.create就搞定了类的实例,类的继承啊这些烦人的概念。

另外,需要提一下的是,父类的修改对子类的影响。接着上面的代码:

 Animal.sayHello = function(){
      console.log('Hi, I changed a way to say Hello!');
}
 cat.sayHello(); //  'Hi, I changed a way to say Hello!'

 Animal = { 
      sayHello: function(){
           console.log('You will never hear me say hello!');
     }
 }
cat.sayHello(); //  'Hi, I changed a way to say Hello!'

其中的原因,相信聪明的你应该能想得出来吧。

总结

总的来说,JS的原型继承与类继承还是有很大差别的。类继承是抽象类再生成抽象类的过程,而原型继承是具体对象之间建立的一种关联,或者说是行为委托。我们可以用仿造类继承的原型继承概念来实现,也可以用JS语言的本质行为委托理念来实现相同的东西。

参考资料: