前言:面试被锤一定要会的茴香豆的六种写法, 整理自红宝书
这里循序渐进展开,中心思想是:
利用原型让一个引用类型继承另一个引用类型的属性和方法
原型链
function ParentType() {
this.property = true;
}
Parent.prototype.getParentValue = function() {
return this.property;
}
function SonType() {
this.sonProperty = false;
}
// son继承parent
SonType.prototype = new ParentType();
SonType.prototype.getSonValue = function() {
return this.sonVaule;
}
var instance = new SonType();
alert(instance.getParentValue()); // true
重写son的原型对象,代之以一个Parent的实例,这样原来存在于parent的属性和方法也存在于Son.prototype中了
判断 实例和原型的关系
- 方法一: instanceof
alert(instance instanceof Object); // true
alert(instance instanceof ParentType); // true
alert(instance instanceof SonType); // true
- 方法二: isPrototypeOf()
alert(Object.prototype.isPrototypeOf(instance)); // true
alert(ParnetType.prototype.isPrototypeOf(instance)); // true
alert(SonType.prototype.isPrototypeOf(instance)); // true
纯原型链的 问题:
- 引用类型带来的问题,其实在创建类那一章节就讲到了
function ParentType() {
this.colors = ['red', 'green'];
}
function SonType() {
}
SonType.prototype = new ParentType();
var instance1 = new SonType();
instance1.colors.push('blue');
alert(instance1.colors); // ['red', 'green', 'blue']
var instance12 = new SonType();
alert(instance2.colors); // ['red', 'green', 'blue']
相当于SonType的prototype,用了同一个数组,这个数组是ParentType.colors的一个实例。然后就是SonType的所有实例都会共享这个引用属性,所以才会造成变更一个Son实例,另外一个引用属性也会变更
2. 创建子类型实例时,不能像超类型的构造函数传递参数。
借用构造函数
或者叫做伪造对象或经典继承
function ParentType() {
this.colors = ['red', 'green'];
}
function SonType() {
// 继承了ParentType
// 在子类构造函数内部调用超类构造函数
ParentType.call(this);
}
var instance1 = new SonType();
instance1.colors.push('blue');
alert(instance1.colors); // ['red', 'green', 'blue']
var instance12 = new SonType();
alert(instance2.colors); // ['red', 'green']
新增的那一行实际上是在调用了Parent的构造函数,这样就在SonType对象执行ParentType()函数中定义的所有对象初始化代码。结果就是SonType的每个实例都会有自己的colors副本了。
这仅仅是解决了上面第一个问题。
第二个问题,子类向超类传递参数
function ParentType(name) {
this.name = name;
}
function SonType() {
// 继承了ParentType
ParentType.call(this, 'nichol');
// 实例属性
this.age = 21;
}
var instance = new SonType();
alert(instance.name); // 'nichol'
alert(instance.age); // 29
组合继承
还叫做伪经典继承。
组合继承究竟组合了什么东西在里面呢?
原型链和借用构造函数技术。
思路就是使用 原型链实现对原型属性和方法的 继承,又通过 借用构造函数来实现对实例属性 的继承
function ParentType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Parent.prototype.getParentValue = function() {
return alert(this.name);
}
function SonType(name, age) {
// 继承了ParentType属性
ParentType.call(this, name);
// 实例属性
this.age = 21;
}
// 继承方法
SonType.prototype = new ParentType();
SonType.prototype.sayAge = function() {
alert(this.age);
}
var instance1 = new SonType('jack', 21);
instance1.colors.push('black');
alert(instance1.colors); // ['red', 'green', 'black']
instance1.sayName(); // 'jack'
instance1.sayAge(); // 21
var instance2 = new SonType('Tom', 23);
alert(instance2.colors); // ['red', 'green']
instance2.sayName(); // 'Tom'
instance2.sayAge(); // 23
这种方式是js中最常用的继承模式。
缺点:会在下面寄生组合式继承提到。
原型继承
大致如下
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
先创造临时性的构造函数,然后把传入的对象作为这个临时构造函数的原型,然后返回这个临时类型的新实例。
var person = {
name: 'Nichol',
friends: ['Jack', 'Tom'],
}
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(person.friends); // ['Jack', 'Tom', 'Rob', 'Barbie']
发现创建的两个新的实例,这个应用类型的friends不仅被person所有,还被anotherPerson和yetAnotherPerson所有。
ES5新增了Object.create()规范化了原型继承,接收两个参数:1. 用作新对象原型的对象 2. 为新对象定义额外属性的对象。
var person = {
name: 'Nichol',
friends: ['Jack', 'Tom'],
}
var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(person.friends); // ['Jack', 'Tom', 'Rob', 'Barbie']
可以看到,如果只塞入一个变量,Object.create()和object()一样,第二个参数使用方法也很简单,就省的继承以后重新再变更name了
var anotherPerson = Object.create(person, {
name: {
value: 'Greg',
}
});
alert(anotherPerson.name); // 'Greg'
原型模式就是想让一个实例对象与另一个实例对象保持类似,不需要创建构造函数了。
缺点还是很明显:引用类型的属性始终都会共享。
寄生式继承
与原型式机密相关的思路,创建一个仅用于封装继承过程的函数,函数在内部以某种方式来增强对象,再像真地是它做了所有工作一样返回对象。(说的弯弯绕绕)
function createAnother(origin) {
var clone = object(origin);
clone.sayHi = function() {
alert('Hi');
}
return clone;
}
// 可以这么用
var person = {
name: 'Nichol',
friends: ['Jack', 'Tom'],
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'Hi'
新对象不仅有person所有属性和方法,而且还有自己的sayHi(),
缺点: 为对象添加函数,不能做到函数复用而降低效率。
寄生组合式继承
组合继承有自己的缺点:
调用两次超类型构造函数: 一次在创建子类原型的时候;一次式在子类型构造函数内部。
function ParentType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Parent.prototype.getParentValue = function() {
return alert(this.name);
}
function SonType(name, age) {
ParentType.call(this, name); // 第二次调用 ParentType()
this.age = 21;
}
SonType.prototype = new ParentType(); // 第一次调用 ParentType()
SonType.prototype.sayAge = function() {
alert(this.age);
}
第一次调用的时候: SonType.prototype会得到两个属性 name和colors;都是ParentType的实例属性,只不过位于SonType的原型中。
第二次调用的时候: SonType构造函数被调用,ParentType又被调用了一次,就导致了又一次在新对象上创建了实例属性name和colors。
于是乎,这两个属性就屏蔽了原型中的两个同名属性。
有两组name和colors,一组在实例上,一组在SonType原型中。然后,天降猛男出场了寄生组合
function inheritPrototype(sonType, parenType) {
var prototype = object(ParentType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
讲真看不太明白,这里留坑以后填
function ParentType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Parent.prototype.getParentValue = function() {
return alert(this.name);
}
function SonType(name, age) {
ParentType.call(this, name);
this.age = 21;
}
// SonType.prototype = new ParentType() 用下面的替换了
inheritPrototype(SonType, ParentType);
SonType.prototype.sayAge = function() {
alert(this.age);
}
只调用了一次ParetType构造函数,避免在SubType.prototype上创建不必要的、多余的属性。 这是最理想的继承范式~