定义一个对象的最简单的方法
var car = { color:"red", drive:function() { alert(this.color + " car moved"); } }
这个方法不是在非常有用,因为它创建了一个单独的对象,而这个对象和任何常见的数据结构没有任何联系,为了创建第二个car实例,必须重新定义它的结构.
通过构造函数创建一个新对象
function Car() { this.color = "red"; this.drive = function(){ alert(this.color + " car moved"); } } var car = new Car(); car.drive();
JavaScript中的每个函数都有一个称为prototype的属性.如果某个函数被用作构造函数,则这个属性会被自动通过new调用创建对象的原型
function Car() { this.color = "red"; } Car.prototype.drive = function(){ alert(this.color + " car moved"); } var car = new Car(); car.drive();
对prototype属性所做的任何更改能够应用于通过new Car()构造的每一个对象,不管它是在更改之前还是更改后创建.为Car.prototype 添加新函数.对于共享相同原型的每一个对象,该函数都可以立即使用,无论它是在更改之前或是之后构造,具体如下:
// 更新原型 function Car(color) { this.color = color; } Car.prototype.drive = function(){ alert(this.color + " car is driving"); } var car1 = new Car("red"); alert(car1.__proto__ === Car.prototype); // true car1.drive(); // 为原型添加新的函数 Car.prototype.stop = function() { alert(this.color + " car has stopped"); } var car2 = new Car("blue"); // 共享同一对象原型的对象 alert(Object.getPrototypeOf(car1) === Object.getPrototypeOf(car2)); // true // 这两个对象现在都能访问这个新的方法 car2.stop(); car1.stop();
面向对象的很重要一个特性就是继承了,这里只要介绍通过原型链继承
// 继承的基本设置 function Car(color) { this.color = color; } Car.prototype.drive = function(){ alert(this.color + " car is driving"); } Car.prototype.turn = function(direction) { alert(this.color + " car turns " + direction); } Car.prototype.stop = function() { alert(this.color + " car has stopped"); } // 消防车 function FirTruck() {} FirTruck.prototype.turnCannon = function(direction) { alert("Cannon moves to the " + direction); } var truck = new FirTruck(); // 因为Car.prototype 并不在truck对象的原型链中 // 因此方法move()并不可用 truck.move(); truck.turnCannon("right");
JavaScript中有一个很特殊的Object.create(proto, properties)方法, 注:IE8及以下不支持
它可以用来创建一个新的空白对象并将其原型设置为proto
function Car(color) { this.color = color; } Car.prototype.move = function(){ alert(this.color + " car is driving"); } Car.prototype.turn = function(direction) { alert(this.color + " car turns " + direction); } Car.prototype.stop = function() { alert(this.color + " car has stopped"); } if(!Object.create){ // 兼容性处理 FireTruck.prototype = Car.prototype; }else{ FireTruck.prototype = Object.create(Car.prototype); } function FireTruck() {} // 检查,以防万一 //alert(Object.getPrototypeOf(FireTruck.prototype) === Car.prototype); // 如果为true // 则Car.prototype被添加到链中 FireTruck.prototype.turnCannon = function(direction){ alert("Cannon moves to the " + direction); } var truck = new FireTruck(); // 现在可以工作了,因为Car.prototype已经在truck对象的原型链中 truck.move(); truck.turnCannon("right");
代码可以运行,但其输出还是存在一些问题
true
undefined car is driving
Cannon moves to the right
小汽车的color现在可以通过Car的构造函数进行设置.构造函数本身没有执行,所以其color显示为undefined.这个问题很好解决,修改FireTruck构造函数,在其中添加对Car构造函数的调用,这样Car就可以初始化它自己的对象变量
function FireTruck() { Car.call(this, "red"); }
再次运行这段代码,欣赏一下正确的输出
即使该代码能够成功运行,但仍有一个小问题需要解决.当这个新函数创建时,它的prototype属性并不为空.它有一个称为constructor的属性将引用函数本身,当调用
FireTruck.prototype = Object.create(Car.prototype);
时,该属性丢失,因为这个新建的对象并不具有自己的属性.可以将丢失的属性作为第二个参数传递给create
FireTruck.prototype = Object.create(Car.prototype, { constructor: { value: FireTruck, // 和FireTruck.prototype.constructor一样 enumerable: false, writable: true, configurable: true } });
这里的constructor并不是通常的属性.当对象的键值进行循环时,它不会出现,但可以通过名称直接访问.通过给create()的第二个参数传递属性"描述符"可以取得相同的效果
描述符具有如下所示的几个字段
value: 初始值
enumerable: 如果属性显示在对象属性的列表中, 在像for...in 这样的循环或Object.keys中
writable: 如果属性可以被指派一个不同的值
configurable: 如果描述符定义的规则可以被修改或其属性可以删除,则为true
这样就可以模仿真实的 constructor 的行为
最终版本的的继承示例
function extend(subConstructor, superConstructor) { if(!Object.create){ subConstructor.prototype = superConstructor.prototype; subConstructor.constructor = { value: FireTruck, // 和FireTruck.prototype.constructor一样 enumerable: false, writable: true, configurable: true } }else{ subConstructor.prototype = Object.create(superConstructor.prototype, { constructor: { value: FireTruck, // 和FireTruck.prototype.constructor一样 enumerable: false, writable: true, configurable: true } }); } } function Car(color) { this.color = color; } Car.prototype.move = function(){ alert(this.color + " car is driving"); } Car.prototype.turn = function(direction) { alert(this.color + " car turns " + direction); } Car.prototype.stop = function() { alert(this.color + " car has stopped"); } function FireTruck() { Car.call(this, "red"); } extend(FireTruck, Car); FireTruck.prototype.turnCannon = function(direction){ alert("Cannon moves to the " + direction); } var truck = new FireTruck(); truck.move(); truck.turnCannon("right");