关键是对后一句Student原型属性赋值为Person类构造的对象,这里笔者解释一下父类的属性和方法是如何copy到子类上的。Js对象在读取某个对象属性的时候,总是先查看自身域的属性列表,如果有返回否则去读取prototype域(每个对象共享构造对象的类的prototype域所有属性和方法),如果找到返回,由于prototype可以指向别的对象,所以Js解释器会递归的去查找prototype域指向对象的prototype域,直到prototype为本身,查找变成了一种循环,停止,此时还没找到成undefined了。

  这样看来,后一句发生的效果是将父类所有属性和方法连接到子类的prototype域上,这样子类继承了父类所有的属性和方法,包括name、sayGoodbye和sayHello。这里与其把后一句看成一种赋值,不如理解成一种指向关系更好一点。这种原型继承的缺陷也相当明显,是继承时父类的构造函数时不能带参数,因为对子类prototype域的修改是在声明子类对象之后才能进行,用子类构造函数的参数去初始化父类属性是无法实现的,如下所示:

function Person(name){
this.name = name;
}
function Student(name,id){
this.id = id;
}
Student.prototype = new Person(this.name);

  两种继承方式已经讲完了,如果我们理解了两种方式下子类如何把父类的属性和方法“抓取”下来,可以自由组合各自的利弊,来实现真正合理的Js继承。下面是个人总结的一种综合方式:

function Person(name){
this.name = name;
}
Person.prototype.sayHello = function(){alert(this.name+“say Hello!”);};
function Student(name,id){
Person.call(this,name);
this.id = id;
}
Student.prototype = new Person();
Student.prototype.show = function(){
alert(“Name is:”+ this.name+” and Id is:”+this.id);
}

  总结是利用对象冒充机制的call方法把父类的属性给抓取下来,而成员方法尽量写进被所有对象实例共享的prototype域中,以防止方法副本重复创建。然后子类继承父类prototype域来抓取下来所有的方法。如想彻底理清这些调用链的关系,推荐大家多关注Js中prototype的constructor和对象的constructor属性,这里不多说了。