原型链继承
子类的所有实例都共享着原型上的所有属性和方法。通过子类实例,可以访问原型上的属性,但是,不能重写原型上的属性。
//定义一个学生类
function Student(stuID, schoolName) {
this.stuID = stuID;
this.schoolName = schoolName;
}
//所有学生都有这样一个特征
Student.prototype.characteristic = '年轻有朝气';
var stu1 = new Student(1001,'第一小学');
console.log(stu1.stuID); //1001
console.log(stu1.characteristic); //'年轻有朝气'
//重写characteristic
stu1.characteristic = '活泼可爱';
console.log(stu1.characteristic); //'活泼可爱'
var stu2 = new Student(1002,'第一小学');
console.log(stu2.characteristic); //'年轻有朝气'
console.log(Student.prototype); //{characteristic: "年轻有朝气"}
上面这段代码表明:
通过stu1.characteristic = '活泼可爱';并没有改变原型上的属性值。
当实例中,存在和原型中同名的属性时,会自动屏蔽原型上的同名属性。stu1.characteristic = = '活泼可爱' 实际上是给实例stu1添加了一个本地属性 characteristic,所以当我们再次访问stu1.characteristic 时,访问的是实例的本地属性,而不是原型上的characteristic属性(它因和本地属性名同名已经被屏蔽了)。
原型上的 characteristic 值是一个基本类型的值,如果是一个引用类型呢?这其中又会有一堆小九九。
其实原型上任何类型的值,都不会被实例所重写。在实例上设置与原型上同名属性的值,只会在实例上创建一个同名的本地属性。但是,原型上引用类型的值可以通过实例进行修改,而且所有的实例访问到的该引用类型的值也会随之改变。(不是很明白,既然引用类型的值都能被修改了,那么为什么还说不会被实例重写??难道修改!= 重写)
//定义一个学生类
function Student(stuID, schoolName) {
this.stuID = stuID;
this.schoolName = schoolName;
}
//所有学生都有这样一个特征
Student.prototype.characteristic = '年轻有朝气';
Student.prototype.examItems = ['语文','数学','英语']; //考试项目
var stu1 = new Student(1001, '第一小学');
console.log(stu1.examItems); //['语文','数学','英语']
//修改examItems
stu1.examItems.push('科学');
console.log(stu1.examItems); //['语文','数学','英语','科学']
var stu2 = new Student(1002, '第一小学');
console.log(stu2.examItems); //['语文','数学','英语','科学']
原型上任何类型的属性值都不会通过实例被重写,但是引用类型的属性值会受到实例的影响而修改。
构造函数继承
在子类的构造函数中,通过 apply( ) 或 call( )的形式,调用父类构造函数,以实现继承。
//定义一个超类/父类: 人
function Person (name, age) {
//人都有姓名,年龄,会吃饭,会睡觉
//传入出生年份 year,自动计算年龄
this.name = name;
this.age = age;
this.eat = function () {
alert('吃饭');
}
this.sleep = function () {
alert('睡觉');
}
}
//定义一个子类: 学生
//学生Student也是人,自然要继承超类 Person 的所有属性和方法
//学生都应当有姓名、年龄、会吃饭、会睡觉
//当然学生也有自己的一些属性:学号,学校名称等,和方法,比如都要去做一件事:写作业
function Student (stuID, schoolName, name, age) {
this.stuID = stuID;
this.schoolName = schoolName;
//用call调用 Person,以实现继承
Person.call(this, name, age);
}
Student.prototype.doHomework = function () {
alert('做作业');
}
//实例化一个学生
var stu1 = new Student(1001, '第一小学', '王宝宝',20);
console.log(stu1.stuID); //1001
console.log(stu1.schoolName); //'第一小学'
console.log(stu1.name); //'王宝宝'
console.log(stu1.age); //20
stu1.eat(); //'吃饭'
stu1.sleep(); //'睡觉'
stu1.doHomework(); //'做作业'
在子类构造函数中,我们通过 call 的方式调用了父类构造函数 Person实现了继承。别忘了,函数只不过是一段可以在特定作用域执行代码的特殊对象,我们可以通过 call 方法指定函数的作用域。
在 stu1 = new Student() 构造函数时,Student 内部 this 的值指向的是 stu1, 所以 this.stuID =stu1.stuID, 所以 Person.call(this, name, age) 就相当于Person.call(stu1, '王宝宝', 20),就相当于 stu1.Person('王宝宝',20)。最后,stu1 去调用 Person 方法时,Person 内部的 this 指向就指向了 stu1。那么Person 内部this 上的所有属性和方法,都被拷贝到了stu1上。
总之,在子类函数中,通过call() 方法调用父类函数后,子类实例 stu1, 可以访问到 Student 构造函数和 Person 构造函数里的所有属性和方法。这样就实现了子类向父类的继承。
缺点
这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,比如 eat()。这样做,有几个缺点:
1. 每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。
2. 方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。
所以,其实单独使用原型链继承或者借用构造函数继承都有自己很大的缺点,最好的办法是,将两者结合一起使用,发挥各自的优势。
例如:
function OSTAccountDC() {
OSTBaseDC.call(this);
}
OSTAccountDC.prototype = new OSTBaseDC(); //通过OSTBaseDC创建一个实例,避免改变父类构造函数的属性,而只是在本地创建一个属性。
OSTAccountDC.prototype.constructor = OSTAccountDC; //使this指向自己OSTAccountDC,而不是指向构造函数OSTBaseDC
OSTAccountDC.prototype.accountRequest = function(callback){
// do something
}