JavaScript中的对象(二)——原型对象

时间:2021-01-17 14:48:57

      原型对象是JS里面很特殊的一个设计,我们会用这个东西来实现对象的公共属性和方法,另外,对象的继承也是依赖这个原型对象的。原型对象的英文名是prototype,简单来说,有点类似模具,每一个对象都是按照模具生成的,这样对象自然会有很多共同的属性。

      我们在第一篇里面讲到了用构造函数来创建对象,这是最常见的创建对象方法了,但是仅仅那样书写也存在问题,我们来看代码:

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;

this.sayName = function () {
alert(this.name);
};
}

var person1 = new Person("P1", 40, "Engineer1");
var person2 = new Person("P2", 25, "Engineer2");

alert(person1.sayName == person2.sayName);

      这段代码先定义了Person的构造函数,这个构造函数除了一些属性之外,还定义了一个sayName的方法。然后用构造函数生成了两个对象person1和person2,最后判定两个实例中的sayName方法是否同一个。

      这段代码的运行结果是false,也就是说,两个对象中的sayName方法不是同一个,而是在每个对象中各自有一个。这个就是一种浪费了,同样的方法在内存里面出现多次,而且每创建一个对象就多一次,实实在在是一个大缺陷。


      原型

      每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

      很明确了,原型的用途是“所有实例共享属性和方法”,而构造函数也是函数,那么构造函数的原型就可以用来存储这一类特定对象的“共享属性和方法”了。回到上面的代码,sayName作为方法,显然不应该定义在构造函数里面,而是构造函数的原型里面! 

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}

Person.prototype.sayName = function () {
alert(this.name);
};

var person1 = new Person("P1", 40, "Engineer1");
var person2 = new Person("P2", 25, "Engineer2");

alert(person1.sayName == person2.sayName);

      修改后的代码就把sayName方法定义到了prototype里面,这样,person1.sayName和person2.sayName就完全是一回事了。这里我们要指出,我们定义的是构造函数的prototye(Person.prototype),但是用构造函数生成的实例person1,person2同样也可以访问到这个原型对象,这说明实例也有一个prototype指针,这个指针和构造函数一样,都指向构造函数的原型对象。

      原型对象也是对象,他有一个默认的constructor属性——指向构造函数,另外还有从Object继承的方法。

function Person() {
}


Person.prototype.name = "name";
Person.prototype.age = 20;
Person.prototype.job = "none";
Person.prototype.sayName = <span style="white-space:pre"></span>function () {
<span style="white-space:pre"></span>alert(this.name);
<span style="white-space:pre"></span>};


var person1 = new Person();
person1.sayName();


var person2 = new Person();
person2.name = "P2";
person2.sayName();

      这段代码里面,把所有的属性和方法都塞到了原型对象里面,构造函数成为了一个全空的函数。然后生成两个对象,在给person2对象的name属性赋值P2。

      这段的运行结果是name和P2。这说明如果没有给新创建的对象的属性赋值,JavaScript是用原型对象的属性来显示的;如果定义了对象的属性,那么就会用这个定义的属性。

      这个属性的查找是一个链式的,具体来说,在用person1.name时,解析器先看“实例person1有name属性么?”,这里当然没有;然后解析器去看“实例person1的原型有name属性么?”,这里就能找到了,于是person1.name就返回原型里面的name属性;同样的顺序在person2.name时也运行一遍,不过person2有name属性,于是返回了对象person2的那么属性。

     

      好了,我们可以看到对象里面的属性可以覆盖原型里面的属性,这样,原型的属性相当于一个默认值——仅当对象没有这个属性时可以访问到。  

      总结一下,原型的用过应该是定义用构造函数生成的所有实例都需要的公共属性和方法,如果创建的对象有和原型同名的属性,则变量的属性优先级更高,可以覆盖掉原型的属性。

      我们看到,原型确实类似于模具,当用构造函数生成对象时,站在使用者的角度来看,这些对象都有相同的方法以及一些属性,好像是同一个模具生成的产品,另外一些各自不同的属性,则是对产品进行了差异化的处理。