创建的每一个函数都有一个prototype(原型)属性,这个属性是一个对象。而类的构造函数也是函数,只不过它是通过 new 操作符调用的,才作为构造函数,所有它也具有原型属性。默认情况下,所有protoptype属性都会自动获得一个constructor(构造函数)属性,这个属性包含一个指针,指向prototype属性所在的函数(即构造函数)。原型属性的作用是为该类的所有实例提供共享的属性和方法,而构造函数中定义的属性和方法是某个实例独有的。通常,我们组合使用构造函数模式和原型模式来创建自定义类。
关于重写整个原型对象引起的问题:
let Person = function (name,age){
this.name = name;
this.age = age;
};
let p = new Person("Tim",18);
Person.prototype = { //重写原型对象
sayName: function (){
alert(this.name);
}
}
p.sayName(); //报错:sayName()不是一个函数
重写原型对象之前:
重写原型对象之后:
从图中可以看到,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针_proto_。重写之后,Person类就指向新的原型对象,而实例p还是指向原来的原型对象,原来的原型对象中没有定义sayName函数。
原型链是实现继承的主要方法,其基本思想是:将子类的原型等于父类的实例,此时作为父类的实例,它拥有_proto_指针指向父类的原型,那么子类创建的所有实例,都能继承父类原型中的属性和方法,从而实现继承。另外,所有函数的默认原型都是Object实例,因此父类的默认原型也会包含一个内部指针指向Object.prototype,这也是所有自定义类都会继承toString()、valueOf()等默认方法的根本原因。
但仅仅使用原型链来实现继承,也会面临着同样的“共享”问题:
因此,通常会借用构造函数来实现组合继承。
function SuperType(name){
this.name = name;
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name); //这里使用了call()来指定父类构造函数的调用对象,这样就可以为每个子类实例提供不同的父类属性值,解决了上述问题
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
}
let instance1 = new SubType("Tom",18);
instance1.sayName();
instance1.sayAge();
let instance2 = new SubType("Mike",16);
instance2.sayName();
instance2.sayAge();