一、理解原型对象
当创建一个新函数时,系统会根据一组特定的规则为函数创建一个prototype属性,该属性会指向一个名为原型对象的对象,在默认情况下,该对象会自动生成一个构造函数(constructor),该构造函数是一个指向函数的指针。而在原型对象中,除了有这个构造函数,我们还可以添加其他的属性和方法。
通俗来讲就是,当我们新建一个函数A时,函数A内部会有一个属性,该属性指向一个对象(名字叫原型对象),而这个对象里面默认有一个构造函数,这个构造函数指向我们最初新建的函数A。然后,我们还可以在原型对象中添加属性和方法。
下面我们来看构造函数、构造函数的原型属性和实例之间的关系:
构造函数内部会有一个prototype属性,该属性指向构造函数的原型对象,当通过构造函数创建实例时,该实例内部将包含一个指针,指向构造函数的原型属性。该关系如图所示
来看下面的例子:
//①默认情况下,构造函数是空的
function Person(){//构造函数首字母大写
}
//②添加属性和方法
Person.prototype.name="dp";
Person.prototype.doSomething=function(){
alert(this.name);
};
//③定义好构造函数和其他属性方法之后,就可以创建实例了
var person1=new Person();
var person2=new Person();
这两个对象都是由A创建的,他们都可以调用A的原型中的属性和方法
alert(person1.name);//dp
alert(person2.name);//dp
当然也可以将其重写
person1="god";
alert(person1.name);//god
重写后的属性也可以删除
delete person1.name;
alert(person1.name);//dp
注意原型的动态性,如果你将①和②调换位置,如下:
function Person(){ }
var person3=new Person();
Person.prototype.name=”dp”;
然后你再执行alert(person3.name)会报错,因为new完person3之后,相当于person3的构造函数和原型属性都是默认值(空),而接下来的Person.prototype.name=”dp”相当于重写了其原型对象,重写原型对象的同时,割裂了原型对象和实例的关系,既然原型对象和实例没有关系了,那么person3.name就没有意义,所以会报错。
二、理解原型链
让我们回顾一下构造函数、原型和实例的关系:构造函数内部会有原型对象,该原型对象中包含一个指向构造函数的指针,而实例包含一个指向原型对象的指针。
那么,假如原型对象是另一个类型的实例呢?也就是说,原型对象包含一个内部指针,指向另一个类型的原型对象,相应的,该原型对象也包含一个指向其构造函数的指针,如图所示。如果将这种关系延续下去,就形成了原型链。
实现原型链的基本模式如下:
<script type="text/javascript">
function Animal(name) {
this.name = name;//设置对象属性
}
Animal.prototype.getName = function() {
alert("It is a "+this.name);
}
function Dog(name){
this.name=name;
}
Dog.prototype=new Animal();//继承了Animal
Dog.prototype.getName=function(){
alert("It is a "+this.name);
};
var tom = new Dog("dog");//创建Dog对象
alert(tom.getName());//It is a dog
</script>
三、继承
许多语言都支持两种继承方式:接口继承和实现继承。在ECMAscript中不支持接口继承,只支持实现继承,而实现继承主要是依靠原型链来实现。根据js语言的本身的特性,js实现继承有五种方式,详见博客