定义类或对象
构造函数方式
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中使用最广泛、认同度最高的一种创建自定义对象的方法。另外,还有动态原型方式、寄生构造函数函数模式、稳妥构造函数模式等方式。