document资料.docx
- 文档编号:25757490
- 上传时间:2023-06-13
- 格式:DOCX
- 页数:22
- 大小:29.76KB
document资料.docx
《document资料.docx》由会员分享,可在线阅读,更多相关《document资料.docx(22页珍藏版)》请在冰豆网上搜索。
document资料
第6章对象
本章目标
了解什么是对象
什么时候定义对象
如何定义对象
使用Object的注意事项
对象是JavaScript的基本数据类型。
在了解JavaScript对象之前,我们先浅显了解什么是对象。
对象就是事物,只是因为计算机编程语言是外国人发明的,起名为object。
对于中国人来说称之为事物,物体更好理解。
在计算机的世界中万物皆为对象,人,动物,计算器,窗口,按钮,英雄联盟中的人物,飞机大战中的子弹等。
对象是一种复合值:
它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。
对象也可看作是属性的无序集合,每个属性都是一个名/值对。
属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。
然而对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JavaScript对象还可以从一个称为原型的对象继承属性。
对象的方法通常是继承的属性。
这种“原型式继承”(prototypalin-heritance)是JavaScript的核心特征。
JavaScript对象是动态的可以新增属性也可以删除属性,但它们常用来模拟静态对象以及静态类型语言中的“结构体”(struct)。
有时它们也用做字符串的集合(忽略名/值对中的值)。
除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。
尽管字符串、数字和布尔值不是对象,但它们的行为和不可变对象非常类似。
对象是可变的,我们通过引用而非值来操作对象。
如果变量x是指向一个对象的引用,那么执行代码vary=x;变量y也是指向同一个对象的引用,但不是这个对象的副本。
通过变量y修改这个对象亦会对变量x造成影响。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate)它的属性。
我们会在开始的几节讲述这些基础操作。
后续的几节讲述高级主题,其中相当一部分内容来自于EC-MAScript5。
刚才我们通过浅显的语文说明了什么是对象,也有专业的术语描述了对象。
现在我们了解什么是属性?
对象是由多个属性够成,属性可以是任意数据类型。
例如人的属性有身高,肤色,体重;动物的属性有是否是哺乳动物,几只腿。
JavaScript中属性包括名字和值。
属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。
值可以是任意JavaScript值。
6.1创建对象
可以通过对象直接量、关键字new和(EC-MAScript5中的)Object.create()函数来创建对象。
接下来几节将对这些技术一一讲述。
6.1.1对象直接量
创建对象最简单的方式就是在JavaScript代码中使用对象直接量。
对象直接量是由若干名/值对组成的映射表,名/值对中间用冒号分隔,名/值对之间用逗号分隔,整个映射表用花括号括起来。
属性名可以是JavaScript标识符也可以是字符串直接量(包括空字符串)。
属性的值可以是任意类型的JavaScript表达式,表达式的值(可以是原始值也可以是对象值)就是这个属性的值。
下面有一些例子:
varempty={};//没有任何属性的对象
varpoint={x:
0,y:
0};//两个属性
varpoint2={x:
point.x,y:
point.y+1};//
varbook={"maintitle":
"JavaScript",//属性名字里有空格,必须用字符串表示
'sub-title':
"TheDefinitiveGuide",//属性名字里有连字符,必须用字符串表示
"for":
"allaudiences",//
"for"是保留字,因此必须用引号
author:
{//这个属性的值是一个对象
firstname:
"David",//注意,这里的属性名都没有引号
surname:
"Flanagan"
}
};
在ECMAScript5(以及ECMAScript3的一些实现)中,保留字可以用做不带引号的属性名。
然而对于ECMAScript3来说,使用保留字作为属性名必须使用引号引起来。
在ECMAScript5中,对象直接量中的最后一个属性后的逗号将忽略,且在ECMAScript3的大部分实现中也可以忽略这个逗号,但在IE中则报错。
对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。
每次计算对象直接量的时候,也都会计算它的每个属性的值。
也就是说,如果在一个重复调用的函数中的循环体内使用了对象直接量,它将创建很多新对象,并且每次创建的对象的属性值也有可能不同。
6.1.2通过new创建对象
new运算符创建并初始化一个新对象。
关键字new后跟随一个函数调用。
这里的函数称做构造函数(constructor),构造函数用以初始化一个新创建的对象。
JavaScript语言核心中的原始类型都包含内置构造函数。
例如:
varo=newObject();//创建一个空对象,和{}一样
vara=newArray();//创建一个空数组,和[]一样
vard=newDate();//创建一个表示当前时间的Date对象
varr=newRegExp("js");//创建一个可以进行模式匹配的EegExp对象
除了这些内置构造函数,用自定义构造函数来初始化新对象也是非常常见的。
第9章将详细讲述其中的细节。
6.1.3原型
在讲述第三种对象创建技术之前,我们应当首先解释一下原型。
原型是JS区别其它编程语言的最重要的特征。
每一个JavaScript对象(null除外)都和另一个对象相关联。
“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对原型对象的引用。
通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。
因此,同使用{}创建对象一样,通过newObject()创建的对象也继承自Object.prototype。
同样,通过newArray()创建的对象的原型就是Array.prototype,通过newDate()创建的对象的原型就是Date.prototype。
没有原型的对象为数不多,Object.prototype就是其中之一。
它不继承任何属性。
其他原型对象都是普通对象,普通对象都具有原型。
所有的内置构造函数(以及大部分自定义的构造函数)都具有一个继承自Object.prototype的原型。
例如,Date.prototype的属性继承自Object.prototype,因此由newDate()创建的Date对象的属性同时继承自Date.prototype和Object.prototype。
这一系列链接的原型对象就是所谓的“原型链”(prototypechain)。
而这一高级部分我们会在JS高级课程里面讲解
6.1.4Object.create()
ECMAScript5定义了一个名为Object.cre-ate()的方法,它创建一个新对象,其中第一个参数是这个对象的原型。
Object.create()提供第二个可选参数,用以对对象的属性进行进一步描述。
6.7节会详细讲述第二个参数。
Object.create()是一个静态函数,而不是提供给某个对象调用的方法。
使用它的方法很简单,只须传入所需的原型对象即可:
varo1=Object.create({x:
1,y:
2});//o1继承了属性x和y
可以通过传入参数null来创建一个没有原型的新对象,但通过这种方式创建的对象不会继承任何东西,甚至不包括基础方法,比如toString(),也就是说,它将不能和“+”运算符一起正常工作:
varo2=Object.create(null);//o2不继承任何
如果想创建一个普通的空对象(比如通过{}或newObject()创建的对象),需要传入Object.prototype:
varo3=Object.create(Object.prototype);//o3和{}和newObject()一样
varo2={};
varo1=newObject();
可以通过任意原型创建新对象(换句话说,可以使任意对象可继承),这是一个强大的特性。
在ECMAScript3中可以用类似例6-1中的代码来模拟原型继承:
例6-1:
通过原型继承创建一个新对象
//inherit()返回了一个继承自原型对象p的属性的新对象
//这里使用ECMAScript5中的Object.create()函数(如果存在的话)
//如果不存在Object.create(),则退化使用其他方法
functioninherit(p){
if(p==null)throwTypeError();//p是一个对象,但不能是null
if(Object.create)//如果Object.create()存在
returnObject.create(p);//直接使用它
vart=typeofp;//否则进行进一步检测
if(t!
=="object"&&t!
=="function")throwTypeError();
functionf(){};//定义一个空构造函数
f.prototype=p;//将其原型属性设置为p
returnnewf();//使用f()创建p的继承对象
}
在看完关于构造函数的内容后,例6-1中的inherit()函数会更容易理解。
现在只要知道它返回的新对象继承了参数对象的属性就可以了。
注意,inherit()并不能完全代替Object.create(),它不能通过传入null原型来创建对象,而且不能接收可选的第二个参数。
不过我们仍会在本章和第9章的示例代码中多次用到inherit()。
inherit()函数的其中一个用途就是防止库函数无意间(非恶意地)修改那些不受你控制的对象。
不是将对象直接作为参数传入函数,而是将它的继承对象传入函数。
当函数读取继承对象的属性时实际上读取的是继承来的值。
如果给继承对象的属性赋值,则这些属性只会影响这个继承对象自身,而不是原始对象:
varo={x:
"don'tchangethisvalue"};
library_function(inherit(o));//防止对o的意外修改
了解其工作原理,需要首先了解JavaScript中属性的查询和设置机制。
接下来会讲到。
6.2属性的查询和设置
4.4节已经提到,可以通过点(.)或方括号([])运算符来获取属性的值。
运算符左侧应当是一个表达式,它返回一个对象。
对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符。
对于方括号来说([]),方括号内必须是一个计算结果为字符串的表达式,这个字符串就是属性的名字:
varauthor=book.author;//得到book的"author"属性
varname=author.surname//得到获得author的"surname"属性
vartitle=book["maintitle"]//得到book的"maintitle"属性
和查询属性值的写法一样,通过点和方括号也可以创建属性或给属性赋值,但需要将它们放在赋值表达式的左侧:
book.edition=6;//给book创建一个名为"edition"的属性
book["maintitle"]="ECMAScript";//给"maintitle"属性赋值
在ECMAScript3中,点运算符后的标识符不能是保留字,比如,o.for或o.class是非法的,因为for是JavaScript的关键字,class是保留字。
如果一个对象的属性名是保留字,则必须使用方括号的形式访问它们,比如o["for"]和o["class"]。
EC-MAScript5对此放宽了限制(包括ECMAScript3的某些实现),可以在点运算符后直接使用保留字。
当使用方括号时,我们说方括号内的表达式必须返回字符串。
其实更严格地讲,表达式必须返回字符串或返回一个可以转换为字符串的值。
在第7章里有一些例子中的方括号内使用了数字,这情况象是非常常见的。
6.2.1作为关联数组的对象
上文提到,下面两个JavaScript表达式的值相同:
object.property
object["property"]
第一种语法使用点运算符和一个标识符,这和C和Java中访问一个结构体或对象的静态字段非常类似。
第二种语法使用方括号和一个字符串,看起来更像数组,只是这个数组元素是通过字符串索引而不是数字索引。
这种数组就是我们所说的关联数组(associativearray),也称做散列、映射或字典(dictionary)。
JavaScript对象都是关联数组,本节将讨论它的重要性。
在C、C++和Java和一些强类型(strongtyped)语言中,对象只能拥有固定数目的属性,并且这些属性名称必须提前定义好。
由于JavaScript是弱类型语言,因此不必遵循这条规定,在任何对象中程序都可以创建任意数量的属性。
但当通过点运算符(.)访问对象的属性时,属性名用一个标识符来表示。
标识符必须直接出现在JavaScript程序中,它们不是数据类型,因此程序无法修改它们。
反过来讲,当通过[]来访问对象的属性时,属性名通过字符串来表示。
字符串是JavaScript的数据类型,在程序运行时可以修改和创建它们。
因此,可以在JavaScript中使用下面这种代码:
varaddr="";
for(i=0;i<4;i++){
addr+=customer["address"+i]+'\n';
}
这段代码读取customer对象的address0、ad-dress1、address2和address3属性,并将它们连接起来。
这个例子主要说明了使用数组写法和用字符串表达式来访问对象属性的灵活性。
这段代码也可以通过点运算符来重写,但是很多场景只能使用数组写法来完成。
假设你正在写一个程序,这个程序利用网络资源计算当前用户股票市场投资的金额。
程序允许用户输入每只股票的名称和购股份额。
该程序使用名为portfolio的对象来存储这些信息。
每只股票在这个对象中都有对应的属性,属性名称就是股票名称,属性值就是购股数量,例如,如果用户持有IBM的50股,那么portfolio.ibm属性的值就为50。
下面是程序的部分代码,这个函数用来给portifolio添加新的股票:
functionaddstock(portfolio,stockname,shares){
portfolio[stockname]=shares;
}
由于用户是在程序运行时输入股票名称,因此在之前无法得知这些股票的名称是什么。
而由于在写程序的时候不知道属性名称,因此无法通过点运算符(.)来访问对象portfolio的属性。
但可以使用[]运算符,因为它使用字符串值(字符串值是动态的,可以在运行时更改)而不是标识符(标识符是静态的,必须写死在程序中)作为索引对属性进行访问。
第5章介绍了for/in循环(6.5节还会进一步介绍)。
当使用for/in循环遍历关联数组时,就可以清晰地体会到for/in的强大之处。
下面的例子就是利用for/in计算portfolio的总计值:
functiongetvalue(portfolio){
vartotal=0.0;
for(stockinportfolio){//遍历portfolio中的每只股票
varshares=portfolio[stock];//得到每只股票的份额
varprice=getquote(stock);//查找股票价格
total+=shares*price;//将结果累加至total中
}
returntotal;//返回total的值
}
6.2.2继承
JavaScript对象具有“自有属性”(ownproperty),也有一些属性是从原型对象继承而来的。
为了更好地理解这种继承,必须更深入地了解属性访问的细节。
本节中的许多示例代码借用了例6-1中的inherit()函数,通过给它传入指定原型对象来创建实例。
假设要查询对象o的属性x,如果o中不存在属性x,那么将会继续在o的原型对象中查询属性x。
如果原型对象中也没有x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查询,直到找到x或者查找到一个原型是null的对象为止。
可以看到,对象的原型属性构成了一个“链”,通过这个“链”可以实现属性的继承。
有关继承参考JS高级课程。
6.2.3属性访问错误
访问对象中的属性并不总是返回或设置一个值。
本节讲述查询或设置属性时的一些出错情况。
查询一个不存在的属性并不会报错,如果在对象o自身的属性或继承的属性中均未找到属性x,属性访问表达式o.x返回undefined。
回想一下我们的book对象有属性“subtitle”,而没有属性“subtitle”:
book.subtitle;//=>undefined:
属性不存在
注意记住这点很重要,并不像JAVA没有的属性会报错。
但是,如果对象不存在,那么试图查询这个不存在的对象的属性就会报错。
null和undefined值都没有属性,因此查询这些值的属性会报错,接上例:
//抛出一个类型错误异常,undefined没有length属性
varlen=book.subtitle.length;
除非确定book和book.subtitle都是(或在行为上)对象,否则不能这样写表达式book.subtitle.length,因为这样会报错,下面提供了两种避免出错的方法:
//一种冗余但很易懂的方法
varlen=undefined;
if(book){
if(book.subtitle)
len=book.subtitle.length;
}//一种更简练的常用方法,获取subtitle的length属性或undefined
varlen=book&&book.subtitle&&book.subtitle.length;
当然,给null和undefined设置属性也会报类型错误。
给其他值设置属性也不总是成功,有一些属性是只读的,不能重新赋值,有一些对象不允许新增属性,但让人颇感意外的是,这些设置属性的失败操作不会报错:
6.3删除
属性delete运算符可以删除对象的属性。
它的操作数应当是一个属性访问表达式。
让人感到意外的是,delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性:
deletebook.author;//book不再有属性author
deletebook["maintitle"];//book也不再有属性"maintitle"
delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)。
当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。
如果delete后不是一个属性访问表达式,delete同样返回true:
o={x:
1};//o有一个属性x,并继承属性toString
deleteo.x;//删除x,返回true
deleteo.x;//什么都没做(x已经不存在了),返回true
deleteo.toString;//什么也没做(toString是继承来的),返回true
delete1;//无意义,返回true
delete不能删除那些可配置性为false的属性。
某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性。
在严格模式中,删除一个不可配置属性会报一个类型错误。
在非严格模式中(以及ECMAScript3中),在这些情况下的delete操作会返回false:
deleteObject.prototype;//不能删除,属性是不可配置的
varx=1;//声明一个全局变量
deletethis.x;//不能删除这个属性
functionf(){}//声明一个全局函数
deletethis.f;//也不能删除全局函数
当在非严格模式中删除全局对象的可配值属性时,可以省略对全局对象的引用,直接在delete操作符后跟随要删除的属性名即可:
this.x=1;//创建一个可配置的全局属性(没有用var)
deletex;//将它删除
然而在严格模式中,delete后跟随一个非法的操作数(比如x),则会报一个语法错误,因此必须显式指定对象及其属性:
deletex;//在严格模式下报语法错误deletethis.x;//正常工作
6.4检测属性
JavaScript对象可以看做属性的集合(例如:
文本框的属性有长度,值,最大长度等属性),我们经常会检测集合中成员的所属关系——判断某个属性是否存在于某个对象中。
可以通过in运算符、hasOwnPreperty()和propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性查询也可以做到这一点。
in运算符的左侧是属性名(字符串),右侧是对象。
如果对象的自有属性或继承属性中包含这个属性则返回true:
varo={x:
1}
"x"ino;//true:
"x"是o的属性
"y"ino;//false:
"y"不是o的属性
"toString"ino;//true:
o继承toString属性
对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。
对于继承属性它将返回false:
varo={x:
1}
o.hasOwnProperty("x");//true:
o有一个自有属性x
o.hasOwnProperty("y");//false:
o中不存在属性y
o.hasOwnProperty("toString");//false:
toString是继承属性
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerableattribute)为true时它才返回true。
某些内置属性是不可枚举的。
通常由JavaScript代码创建的属性都是可枚举的,除非在ECMAScript5中使用一个特殊的方法来改变属性的可枚举性,随后会提到:
varo=inherit({y:
2});
o.x=1;
o.propertyIsEnumerable("x");//true:
o有一个可枚举的自有属性x
o.propertyIsEnumerable("y
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- document 资料