九、对象

对象是被称为属性的命名值的集合。属性可以是保存对象状态的变量,也可以是定义对象功能的函数。对象的吸引力在于它们在提供功能的同时隐藏了它们的内部工作。你需要知道的只是一个对象能为你做什么,而不是它是如何做的。

对象属性

可以使用 new 指令以下面的显式方式创建一个空对象。

var box = new Object();

使用点符号或数组符号将对象的属性赋给时,会自动创建这些属性。

box.x = 2;

box["y"] = 3;

同样,属性可以用这两种方式之一引用。

console.log(box.x);    // "2"

console.log(box["y"]); // "3"

可以使用 delete 指令从对象中删除属性。这不能用常规变量来完成,只能用对象属性来完成。

box.z = 1;    // add property

delete box.z; // delete property

要检查对象是否包含属性,可以使用 in 运算符。然后,属性名被指定为字符串。

console.log("x" in box); // "true"

console.log("z" in box); // "false"

对象方法

函数可以以属性的形式添加到对象中。这种功能被称为方法。当引用函数声明时,括号被省略。

box.getArea = myArea;

function myArea() { return this.x * this.y; }

这里使用的this关键字是对当前拥有该函数的对象的引用。如果在对象上下文之外调用函数,关键字将改为引用window对象。为了防止这种脱离上下文的调用,最好使用函数表达式内联函数。这也防止了函数名不必要地弄乱了全局名称空间。

box.getArea = function() { return this.x * this.y; };

一旦绑定到对象,就可以以熟悉的方式调用该方法。this关键字在这里指的是 box 对象,它具有前面定义的属性xy

console.log( box.getArea() ); // "6"

对象文字

创建对象的一种更简单的方法是使用对象文字,它由一组花括号分隔。创建空对象时,括号是空的。

var box = {};

使用对象文字的优点是,可以在创建对象时设置属性,方法是将它们包含在花括号中。属性的每个名称-值对由冒号分隔,每个属性依次由逗号分隔。

var box = {

x: 2,

y: 3,

getArea: function() { return this.x * this.y; }

};

如果只需要一个对象实例,那么对象文字就很有用。如果需要多个实例,可以使用函数构造函数。

构造函数

可以从构造函数中创建对象。通过允许从一组定义中创建一个对象的多个实例,它们提供了其他语言中的类所提供的功能。按照惯例,打算用作对象构造函数的函数以大写字母开头,以提醒它们的用途。

function Box(x, y) {

this.x = x;

this.y = y;

this.getArea = function() { return this.x * this.y; };

}

为了从这个构造函数中实例化一个或多个对象,用new指令调用这个函数。就像任何其他函数一样,构造函数可以接受参数,如下例所示。

var b1 = new Box(1, 2);

var b2 = new Box(3, 4);

每个对象实例都包含自己的一组属性,这些属性可以保存与其他实例不同的值。

console.log(b1.x); // "1"

console.log(b2.x); // "3"

先前定义的构造函数包括在另一个函数中声明的函数。这种嵌套函数可以访问其父函数中定义的变量,因为它形成了一个包含外部函数范围的所谓闭包。这种对象创建模式的一个优点是它提供了信息隐藏。例如,下面的示例有一个方法,它使用一个局部变量来记录该方法被调用的次数。这个特性类似于基于类的语言中的私有属性,因为局部变量只在构造函数中可见。

function Counter(x, y) {

var count = 0;

this.printCount = function() { console.log(count++); };

}

var c = new Counter();

c.printCount(); // "0";

c.printCount(); // "1";

这种对象创建模式的一个小缺点是每个实例都有自己的printCount方法,这会增加每个对象消耗的内存。避免这种情况的另一种模式是利用继承将方法添加到对象的原型中。

遗产

一个对象可以从另一个对象继承属性。这为对象重用其他对象的代码提供了一种方式。专门化的对象通常称为子对象,更一般的对象称为父对象。

在 JavaScript 中,继承是通过原型继承模型实现的。在这个模型中,每个对象都有一个内部属性,作为到另一个对象的链接,称为它的原型。考虑下面的对象。

var myObj = new Object();

这个对象从内置的Object构造函数继承属性。当请求一个对象不直接包含的属性时,JavaScript 将自动搜索继承链,直到找到请求的属性或到达链的末端。

// Add a property to myObj

myObj.x = 5;

// Call a method from Object

myObj.hasOwnProperty("x"); // true

链中的最后一个链接总是内置的Object构造函数,它的原型链接又是 null,这标志着链的结束。可以使用__proto__属性检索对对象原型链接的引用。这不要与构造函数的 prototype 属性混淆,它是函数的文字对象表示,当创建该函数的新实例时,它被分配给__proto__

myObj.__proto__ === Object.prototype; // true

myObj.__proto__.__proto__ === null;   // true

通过扩展原型链来实现继承。这可以在构造函数中完成,方法是将prototype属性设置为要继承的对象。

function Parent() { this.a = "Parent"; }

function Child() { }

Child.prototype = new Parent();

var child = new Child();

console.log(child.a); // "Parent"

类型检查

您可以通过比较对象的__proto__链接和构造函数的prototype属性来手动确认对象的类型。

child.__proto__ === Child.prototype; // true

JavaScript 还提供了instanceof操作符。该操作符在原型链中导航,如果左侧对象指向原型链中右侧的构造函数,则返回 true。

child instanceof Child;  // true

child instanceof Parent; // true

child instanceof Object; // true

对象创建

执行继承的另一种方式是通过Object.create方法。此方法提供了一种更简单的实现继承的方法,它允许一个对象直接从另一个对象继承,而无需使用额外的构造函数。

var p = new Parent();

var c1 = Object.create(p); // inherit from p

方法的第二个可选参数允许您使用特殊的表示法初始化对象的属性。

var c2 = Object.create(p, { name: { value: "Child 2" } } );

console.log(c2.name); // "Child 2"

对象的prototype属性可以用来动态地向原型添加属性。这将导致链接到该原型的所有对象都继承新的属性。

Parent.prototype.x = "new property";

console.log(c2.x); // "new property"

如本章所示,JavaScript 在创建和使用对象时提供了很大的灵活性。选择使用哪种方法往往归结为个人喜好的问题。