JS中的面向对象(二)

上个部分介绍了对象的产生,这一部分介绍对象的继承。

因为没有类的概念,继承的概念变得有点晦涩。在JavaScript中继承的问题要分两种情况讨论,从构造函数中继承和从对象中继承。

从对象中继承

这种继承的想法是从已有的对象中继承属性和方法。

原型式继承

Douglas Crockford发明了这种方法。

1
2
3
4
5
function object(o){
function F(){}
F.prototype = o;
return new F();
}

这个object函数接收一个对象,根据这个对象生成子对象返回。在object()函数内部首先创造一个构造函数,然后将传入的对象作为这个构造函数的原型对象,最后利用这个构造函数生成一个新对象返回。如果还记得原型模式生成对象的方法,就会发现这里的继承实际上就是将构造函数的原型变为一个已经存在的对象。和原型模式一样,原型继承方法生成的新对象共享了原型对象中的所有属性和方法。克服这个缺点的方法就是你根据自己的需要去覆盖原型中的属性和方法。

和这个思路类似的另外一种思路是寄生式继承。寄生式继承的思路是利用一个构造函数生成一个对象,再继承这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};

return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

利用构造函数继承

如果已经有了一个构造函数,企图利用这个构造函数进行继承。它的基本思想是在子类的构造函数内部调用父类的构造函数。这样在调用子类构造函产生对象的时候调用了父类的构造函数,也就继承了父类的对象的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
function.SuperType(){
this.color=["red","blue","green"];
}
function.SubType(){
superType.call(this);//如果父类构造函数需要传参,那么在调用父类构造函数时传入
//在这里加入一些属性
}

var instance = new SubType();
instance.colors.push("black");
alert(instance.colors); //"red,blue,green"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"

看上面的例子会觉得根本没有必要使用继承,直接用父类构造函数生成对象,再给对象添加属性不也是一样吗?这里加了一个中间的子类,不是多此一举吗?增加了子类,可以在子类中添加一些属性和方法。调用子类构造函数生成的对象不仅继承了父类的属性和方法还拥有子类的属性。

和构造函数模式一样,这种继承的问题就是不能够在生成的子对象中共享一些属性和方法。解决方案也就是用prototype去继承需要共享的属性和方法。

组合继承

组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。下面来看一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
//prototype对象中的constructor属性是一个指向"自己"这个构造函数的指针
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

这个方法首先是生成了一个父类对象,将子类的原型指针指向这个对象。再利用子类构造函数生成子类对象。可以简化这一过程将子类的原型指针直接指向父类的原型对象,而不是父类对象。

1
2
3
4
5
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}

为什么不直接像下面这样做呢?

1
2
3
function inheritPrototype(subType, superType){
subType.prototype = superType.prototype;
}

如果这么做的话对子类原型的修改在父类原型中也会反映出来。这并不是我们所希望发生的事情。

当然也可以这么做

1
2
3
4
5
6
function inheritPrototype(subType, superType){
var F =function(){};
F.prototype = superType.prototype;
F.prototype.constructo = subType;
subType.prototype = new F();
}

随后,只要调用这个函数,子类的prototype就会正确的生成并且不干扰父类的prototype。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function.SuperType(name){
this.name=name;
this.colors=["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function.SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
}
instance1 = new SubType("Jordan",52);

总结

继承实质上是将父类的东西拿为己用,那么还有一种很直接的思路就是将父类的东西都拷贝过来,子类不就继承了父类了吗?将子类的原型对象拷贝为父类的原型对象就完成了这一过程。

但是由于JavaScript是一种使用引用类型的语言,所有的Object是通过引用传递的,而函数是通过值传递的。所以执行拷贝会使得子类的原型对象的修改引起父类原型对象的修改。

怎么解决这个问题呢。使用深拷贝!前面的inheritPrototype()函数就是一种深拷贝的方法,因为new操作符执行的是深拷贝。

当然,如果不嫌麻烦,也可以自己写一个函数执行深拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
function deepCopy(superType, subType) {
var subType = subType || {};
for (var i in superType) {
if (typeof superType[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], subType[i]);
} else {
subType[i] = superType[i];
 }
}
return superType;
}