定义类或对象

构造函数方式

1
2
3
4
5
6
7
8
9
10
11
function Car(sColor,iDoors,iMpg) {
    this.color = sColor;
    this.doors = iDoors;
    this.mpg = iMpg;
    this.showColor = function() {
      alert(this.color);
    };
}
 
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);

前面的例子中,每次new一个新对象,都要创建新函数 showColor(),意味着每个对象都有自己的 showColor() 版本,导致重复创建函数对象的问题。

原型方式

每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象时该函数的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Car() {} //用空构造函数来设置类名
 
//所有的属性和方法都被直接赋予 prototype 属性
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
    alert(this.color);
};
 
var oCar1 = new Car();
var oCar2 = new Car();

通过原型方法创建的对象实例 Car1 和 Car2 的 prototype 都指向同一个原型对象,这个对象上保存了相应的属性和方法,即解决了重复创建函数对象的问题。

但单纯运用原型方式创建对象也存在问题:首先,这个构造函数没有参数。使用原型方式,不能通过给构造函数传递参数来初始化属性的值,因为 Car1 和 Car2 的 color 属性都等于 "blue",doors 属性都等于 4,mpg 属性都等于 25。这意味着必须在对象创建后才能改变属性的默认值;另外将属性定义在 prototype 上,若Car1改变了color的值,oCar2.color也会改变

混合的构造函数/原型方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
}
 
Car.prototype.constructor = Car;
 
Car.prototype.showColor = function() {
  alert(this.color);
};
 
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
 
oCar1.drivers.push("Bill");
 
alert(oCar1.drivers); //输出 "Mike,John,Bill"
alert(oCar2.drivers); //输出 "Mike,John"

所有的非函数属性都在构造函数中创建,意味着又能够用构造函数的参数赋予属性默认值了。而由所有实例共享的属性 constructor 和方法 showColor() 则是在原型中定义的,所以没有内存浪费。此外,给 oCar1 的 drivers 数组添加 "Bill" 值,不会影响到 oCar2 的数组,因为它们分别引用不同的数组,所以输出这些数组的值时,oCar1.drivers 显示的是 "Mike,John,Bill",而 oCar2.drivers 显示的是 "Mike,John"。

这种混合的构造函数/原型方式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义对象的方法。另外,还有动态原型方式、寄生构造函数函数模式、稳妥构造函数模式等方式。

上一节:对象的概念

下一节:数组的概念