面向对象的程序设计-3-继承
写在前面
都知道,当我们读取一个对象的属性或方法的时候,会优先在这个对象上面找,如果在这个对象上找不到就会遍历他的原型,还没找到?--->原型的原型,又没找到?-->继续往上。。。
这便是原型链的功用。下面,我探讨了一下原型链的使用与扩展,依靠原型链实现继承。
至于什么是继承? 我的理解是,一个对象可以直接使用另一个对象的属性和方法。
本文结构:
- 直接使用原型链
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
其中的继承方式层层递进,不断进化完善缺点。
进化过程: 1 → 2 → 3↘
→ → 6
4 → 5 ↗
一、直接使用原型链
1.回顾我的上一遍文章中写的,构造函数、原型、实例之间的关系::
每一个构造函数都有prototype指针指向一个原型对象,原型对象有一个constructor指针指向构造函数,而实例通过[[prototype]]指针共享一个原型对象。
2.原型链继承的实现:把一个构造函数的原型对象等于另一种类型(另一种构造函数)的实例。
function SuperType() { this.property = 'aaa'; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType() { this.subproperty = 'bbb'; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; } var instance = new SubType(); console.log(instance)
关系图解
执行的结果:SubType.prototype 指向了一个 SuperType 的实例。并通过 SubType.prototype.getSubValue 为实例添加了方法。
当最终实例 instance 调用 getSuperValue 方法的时候。 先遍历实例instance对象,没找到--->搜索instance.prototype(也是SuperType的一个实例),还没找到--->搜索instnce.prototype.prototype (也是SuperType.prototype);
3.注意:
3.1 别忘记默认的原型
原型链可以无限长(当然为了性能,不要搞太长)。但是去到最后必然是 Object.prototype
3.2 确定原型与实例的关系:原型链中的出现的原型都算该实例的原型
instance instanceOf SubType // true
SuperType.isPrototypeOf(instance) // true
3.3 谨慎地定义方法: 因为是修改了SubType的原型对象,因此必须在替换之后才定义原型上的方法: SubType.prototype.getSubTypeValue() 并且不能使用字面量,不然又换了一个新对象。
4.直接使用原型链继承的优缺点
SuperType构造函数中若使用了引用类型值。就会放映在SubType的原型上。在原型上出现引用类型,容易被实例修改。影响所有的实例。
使用 SubType 创建对象的时候,不能再给SuperType传递参数了。因为SubType.prototype 已经是一个SuperType的实例了。
二、借用构造函数实现继承
为解决直接使用原型链继承中不能给 SuperType 传递参数的问题以及引用类型值问题而生。
function SuperType(name) { this.colors = ['red','blue','green']; this.name = name; } function SubType(name) { SuperType.call(this, name); } var instance1 = new SubType('jody'); var instance2 = new SubType('miaowwwww'); instance1.colors.push('black'); console.log(instance1) console.log(instance2); console.log(instance2.name)
首先要知道,构造函数只不过是在特定的环境(this)中执行代码,并为它定义属型而已。因此,大可以在SubType中执行以下SuperType,这样一来,SubType 就获取 SuperType 的属性和方法。
优点:解决了原型链继承的引用类型问题,以及 传递参数的问题
缺点:若仅仅使用借用构造函数,无法避免构造函数模式的问题,(方法都在实例中,方法的复用性降低了),并且instance1 并不是SuperType 的一个实例,无法使用instanceOf,isPrototypeOf.
三、组合继承
鉴于借用构造函数继承的缺点,当然不会仅仅借用构造函数啦。
组合继承又称伪经典继承,结合了原型链和借用构造函数 两种技术。(相信,大家看完借用构造函数的缺点就已经想到了)
function SuperType(name) { this.colors = ['red','blue','green']; this.name = name; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } // 继承方法、 SubType.prototype = new SuperType(); // 为了使用SuperType.prototype上的方法,所以要添加 SubType.prototype.constructor = SubType; //new 的时候要调用这个constructor?并没有,这个属性只是标记这个对象是这个类型,多一个判断方法而已。这里仅仅是补全替换prototype导致的constructor的缺失 SubType.prototype.sayAge = function() { // 在SubType原型上,同时也是SuperType的实例 console.log(this.age); } var instance1 = new SubType('jody', 22); console.log(instance1)
这种方式确实完美解决了传递参数,引用类型,公共方法重用的缺点。同时也可以被 instanceOf 和 isPrototypeOf() 识别。
但是它产生了新的问题:SubType实例含有(colors,name)属性,SubType.prototype也有(colors,name)这部分属性冗余了。(这个问题将在 六、组合式继承中解决)
四、原型式继承
// 原型式继承即通过object函数实现 function object(o) { function F() {}; F.prototype = o; return new F(); } // 实例 var person = { name: 'miaowwwww', friends: ['aaa', 'bbb'] }; var person1 = object(person); person1.name = 'jody'; person1.friends.push('ccc'); console.log(person1)
原型式继承跟原型链是一个理念(把原型指向另一种类型的对象(实例))。把空构造函数的原型指向一个对象,然后创建空构造函数的实例,并放回。
4.1 object函数在es5 中得到了实现:Object.create(obj, defineProperties) :第二个参数而可选,设置属性,及其描述,描述默认false
var person2 = Object.create(person, { name:{ value: 'dd', writable: false }, age: { value: 22 } }) console.log(person2)
4.2 缺点:跟原型链一样,是原型上的引用类型值
五、寄生式继承
5.1 寄生式是在原型式的基础上的改进。 —_— 只是把定义实例属性的逻辑放到一个函数里面了而已
function craateAnother(original) { var clone = Object.create(original); clone.sayName = function() { console.log(this.name); }; return clone; } var person = { name: 'jody', friends: ['aa','bb']}; var person1 = createAnother(person);
5.2 缺点:公共的属性和方法(如:sayName)不能复用。
六、寄生组合式继承
这是 三、组合继承 和 五、寄生式继承的技术组合。(组合继承使用寄生式继承修改了,组合继承的缺点。
1.先贴出组合继承的代码理解一下:
组合继承的缺点:两次调用SuperType(), 导致的 SubType.prototype产生了冗余的(name,colors...)等SuperType的属性。
function SuperType(name) { this.name = name; this.colors = ['aa','bb']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); // 第二次执行SuperType this.age = age; } SubType.prototype = new SuperType(); //第一次执行SuperType SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); }
既然知道 SubType.prototype = new SuperType(); 只是为了获取SuperType.prototype。 那么可以绕过执行SuperType,直接获取SuperType.prototype。(这便是寄生式的作用)
2 寄生式继承的用处 : 使用Object.create() 创建一个空的实例,并且该实例的prototype 指向了 SuperType.prototype. (干净,无冗余属性)
function inheritPrototype(subType, superType) { var emptyInstance = Object.create(superType.prototype); emptyInstance.constructor = subType; //补充constructor的缺失 subType.prototype = emptyInstance; }
3.寄生组合式继承代码
function inheritPrototype(subType, superType) { var prototype = Object.create(superType.prototype); prototype.constructor = subType; //补充constructor的缺失 subType.prototype = prototype; }
function SuperType(name) { this.name = name; this.colors = ['aa','bb']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); // SubType.prototype = SuperType.prototype; //不可以这么做,因为sayAge会添加在SuperType.prototype中 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); } var instance = new SubType('jody', 22); console.log(instance) console.log(instance.name)
至此: 寄生组合式继承被认为是引用类型最理想的继承范式。
备注:(接受指导与批评)
本文是阅读高级程序设计(第三版)P162~P174 的理解与总结。