概述
第10章 使用对象
ECMA-262定义对象是“属性的无序集合,每个属性存放一个原始值、对象或者函数”。严格地讲,这就相当于说明了对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象想象成散列表:无非就是一组名值对,其中值可以是数据或函数。本章主要讲解JavaScript对象的基本概念和知识,在后面章节中再深入分析JavaScript类型和空间的高级用法。
【学习重点】
▲ 理解JavaScript对象及其分类
▲ 正确操作对象
▲ 操作对象属性
▲ 了解各种类型对象
▲ 使用对象的方法
▲ 熟练使用原生对象
10.1 认识对象
面向对象的语言都有一个标志:都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
对象是JavaScript的基本数据类型。对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。对象也可看作是属性的无序集合,每个属性都是一个名值对。属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。这种基本数据结构还有很多种叫法,如散列(hash)、散列表(hashtable)、字典(dictionary)、关联数组(associative array)等。
对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JavaScript对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种原型继承是JavaScript的核心特征。
JavaScript对象是动态的,可以新增属性,也可以删除属性,但它们常用来模拟静态对象,以及静态类型语言中的结构体(struct)。有时它们也用作字符串的集合,忽略名值对中的值。
除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。
尽管字符串、数字和布尔值不是对象,但它们的行为和不可变对象非常相似。
JavaScript的对象是可变的,我们通过引用而非值来操作对象。例如,如果变量x是指向一个对象的引用,那么执行代码var y=x;,变量y也是指向同一个对象的引用,而非这个对象的副本,通过变量y修改这个对象亦会对变量x造成影响。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate)它的属性,下面各节会讲解这些基本操作。后续的章节中会讲解高级用法,其中相当一部分内容将会在第23章ECMAScript 5节中讲解。
10.2 对象分类
在JavaScript中,用户可以创建并使用的对象包括两大类:原生对象和宿主对象。原生对象属于JavaScript语言的一部分,而宿主对象由具体运行环境定义,如客户端浏览器等。
10.2.1 原生对象
JavaScript内置了很多本地对象,这些本地对象都是抽象的类型(构造函数),本地对象列表参见表10-1所示。由于用户自定义对象都是本地对象的实例,所以它们属于本地对象范畴。
表10-1 JavaScript预定义本地对象
原生对象具有松散和动态的命名属性。对象的命名属性用于保存值,该值可以是指向另一个对象的引用。从这个意义上分析,函数也是对象,构造基本数据类型,如String、Number、Boolean等。
10.2.2 宿主对象
宿主对象不是JavaScript定义的对象(非本地对象),是由JavaScript寄宿的环境定义。例如,Window、Document、History、Location、Screen、Navigator、Body、Form、Event等都是宿主对象,它们由客户端浏览器定义,与JavaScript语言本身没有直接关系。不过JavaScript能够访问它们,可以读写宿主对象包含的属性,也可以调用它们的方法。
10.3 对象基本操作
对象通过名/值对的形式保存数据,数据在对象内是无序排列,使用点语法可以存取对象属性,对象属性的值可以是任意类型的数据,可以是值类型数据,也可以是对象、函数等。
除了包含属性之外,每个对象还拥有3个相关的对象特性(object attribute)。
☑ 对象的原型(prototype):指向另外一个对象,本对象的属性继承自它的原型对象。
☑ 对象的类(class):是一个标识对象类型的字符串。
☑ 对象的扩展标记(extensible flag):指明了(在ECMAScript 5中)是否可以向该对象添加新属性。
第13章会有关于原型和属性继承的讲解,将进一步详细讲解这3个特性。
10.3.1 创建对象
使用new运算符可以创建对象,new运算符后面必须是一个构造函数,用于初始化对象实例。
【示例1】下面使用构造函数创建对象。
使用Object构造函数创建的对象是一个不包含任何属性和方法的空对象,而使用内置构造函数创建的对象将会继承该构造函数的属性和方法。
上面示例中第2行代码创建了一个空数组对象,但是这个新创建的对象o具有数组操作的基本方法和属性,如length属性可以获取该数组的元素个数,而push()方法将会为该数组对象添加新元素。
自定义构造函数也可以实例化,详细内容将在后面章节讲解。
【示例2】除了使用构造函数创建对象外,还可以使用直接量来定义对象。
在对象直接量内,属性名与属性值之间通过冒号进行分隔,属性值可以是任意类型的数据,属性名必须符合JavaScript标识符语法规范。属性与属性之间通过逗号进行分隔,最后一个属性末尾不要逗号。使用对象直接量显得更加灵活而直观,因此也是定义对象最广泛的一种方法。
10.3.2 引用对象
对象不能直接访问,只能通过访问对象的引用来间接地访问对象。当创建对象时,存储在变量中的值只是引用对象的地址,而不是对象本身,如图10-1所示。
图10-1 对象引用的示意图
【示例】本示例是实例化对象的引用和删除操作,当删除对象引用的变量o之后,而对象本身依然存在,并没有因为删除变量o而同时被删除。
10.3.3 销毁对象
JavaScript提供一套垃圾回收机制,它能够自动回收JavaScript程序中无用的存储单元。因此,不需要用户专门销毁无用对象以释放内存。当对象没有被任何变量引用时,处于废除状态,JavaScript能自动侦测并运行垃圾回收程序把所有废除的对象注销,以释放内存。
每当函数对象被执行完毕,垃圾回收程序就会自动被运行,释放函数所占用的资源,并释放局部变量。另外,如果对象处于一种不可预知的情况下时,也会被回收处理。
【示例】当把对象的所有引用变量都设置为null时,可以强制对象处于废除状态,并被回收。
在设计中,对于不用的对象,应该把其所有引用变量都设置为null,将对象废除,用来释放内存空间。这是一种好的设计习惯,即节省系统开支,又可以预防错误。
10.4 属性基本操作
属性包括名字和值。属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。值可以是任意JavaScript值,或者(在ECMAScript 5中)可以是一个getter或setter函数(或两者都有)。在后面章节会讲解关于getter和setter函数。
除了名字和值之外,每个属性还有一些与之相关的值,称为属性特性(property attribute)。
☑ 可写(writable attribute):表明是否可以设置该属性的值。
☑ 可枚举(enumerable attribute):表明是否可以通过for/in循环返回该属性。
☑ 可配置(configurable attribute):表明是否可以删除或修改该属性。
在ECMAScript 5之前,通过代码给对象创建的所有属性都是可写的、可枚举的和可配置的。在ECMAScript 5中则可以对这些特性加以配置,第23章将会讲解如何操作。
10.4.1 定义属性
使用冒号可以为对象定义属性,冒号左侧是属性名,右侧是属性值。属性与属性之间通过逗号运算符进行分隔。
【示例1】在本示例中,定义了一个3层嵌套的对象结构。虽然3层嵌套对象都包含相同的属性名,但是由于它们分属于不同的作用域,因此不会发生冲突。
【示例2】除了在对象结构体内定义属性外,还可以通过点运算符在结构体外定义属性。
【示例3】也可以通过构造函数定义属性。
在声明变量时使用var语句,但是在声明对象属性时不能使用var语句。有关构造函数的详细内容请参阅后面章节。
10.4.2 访问属性
通过点运算符可以访问对象属性,点运算符左侧是对象引用的变量,右侧是属性名,属性名必须是一个标识符,而不是一个字符串。
【示例1】针对10.4.1节示例中定义的三级嵌套对象,使用下面代码可以访问最里层对象属性y的值。
alert(o.y.y.y) , //读取第三层对象的属性y的值,返回false
对象属性与变量的工作方式相似,性质相同,用户可以把属性看作是对象的私有变量,用来存储数据。
【示例2】从结构上分析,对象与数组相似,因此可以使用中括号来访问对象属性。针对上面示例,可以使用如下方式来读取最内层的对象属性y的值。
alert(o[“y”][“y”][“y”]), //读取第三层对象的属性y的值,返回false
以数组形式读取对象属性值时,应以字符串形式指定属性名,而不能够使用标识符。
【示例3】对象是属性的集合,因此可以使用for/in语句来遍历对象属性。这对于无法确定对象包含属性时,是非常有用的。
使用for/in语句遍历对象属性时,应该使用数组操作方式来读取对象属性的值,属性没有固定显示顺序,同时也只能够枚举自定义属性,无法枚举某些预定义属性。
提示:当读取不存在的属性时,不会抛出异常,而是返回undefined。
10.4.3 赋值和删除属性
与读取对象属性值操作相同,设置对象属性的值也可以使用点运算符和数组操作方法来实现。
【示例1】下面的代码使用了点运算符和中括号运算符读取属性。
一旦为未命名的属性赋值后,对象会自动创建该名称的属性,在任何时候和位置为该属性赋值,都不需要创建属性,而只会重新设置它的值。
【示例2】使用delete运算符可以删除对象属性,这与变量操作相同。
当删除对象属性之后,不是将该属性值设置为undefined,而是从对象中彻底清除属性。如果使用for/in语句枚举对象属性,只能枚举属性值为undefined的属性,但是不会枚举已删除属性。
10.4.4 对象方法
在JavaScript中,方法(Method)就是对象属性的一种特殊形式,即值为函数的属性。从功能角度分析,方法是对象执行特定行为的逻辑块,是与外界实现行为交互的动作。
【示例1】由于函数是一类数据,它可以被赋值给对象属性,于是该属性就是对象的一个方法。
也可以通过如下方式定义对象的方法:
使用小括号运算符可以调用对象的方法:
o.x(); //调用对象的方法
【示例2】对象的方法内部都包含一个this关键字,它总是引用调用该方法的对象。例如,在对象o的x()方法中访问当前对象的y属性值。当使用不同对象调用时,则返回值也不相同。
【示例3】对象方法与普通函数用法完全相同,可以在方法中传递参数,可以设计返回值。
【拓展】关键字this总是指向当前调用的对象。
在上面示例中,this指向对象o,即定义该方法的对象自身。当然,用户可以使用对象名o引用当前调用对象。
但是,对于构造对象来说,当对象实例化后,用户无法调用当前方法的实例对象的名称,使用this能确保在不同环境下都能找到调用当前方法的对象。
在上面示例中,首先使用this关键字定义一个公共方法,然后创建两个对象o和f,分别设置它们的属性name值为o和f,并绑定公共方法。但是分别调用不同对象的who方法时,会发现返回值是不同的。这是因为在对象o中,this指向对象o自身,而在f对象中,this又指向对象f自身,从而实现使用this关键字进行对象抽象化操作。
提示:构造函数是一类特殊函数,它能够初始化对象,利用参数初始化this关键字所引用对象的x和y属性值。因此,构造函数就相当于对象结构模板,利用它可以实例化包含相同属性但不同属性值的对象。
10.5 原生对象类型
原生对象可以分为3种类型:构造对象、实例对象和原型对象。构造对象和原型对象是两类特殊的对象,但是它们构成了JavaScript编程的核心,实例对象是构造对象的实例化,两者是继承和被继承的关系。
10.5.1 构造对象
构造对象实际上就是构造函数,构造函数与普通函数没有本质区别,不过它拥有this关键字,且只能使用new运算符调用。
JavaScript预定义了几个核心构造对象,如Object、Date、Function等,用户也可以自定义构造对象。为了与普通函数区分开来,一般约定构造函数首字母大写。
【示例1】下面代码定义了一个构造对象Point,该对象包含两个属性x和y。
构造对象不能够直接被引用,必须使用new运算符调用:
var point=new Point(100,200); //实例构造对象
alert(point.x); //读取对象的属性x,返回值100
实例化的point就可以被引用或存取操作。
【示例2】构造对象的属性与构造对象相关联,不与每个实例对象相关联。因此无论创建多少个实例对象,每个构造对象的属性都只有一个副本。
在上面实例中,演示了如何定义构造对象的属性,以及如何读取构造对象的属性值。
构造对象的属性被称为静态属性,实例对象不能继承静态属性,也不能够通过实例对象访问静态属性。反过来,用户也不能通过构造对象访问实例对象的属性。
由于每个构造对象的属性都只有—个副本,所以说构造对象的属性是全局变量,与类型函数(构造对象)关联在一起,在JavaScript的名字空间中拥有自己的作用域,这样就不会被其他同名的全局变量所覆盖。
构造对象的方法是一个与构造对象相关联的方法,而不是与由该类型创建的实例对象相关联的方法。这种方法被称为静态方法,要调用静态方法,只能使用构造对象本身,而不能使用该类的实例对象。
【示例3】针对上面示例中的Point构造对象(类型),可以为它定义一个静态方法。
静态方法与静态属性都是全局变量,在JavaScript名字空间中有一个独立的作用域,避免与全局变量冲突。
【示例4】在静态方法中,不能使用this关键字来读取实例对象的属性。下面做法是错误的。
此时this指代的是Point构造对象,而不是实例对象。由于Point构造对象是Function构造对象的实例,所以下面写法也是合法的。
在上面示例中,this.length表示读取函数Point()中包含的属性个数,所以返回值是2,而不是3。this关键字在这里表示函数Point()的结构。属性name是构造对象Point的属性,而不是函数Point()的本地成员。
10.5.2 实例对象
实例对象是构造函数创建的,通过new运算符调用构造函数实现。
【示例1】实例对象的属性可以通过原型机制从构造函数继承,也可以自定义本地属性。
在上面示例中,实例对象p继承了构造函数Point()的两个属性x和y,同时又自定义了一个本地属性z。继承属性能够共享,任何Point的实例,都拥有该属性。而对本地属性没有共性,每个实例对象的本地属性是不同的,也不能够共享。
【示例2】本地方法与本地属性在本质上都是相同的,它们都是对象的私有变量。本地方法使用关键字this引用实例对象本身。在本示例中,实例对象p1和实例对象p2的方法saying()分别引用各自对象的属性x的值。
使用构造函数创建多个实例,就会产生多个本地方法,多个匿名函数可能会降低系统资源的利用率。因此,在JavaScript中,给构造函数定义方法时,一般通过原型机制定义原型方法,这样能够实现多个实例共享一个方法,降低系统资源浪费。
10.5.3 原型对象
原型对象与实例对象本质相同,不过原型对象包含的属性和方法能够为所有实例对象共享。JavaScript通过原型对象实现继承,被称为原型继承。
【示例1】JavaScript构造对象都有个prototype属性,它引用一个原型对象,当实例化构造对象后,所有实例对象被允许访问这个原型对象。下面代码使用函数的prototype属性为构造函数Point定义了一个原型属性和原型方法。
针对上面示例,也可以这样定义原型属性和原型方法。
使用原型对象可以大幅度降低对系统资源的消耗,所有实例对象都共享同一个原型对象,而不是各自拥有独立的副本。
【示例2】创建实例对象之后,用户可以继续为对象添加原型属性和原型方法,所有实例对象都能够继承这些动态添加的原型属性和原型方法。
上面示例就是在对象实例化之后才定义原型属性和原型方法的,但是实例对象仍然能够继承到这些后来添加的原型成员。
【示例3】实例化后,下面添加原型的方法是错误的,因为它覆盖了prototype属性对原型对象的引用。
当使用new运算符调用构造函数时,JavaScript会自动为实例对象传递一个原型对象的引用,原型对象的引用地址是在实例化过程中自动创建的,一旦覆盖prototype属性值,则JavaScript解释器就无法准确找到原型对象包含的原型方法和原型属性。
【示例4】每个构造函数都有一个原型对象,原型属性可以被每个实例对象共享,而每个实例对象也可以定义本地属性,如果原型属性与本地属性发生冲突时,则本地属性具有更大优先权。
在上面示例中,当JavaScript解释器在检索属性x和y时,首先会检查对象实例是否定义名为x和y的本地属性。如果没有,则会检查原型对象是否定义同名属性。所以上面实例最后返回值为true和false。其中本地属性覆盖初始化属性。但是,当写入属性值时,原型属性为只读属性,写操作只能作用于本地属性。
【示例5】使用delete运算符可以删除实例对象的本地属性x和y,但原型属性是不能够被删除的。因此,利用这个特性可以使用原型属性设计本地属性的初始值。
【示例6】在JavaScript中,所有构造对象都拥有原型对象,如Function、Date、Object等,用户可以利用原型方法为JavaScript内置对象扩展方法。例如,valueOf()方法是Object对象的一个默认方法,但是它的作用域是对象,而不是全局,因此必须按如下方法进行调用:
var a =“string”;
alert(a.valueOf());
下面尝试使用原型方法扩展该方法的用法,使得该方法拥有全局作用域,具有静态函数的功能。例如,可以按如下方式调用valueOf()方法:
var a =“string”;
alert(valueOf(a));
扩展方法如下:
上面代码利用原型方法为Object构造对象定义了一个原型方法valueOf(),这个原型方法虽然与Object对象的默认方法valueOf()重名,但它们的作用域是不同的,所以不会发生冲突。其中自定义的原型方法拥有全局作用域,而Object对象的默认方法valueOf()仅拥有对象作用域。
为了兼容IE浏览器,再定义一个全局变量引用这个原型方法,因为IE浏览器在全局作用域中找不到这个原型方法,而是把它作为对象作用域中的一个方法对待。
10.6 使用构造器
JavaScript在Object超类中定义了一个constructor(构造器)属性,并定义该属性始终指向对象的构造函数。由于所有对象都继承于Object对象,所以每个对象都有一个constructor属性,但是它们的值不同,分别引用自己的构造函数。
【示例1】下面的代码定义了一个自定义类型的函数。
作为对象,构造函数也拥有自己的constructor属性:
通过上面示例可以看到,构造函数Point的构造器为Function对象。
【示例2】本示例显示了超类Object对象,也是通过Function对象构造的。
var o=Object.constructor;
alert(o); //返回Function
Function对象的构造器是它自身:
var o=Function.constructor;
alert(o == Function); //返回true
Object对象是所有对象的超类,作为对象的Function,当然它也是Object的一个子类:
alert(Function instanceof Object);//返回true,说明Function对象是Object对象的实例
反过来Object对象也是Function对象的实例:
alert(Object instanceof Function);//返回true,说明Object对象是Function对象的实例
Object对象是由Function()函数构造的。
【示例3】原型对象也有构造器,它的构造器指向原型对象的构造函数:
var o=Point.prototype.constructor;
alert(o == Point); //返回true,说明原型对象的构造器为Point
constructor属性沿着原型链,指向更高一级的构造器—Object对象:
通过上面示例,可以证明对象的构造器不是唯一的,即constructor属性值不是固定的。但是,如果删除Object()构造函数的原型对象,而对象p的构造器仍然指向Object,这是因为Object是所有对象的超类,所有对象都继承于它。所以,不管Object构造函数的原型对象是否存在,constructor属性最后总是指向它。例如,下面的示例演示了对象p的构造器最终指向Object。
【拓展】由于构造器总是指向一个构造函数,而构造函数定义了一个对象的类,所以用户可以使用属性constructor来判定对象的数据类型。例如,下面条件语句可以确定一个变量是否为数字对象。
但是,对于自定义类型来说,不能保证constructor属性总是存在的。例如,在下面示例中,这个判定标准并不标准,因为constructor属性被修改了,它指向Number()构造函数的引用。
对于JavaScript内置对象来说,由于它们的原型是只读的,用户无法修改它们的原型对象,所以不必使用constructor属性判断内置对象的数据类型。
10.7 使用对象基本方法
在JavaScript中,Object是超级类型,Object对象默认定义了几个核心方法,如表10-2所示,由于继承关系,所有对象都将拥有这些方法。熟练掌握它们,能够提高控制对象的能力。
表10-2 Object对象的基本方法
10.7.1 案例:重写toString
toString()方法能够返回一个对象的字符串表示,它返回的字符串比较灵活,可能是一个具体的值,也可能是一个对象的类型标识。
【示例1】下面代码显示对象实例与对象类型的toString()方法返回值是不同的。
toString()方法返回信息简单,为了能够返回更多有用信息,用户可以重写该方法。例如,针对实例对象返回的字符串都是"[object Object]",可以对其进行扩展,让对象实例能够返回构造函数的源代码。
调用f.toString(),则返回函数的源代码,而不是字符串"[object Object]"。当然,重写方法不会影响JavaScript内置对象的toString()返回值,因为它们都是只读的。
alert(f.toString()); //返回函数的源代码
当把数据转换为字符串时,JavaScript一般都会调用toString()方法来实现。由于不同类型的对象在调用该方法时,所转换的字符串都不同,而且都有规律,所以开发人员常用它来判断对象的类型,弥补typeof运算符和constructor属性在检测对象数据类型的不足,详细内容请参阅第4.4.4节的讲解。
【示例2】当自定义类型时,用户可以重置toString()方法,以便准确跟踪自定义对象的数据类型。例如,对于自定义类型的toString()方法返回值为"[object Object]",可以为自定义类型Me定义一个标识字符串"[object Me]"。
提示:除了toString()方法外,Object还定义了toLocaleString()方法,该方法能够返回对象的本地字符串表示。不过Object对象定义toLocaleString()方法默认返回值与toString()方法返回值完全相同。
JavaScript在部分子类型中重写了toString()和toLocaleString()方法。例如,在Array中重写toString(),让其返回数组元素值的字符串组合;在Date中重写toString(),让其返回当前日期字符串表示;在Number中重写toString(),让其返回数字的字符串表示;在Date中重写toLocaleString(),让其返回当地格式化日期字符串。
10.7.2 案例:重写valueOf
valueOf()方法能够返回对象的值。Object对象默认valueOf()方法返回值与toString()方法返回值相同,但是部分类型对象重写了valueOf()方法。
【示例1】Date对象的valueOf()方法返回值是当前日期对象的毫秒数。
对于String、Number和Boolean对象具有明显的原始值时,它们的valueOf()方法会返回合适的原始值。
【示例2】在自定义类型时,除了重写toString()方法外,建议也重写valueOf()方法。这样当读取自定义对象的值时,避免返回的值总是"[object Object]"。
在特定环境下数据类型转换时(如把对象转换为字符串),valueOf()方法的优先级要比toString()方法的优先级高。因此,如果一个对象的valueOf()和toString()方法返回值不同时,而希望转换的字符串为toString()方法的返回值时,就必须明确调用对象的toString()方法。
【示例3】在本示例中,当获取自定义类型的对象p时,alert()方法会首先调用valueOf()方法,而不是toString()方法,如果需要获取该对象的字符串表示,则必须明确调用对象的toString()方法。
10.7.3 案例:检测私有属性
根据继承关系不同,对象属性可以分为两类:私有属性和继承属性。
【示例1】在下面自定义类型中,this.name就表示对象的私有属性,而原型对象中的name属性就是继承属性:
为了方便判定一个对象属性的类型,Object对象预定义了hasOwnProperty()方法,该方法可以快速检测属性的类型。
【示例2】针对上面自定义类型,可以实例化对象,然后判定当前对象调用的属性name是什么类型。
凡是构造函数的原型属性(原型对象包含的属性),都是继承属性,使用hasOwnProperty()方法检测时,返回false。但是,对于原型对象本身来说,则这些原型属性又是原型对象的私有属性,所以返回值又是true。
【示例3】在本示例中,演示了toString()方法对于Date对象来说,是继承属性,但是对于Date构造函数的原型对象来说,则又是它的私有属性。
hasOwnProperty()方法只能判断指定对象中是否包含指定名称的属性,但无法检查对象原型链中是否包含某个属性,所以能够检测出来的属性必须是对象成员。
【示例4】下面示例演示了hasOwnProperty()方法所能检测的属性范围。
10.7.4 案例:检测枚举属性
在大多数情况下,in运算符是探测对象中属性是否存在的最好途径。然而在某些情况下,可能希望仅当一个属性是自有属性时才检查其是否存在。in运算符会检查私有属性和原型属性,所以不得不选择hasOwnProperty()方法。
【示例1】for/in语句可用来遍历一个对象中的所有属性名,该枚举过程将会列出所有的属性,包括原型属性和私有属性。很多情况下需要过滤掉一些不想要的值,如方法或原型属性。最为常用的过滤器是hasOwnProperty方法,或者使用typeof运算符进行排除。
使用for/in语句枚举,属性名出现的顺序是不确定的,最好的办法就是完全避免使用for/in语句,而是创建一个数组,在其中以正确的顺序包含属性名。通过使用for语句,可以不用担心可能出现原型属性,并且按正确的顺序取得它们的值。
对于JavaScript对象来说,用户可以使用for/in语句遍历一个对象“可枚举”的属性。但并不是所有对象属性都可以枚举,只有用户自定义的本地属性和原型属性才允许枚举。
【示例2】对于下面自定义对象o,使用for/in循环可以遍历它的所有私有属性、原型属性,但是JavaScript允许枚举的属性只有a、b和c。
【示例3】为了判定指定本地属性是否允许枚举,Object对象定义了propertyIsEnumerable()方法。该方法的返回值为true,则说明指定的本地属性可以枚举,否则是不允许枚举的。
10.7.5 案例:检测原型对象
在JavaScript中,Function对象预定义了prototype属性,该属性指向一个原型对象。当定义构造函数时,系统会自动创建一个对象,并传递给prototype属性,这个对象被称为原型对象。原型对象可以存储构造类型的原型属性,以便让所有实例对象共享。
【示例1】下面的代码为自定义类型函数定义了两个原型成员。
当使用new运算符调用函数时,就会创建一个实例对象,这个实例对象将继承构造函数的原型对象中所有属性。从某种意义上讲:
为了方便判定,Object对象定义了isPrototypeOf()方法,该方法可以检测一个对象的原型对象。
【示例2】针对下面的示例,可以判断f.prototype就是对象o的原型对象,因为其返回值为true。
var b=f.prototype.isPrototypeOf(o);
alert(b);
【示例3】下面示例演示了各种特殊对象的原型对象。
☑ 函数的原型对象可以是Object.prototype,或者Function.prototype:
☑ Object和Function对象的原型对象比较特殊:
☑ Object.prototype和Function.prototype的原型对象都是Object.prototype,而Function.prototype的原型对象可以是Function.prototype,但是Object.prototype的原型对象绝对不是Function. prototype:
10.8 使用原生对象
JavaScript预定义了很多核心对象,这些对象是JavaScript开发的基础。如Function对象是原型构造基础,Object对象是对象继承基础,Number、Boolean和String对象是基本数据类型基础。另外,Array、Error、Date、RegExp、Math等对象为程序提供特定功能。本节简单介绍Global、Math和Date对象基本用法,其他对象将在不同章节内详细讲解。更详细的信息可以参考本书光盘中的JavaScript核心对象参考手册。
10.8.1 Global对象
Global表示全局对象,它定义所有JavaScript对象、变量和函数的全局作用域。通过使用全局对象,可以访问其他所有预定义对象、函数、变量、实例对象和属性。在不同宿主环境中,Global指代不同的对象。例如,在客户端浏览器中表示Global指代Window对象。
Global对象拥有众多全局属性(如表10-3所示)和全局方法(如表10-4所示)。
表10-3 Global对象的属性
表10-4 Global对象的方法
10.8.2 Math对象
在JavaScript中,把所有复杂的数学公式与运算都封装在Math对象中。该对象不需要实例化,在全局作用域中可以直接调用。
【示例1】求6除以5的整数值,则可以使用如下方法来实现:
Math对象包含多个数学常量,用来表示特定数学值,它们在数学计算中比较实用,如表10-5所示。
表10-5 Math对象常量
从用途上分析,Math对象方法可以分为两类:专业计算方法(如表10-6所示)和数学常规方法(如表10-7所示)。
表10-6 Math对象专业方法
表10-7 Math对象常规方法
☑ 专业计算方法都是一些数学函数,如三角函数、指数对数计算、根幂计算等。在数学常规方法中,包括数值取值(如取整、取正)、随机数和数值比较。
【示例2】下面的示例是使用random()方法生成1~10之间的随机数(包括1和10)。
random()方法能够生成一个在0~1之间的随机数,不包括0和1。如果希望获取指定范围的随机数,可以使用如下公式:
number=Math.random()*total+first
其中total表示随机数的范围,而first表示可能最小的随机数。例如,生成10个从1~10之间的随机数,可以设计:
如果生成整数随机数,可以使用floor()进行取整,但不可以使用ceil()、round()方法,因为它们会向上取值,导致超出指定范围。
如果希望生成10个在10~20之间的随机整数,不包括10和20,则可以使用如下代码:
其中数值9表示10~20之间的范围,而11表示随机数的可能最小值。
10.8.3 Date对象
在JavaScript中,Date对象负责获取和设置时间。在其他语言中,时间是一种数据类型,但是JavaScript仅把时间作为一种特殊格式的字符串来表示,并通过Date对象进行管理。在特殊环境中,时间也可以转换为数值,如时间比较、时间运算。
创建时间对象的方法有4种。
【示例1】获取本地系统的当前时间。
【示例2】通过多选参数创建指定的时间对象。此时,构造函数Date()的参数格式如下:
new Date(year, month, day, hours, minutes, seconds, ms)
除了前两个参数(年和月)外,其他所有参数都是可选的。其中月数参数从0开始,如0表示第1个月,11表示第12个月。
所有声明的日期和时间使用的都是本地时间,而不是UTC时间。
【示例3】通过时间格式字符串创建指定的时间对象。此时,月份是从1开始,而不是从0开始。代码如下:
【示例4】通过传递一个毫秒数创建指定的时间对象。这个毫秒数是距离1970年1月1日午夜(GMT时间)的毫秒数,代码如下:
创建Date对象之后,就可以调用该对象的各种方法操作时间了。Date对象的方法包括两大类:
☑ 设置时间,如设置时间对象的小时字段setHours(),设置时间对象的月份字段setMonth()等。
☑ 获取时间对象的各个字段值,如获取时间对象的小时字段getHours(),获取时间对象的月份字段getMonth()等。
【示例5】下面的代码是使用时间对象的getDay()获取当前时间属于周几。
【示例6】利用Date对象还可以判断两个时间的时差。本示例可以计算一个循环体空转100万次所花费的毫秒数。
10.9 综合案例:设计计算器
本节设计一个简单的计算器,该计算器能够进行加、减、乘、除四则运算,以及连续运算、求余运算。如果发生被除数为零的错误,会给出错误提示,演示效果如图10-2所示。
图10-2 简单计算器
【操作步骤】
第1步,新建文档,保存为index.html,在页面设计计算器的页面结构,代码如下:
第2步,使用CSS设计计算器的界面效果,详细样式代码就不再显示,读者可以参阅光盘示例代码,效果如图10-3所示。
图10-3 设计界面结构效果
第3步,在页面头部位置插入<script type=“text/JavaScript”>,然后定义6个初始变量。
第4步,定义运算按键的响应函数,设计当点击运算按键时准备执行的动作。最后,需要把这些函数分别绑定到对应按键的标签上。
第5步,在数字按键标签上绑定一个命令函数command(num),该函数将根据按键显示的值,把该值复制到文本框中进行显示,并做好计算准备。
第6步,设计计算函数,该函数将检测设置条件,以及计算类型,执行计算操作。
最后
以上就是个性电话为你收集整理的第10章 使用对象第10章 使用对象的全部内容,希望文章能够帮你解决第10章 使用对象第10章 使用对象所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复