大多数JavaScript 代码以面向对象的形式编写。无论通过创建自定义对象还是使用内置的对象,诸如文档对象模型(DOM)和浏览器对象模型(BOM)之中的对象。因此,存在很多对象成员访问。
对象成员包括属性和方法,在JavaScript 中,二者差别甚微。对象的一个命名成员可以包含任何数据类型。既然函数也是一种对象,那么对象成员除传统数据类型外,也可以包含一个函数。当一个命名成员引用了一个函数时,它被称作一个“方法”,而一个非函数类型的数据则被称作“属性”。
正如本章前面所讨论过的,对象成员比直接量或局部变量访问速度慢,在某些浏览器上比访问数组项还要慢。要理解此中的原因,首先要理解JavaScript 中对象的性质。
一、原形
JavaScript 中的对象是基于原形的。原形是其他对象的基础,定义并实现了一个新对象所必须具有的成员。这一概念完全不同于传统面向对象编程中“类”的概念,它定义了创建新对象的过程。原形对象为所有给定类型的对象实例所共享,因此所有实例共享原形对象的成员。
一个对象通过一个内部属性绑定到它的原形。Firefox,Safari,和Chrome 向开发人员开放这一属性,称作__proto__;其他浏览器不允许脚本访问这一属性。任何时候你创建一个内置类型的实例,如Object 或Array,这些实例自动拥有一个Object 作为它们的原形。
因此,对象可以有两种类型的成员:实例成员(也称作“own”成员)和原形成员。实例成员直接存在于实例自身,而原形成员则从对象原形继承。考虑下面的例子:
var book = {
title: "High Performance JavaScript",
publisher: "Yahoo! Press"
};
alert(book.toString()); //"[object Object]"
此代码中,book 对象有两个实例成员:title 和publisher。注意它并没有定义toString()接口,但是这个接口却被调用了,也没有抛出错误。toString()函数就是一个book 对象继承的原形成员。处理对象成员的过程与变量处理十分相似。book.toString()被调用时,对成员进行名为“toString”的搜索,首先从对象实例开始,如果book 没有名为toString 的成员,那么就转向搜索原形对象,在那里发现了toString()方法并执行它。通过这种方法,booke 可以访问它的原形所拥有的每个属性或方法。
你可以使用hasOwnProperty()函数确定一个对象是否具有特定名称的实例成员,(它的参数就是成员名称)。要确定对象是否具有某个名称的属性,你可以使用操作符in。例如:
var book = {
title: "High Performance JavaScript",
publisher: "Yahoo! Press"
};
alert(book.hasOwnProperty("title")); //true
alert(book.hasOwnProperty("toString")); //false
alert("title" in book); //true
alert("toString" in book); //true
此代码中,hasOwnProperty()传入“title”时返回true,因为title 是一个实例成员。传入“toString”时返回false,因为toString 不在实例之中。如果使用in 操作符检测这两个属性,那么返回都是true,因为它既搜索实例又搜索原形。
二、原形链
对象的原形决定了一个实例的类型。默认情况下,所有对象都是Object 的实例,并继承了所有基本方法,如toString()。你可以用“构造器”创建另外一种类型的原形。例如:
function Book(title, publisher){
this.title = title;
this.publisher = publisher;
}
Book.prototype.sayTitle = function(){
alert(this.title);
};
var book1 = new Book("High Performance JavaScript", "Yahoo! Press");
var book2 = new Book("JavaScript: The Good Parts", "Yahoo! Press");
alert(book1 instanceof Book); //true
alert(book1 instanceof Object); //true
book1.sayTitle(); //"High Performance JavaScript"
alert(book1.toString()); //"[object Object]"
Book 构造器用于创建一个新的Book 实例。book1 的原形(__proto__)是Book.prototype,Book.prototype的原形是Object。这就创建了一个原形链,book1 和book2 继承了它们的成员。注意,两个Book 实例共享同一个原形链。每个实例拥有自己的title 和publisher 属性,但其他成员均继承自原形。当book1.toString()被调用时,搜索工作必须深入原形链才能找到对象成员“toString”。正如你所怀疑的那样,深入原形链越深,搜索的速度就会越慢。图2-11 显示出成员在原形链中所处的深度与访问时间的关系。
虽然使用优化JavaScript 引擎的新式浏览器在此任务中表现良好,但是老的浏览器,特别是InternetExplorer 和Firefox 3.5,每深入原形链一层都会增加性能损失。记住,搜索实例成员的过程比访问直接量或者局部变量负担更重,所以增加遍历原形链的开销正好放大了这种效果。