查阅了挺多资料,也看了很久。网上列了好些继承的方法,各种方法,比如原型式继承,原型链继承,组合继承,寄生式继承,寄生组合式继承等。我挺不喜欢这样的方法的,感觉像是背答案一下,我也不知道这名称是谁取。猜猜应该来源某本权威的书,网上有很多类似的博文。我就说说我能理解的部分,以及整理成我理解的方式。不当之处,欢迎指出!
类继承
面向对象设计语言中比较经典的概念就是继承,那继承是什么?讲个通俗易懂的小故事:
突然你变成上帝了,跟传说一样,你一个人呆太久呆地太无聊了,你准备造动物了。你拿出一张神纸(类),在纸上写出动物具备的所有特征,然后对着纸一吹,吹(实例化)出了好多活生生的动物(实例);你看到一只你特别喜欢的动物——比如说它是猫,想多造一些;于是你又拿出来了一张神纸(类) ,写上这只猫区别于一般动物的特别之处,但是你在第一张纸上写的那些特征怎么办呢?如果再全部写一遍会让凡人嘲笑你这个笨上帝的,所以你灵机一动,写上了一句神奇的话:“凡动物有的特征猫也通通有”(继承)。然后有一口气,就出来了很多可爱的小猫咪(实例)。
这就是我理解的传统面向对象语言的类继承。需要特别指出的是,那张神纸(类)是抽象的存在,相当于动物实例的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语言的本质行为委托理念来实现相同的东西。
参考资料:
- https://github.com/getify/You-Dont-Know-JS 你不知道的JS(上卷)
- http://www.cnblogs.com/wilber2013/p/4966587.html 关于JavaScript继承的那些事
- http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html Javascript继承机制的设计思想
- http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html Javascript 面向对象编程(一):封装
- http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html Javascript面向对象编程(二):构造函数的继承
- http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html Javascript面向对象编程(三):非构造函数的继承
- http://yijiebuyi.com/blog/bc5a9d2f60b829d6e7d052541f9ae681.html javascript 中面向对象实现 如何继承
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof MDN instanceof
- https://medium.com/javascript-scene/common-misconceptions-about-inheritance-in-javascript-d5d9bab29b0a#.kfk2gyybl Common Misconceptions About Inheritance in JavaScript