TDD测试驱动的javascript开发(3) ------ javascript的继承
|
说起面向对象,人们就会想到继承,常见的继承分为2种:接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。 由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承。 1. 原型链 1.1 原型链将作为实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 构造函数---原型---实例 之间的关系: 每一个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。 function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); //通过原型链实现继承
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var subInstance = new SubType();
var superInstance = new SuperType();
TestCase("test extends",{
"test superInstance property should be true" : function() {
assertEquals(true,superInstance.property);
},"test superInstance getSuperValue() should be return true" : function() {
assertEquals(true,superInstance.getSuperValue());
},"test subInstance property should be false" : function() {
assertEquals(false,subInstance.subproperty);
},"test subInstance could visit super method " : function() {
assertEquals(true,subInstance.getSuperValue()); //SubType继承SuperType,并调用父类的方法
}
});
注:要区分开父类和子类的属性名称,否则子类的属性将会覆盖父类的同名属性值:看如下代码:
function SubType() {
this.property = false;
}
SubType.prototype.getSubValue = function() {
return this.property;
};
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
SubType.prototype = new SuperType(); //通过原型链实现继承
var subInstance = new SubType();
var superInstance = new SuperType();
TestCase("test extends",superInstance.property); //父类的property值为true
},superInstance.getSuperValue()); //superInstance调用方法
},subInstance.property); //子类的property属值为false
},"test subInstance could visit super method " : function() {
assertEquals(false,并调用父类的方法,可以属性被覆盖了,返回false
}
});续:当然,如果我们不要求对属性值进行初始化的时候,就不必考虑这个问题,我们会采用上一章讲的构造函数模式+原型模式来创建类和实现继承关系。
当以读取模式访问一个实例属性时,首先会在实例中搜索该属性,如果没有找到该属性,则继续在实例的原型中寻找。在通过原型链实现继承的情况下,会继续沿着原型链继续向上 subExtends.getSuperValue()首先在实例中查找,然后在SubType.prototype,最后在SuperType.prototype中找到。 补充: 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的原因。 1.2 确定原型和实例的关系 方法一:使用instanceof 操作符 ---- 只要该实例是原型链中出现过的构造函数,结果就会返回true function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); //通过原型链实现继承
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var subInstance = new SubType();
TestCase("test extends",{
"test subInstance should instanceof Object" : function() {
assertInstanceOf(Object,subInstance);
},"test subInstance should instanceof SuperType " : function() {
assertInstanceOf(SuperType,"test subInstance should instanceof SubType " : function() {
assertInstanceOf(SubType,subInstance);
}
});
方法二:使用isPrototypeOf()方法 ---- 只要原型链中出现过该原型,都可以说是该原型链所派生的实例的原型,结果会返回true function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); //通过原型链实现继承
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var subInstance = new SubType();
TestCase("test extends",{
"test subInstance isPrototypeOf Object" : function() {
assertEquals(true,Object.prototype.isPrototypeOf(subInstance));
},"test subInstance isPrototypeOf SuperType " : function() {
assertEquals(true,SuperType.prototype.isPrototypeOf(subInstance));
},"test subInstance isPrototypeOf SubType " : function() {
assertEquals(true,SubType.prototype.isPrototypeOf(subInstance));
}
});
注:在实践中,我们很少会单独的使用原型链,因为它存在两个问题: 一、引用类型值的原型:包含引用类型值的原型属性会被所有实例共享,这也正是为什么要在构造函数中定义属性,而不在原型中定义属性的原因 二、创建子类型的实例时,不能向超类型的构造函数中传递参数。 1.3 借用构造函数(伪造对象 ---- 经典继承) 原理: 在子类型构造函数的内部调用超类型的构造函数 function SuperType(name) {
this.name = name;
this.friends = ['tong','feng'];
}
function SubType(name,age) {
SuperType.call(this,name);
}
var subInstance = new SubType("tongtong",26);
subInstance.friends.push('ty');
var subInstance2 = new SubType("fengfeng",27);
TestCase("test constructor extends",{
"test subInstance friends property" : function() {
assertEquals("ty",subInstance.friends[2]); //将ty push到数组
},"test subInstance2 friends length" : function() {
assertEquals(2,subInstance2.friends.length); //subInstance2 friends属性没有改变
}
});
1.4 组合继承(经典伪继承) ----- 推荐模式 将原型链和借用构造函数的技术组合到一块,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承 function SuperType(name) {
this.name = name;
this.friends = ['tong','feng'];
}
SuperType.prototype.sayName = function() {
return this.name;
};
function SubType(name,age) {
//继承属性
SuperType.call(this,name); //调用构造函数
this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); //调用构造函数
SubType.prototype.sayAge = function() {
return this.age;
};
var subInstance = new SubType("tongtong",27);
TestCase("test extends",{
"test subInstance name property" : function() {
assertEquals("tongtong",subInstance.sayName());
},"test subInstance2 name property" : function() {
assertEquals("fengfeng",subInstance2.sayName());
},"test subInstance friends property" : function() {
assertEquals("ty",subInstance2.friends.length); //subInstance2 friends属性没有改变
}
});
这种继承的缺点:将会2次调用构造函数,性能一般,解决办法:参见1.7寄生组合式继承 1.5 原型式继承 这种方法没有严格意义上的构造函数,思想是借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。 它要求你必须有一个对象可以作为另一个对象的基础。如果有这么一个对象,可以把它传递给object()函数,然后该函数就会返回一个新对象。 function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
var person = {
name : "tongtong",friends : [ "feng","tong" ]
};
var another = object(person);
another.name = "newtong";
another.friends.push('lan');
var other = object(person);
TestCase("test prototype extends",{
"test another is extends person name property" : function() {
assertEquals("newtong",another.name);
},"test person firends property length is 3" : function() {
assertEquals(3,person.friends.length);
},"test other firends property length is 3" : function() {
assertEquals(3,other.friends.length);
}
});
person 作为另一个对象的基础,我们把它传入到object()中,然后该函数就会返回一个新对象,这个对象将person做为原型。 ECMAScript通过Object.create()方法规范了原型式继承,在传入一个参数的时候,Object.create()与object()方法的行为相同。 var person = {
name : "tongtong","tong" ]
};
var another = Object.create(person);
another.name = "lisa";
another.friends.push("lan");
TestCase("test prototype extends",{
"test another is extends person name property" : function() {
assertEquals("lisa",person.friends.length);
}
});
(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
