对象是javascript的基本数据类型。对象是一种复合值。它将很多值(原始值 或者其他对象)聚合在一起。可通过名字访问这些值。对象也可以看做是属性的无序集合,每个属性都有一个名/值。属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。这种基本数据结构还有很多叫法,有些我们已经非常熟悉,比如“散列”(hash)、“散列表”(hashtable)、“字典”(dictionary)、“关联数组”(assciativeArray)。然而对象不仅仅是字符串到值的映射,除了可以保持的自有的属性,javascript对象还可以从一个陈为原型的对象继承属性。对象的方法通常是继承的属性。这种“原型式继承”(protypal inheritance)是javascript的核心特征。
javascript对象是动态的--可以新增属性也可以删除属性。--但他们常来模拟静态对象以及静态类型语言中的“结构体”(struct)。有时他们也用做字符串的集合(忽略名/值对应中的值)。
除了字符串、数字、true、false、null、undefined之外,javascript值都是对象。尽管字符串、数字、和布尔值不是对象。但他们的行为和不可变对象(3.6)非常相似。
3.7节已经讲到,对象是可变的,我们通过引用而非值来操作对象。如果变量x是指向一个对象的引用,那么指向代码var y =x;变量y也是指向同一个对象的引用。而非这个对象的副本,通过修改y的值也会对变量x造成影响。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)、和枚举(enumerate)它的属性。我们在开始几节讲述这些基础的操作。后续几节讲述高级的内容。其中一部分内容来自ECMAScrit5。
属性包含名字和值,属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。值可以是任意javascript值。或者在(ECMAScript5中)可以是一个getter或者setter函数的讲解。除了名字和值之外,每个属性还有一些与之相关的值,称为“属性特性”(property attribute)
- 可写:(writable attribute),表明是否可设置该属性的值。
- 可枚举:(enumerable attribute),表明是否可以通过for/in循环返回该属性
- 可配置:(configurable attribute)表明是否可以删除或修改该属性。
在ECMAScript5之前,通过代码给对象创建的所有属性都是可写可枚举的的。在ECMAScript5之后则可以对这些特性加以配置。6.7讲述如何操作。
除了基本的属性之外,每个对象还有三个相关的对象特性(object attribute):
- 对象的原型(protype)指向另外一个对象,本对象的属性继承它的原型对象。
- 对象的类(class)是一个表示对象类型的字符串
- 对象的扩展标记(exensible flag)指明了(在ECMAScript5中)指明了是否可以向该对象添加新属性。
对象最常见的用法是创建(create)、设置(set)、查找
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)、和枚举(enumerate)它的属性。我们在开始几节讲述这些基础的操作。后续几节讲述高级的内容。其中一部分内容来自ECMAScrit5。
1.iii和2.ii会有关原型和属性继承的讲述,6.8节会进一步讲述着三个特性。
最后,我们用下面这些属于对三类javascript对象和两类属性做区分
- 内置对象(native object)是由ECMAscript规范定义的对象或类。例如,数组、函数、日期政策表达式都是内置对象
- 宿主对象(host object)是由javascript解释器锁嵌入的宿主环境(比如web浏览器)定义的。客户端javascript中表示网页结果的htmlelement均是宿主对象。宿主环境定义的方法可以当成普通的javascript的函数对象,那么宿主对象也可以当成内置对象。
- 自定义对象(user-defind object)是由运行的javascript代码创建的对象:
- 自有属性(own property)是直接在对象中定义的属性
- 继承属性(inherited property)是在对象的原型对象中定义的属性。
1.创建对象
可以通过对象直接量、关键字new和(ECMAscript5中的)Object.create()函数来创建对象。
i.对象的直接量
创建对象最简单的方式就在是javascript代码中使用对象的直接量。对象直接量是由若干名/值组成的映射表,名/值对象中间用用冒号分隔,名/值之间 用逗号分隔,整个映射表用花括号括起来。属性名可以是javascript标识符也路是字符串直接量(包括空字符串)。属性的值可以是任意类型的javascript表达式,表达式的值(可以是原始值),就是这个属性的值。
var empty ={};//一个空对象 var point ={x:0,y:0}; //两个属性 var point2 ={x:point.x,y:point.y+1}; var book ={ "main title":"javascript", //属性名字里有空格,必须用字符串表示 ‘sub-title‘:"the defintive guide", //属性里有连接字符,因此需要使用双引号 "for":"all adiences", //"for是保留字,因此需要双引号。 author:{ //这个属性的值是一个对象 firstName:"dabid", //这里属性的值也是一个对象 surname:"flangan" //这里的属性名都没有引号 } };
对象的直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。每次计算对象直接量的时候,也都会计算它的每个属性的值。也就是说,如果在一个重复的调用函数中循环体内使用了对象的直接量。它将创建很多新对象。并且每次创建的对象的属性值也有可能不同。
ii.通过new创建对象
通过new创建并初始化一个对象,关键字new后跟随一个函数调用。这里的函数称作构造函数(constructor),构造函数用以初始化一个新创建的对象。javascript语言核心中的原始类型都包含内置构造函数。例如:
var o =new Object();创建一个空对象,和{}一样 var a =new Array(); var d =new Date(); var r =new RegExp("js");
除了这些内置构造函数,用自定义构造函数来初始化新对象也是非常常见的。第9章将详细描述其中的细节
iii.原型
在讲述第三章对象创建技术之前,我们应该首先解释一下原型。每一个javascript对象(null除外)都和令一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。
通过对象直接量创建的对象都有同一个原型函数,并可以通过javascript代码Object.prototype获得对原先对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数prototype属性的值。因此,同使用{}创建对象一样,通过new Object()创建的对象也继承Object.prototype.同样,通过new arrary创建的对象原型就是Arrary.prototype,通过new Array()对象创建的对象的原型就是Date.prototype.
没有原型的对象为数不多,object.prototype就是其中之一。它不继承任何属性。其它原型的对象都是普通对象,普通对象都具有原型。所有的内置构造函数(以及大部分自定义的构造函数)都具有 一个继承自Object.prototype的原型。
例如Date.prototype的属性继承自Object.protype,因此,new Date()创建的date对象的属性同时继承Date.prototype和Object.prototype。这一系列链接的原型对象就是所谓的“原型链”(prototype chain)。
6.2.ii会讲述继承的工作机制。6.8.i会讲解如何获取对象的原型。第9章将会更详细的讨论原型和构造函数,包括如何用过编写构造函数定于对象“类”,以及给构造函数的protype属性值可以让其“实例”直接使用上这个原型的属性和方法。
iiii.Object.create()
ECMAScript5定义了一个名为Object.create()的方法,它创建一个新的对象,其中第一个参数就是这个对象的原型。Object.create()提供第二个可选参数,用以对对象的属性进行进一步描述。(6.7会详细讲解第二个参数)
Object.create()是一个静态函数,而不是提供给某个对象的调用方法。使用它的方法很简单,只需传入所需的原型对象即可。
var o1 = Object.create({ x: 1, y: 2 }); //o1继承了属性x和y
可以通过传入参数null来创建一个没有原型的新对象,但通过这样方式创建的对象不会继承任何东西,甚至不包括基础方法。比如toString(),也就是说,它不能和“+”运算符一起工作。
var o2 = Object.create(null); //o2不继承任何属性和方法
如果要创建一个普通的空对象,(比如通过{}或new Object()创建的对象),需要传入Object.prototype:
var o3 =Object.create(Object.prototype); //o3和{}和new Object一样
可以通过任何原型创建新对象(换句话说,可以使任意对象可继承),这是一个强大的特性。在ECMACscript3中可以用类型下面的代码模拟属性继承。
//inherit()返回了一个继承自原型对象p属性的新对象 //这里是有ECMAScript5中的Object.create()函数(如果存在的话) //如果不存在Object.create,则使用其他方法 function inherit(p) { if (p == null) throw TypeError(); //p是一个对象,不能是null if (Object.create) //如果Object.create存在 return Object.create(p); //直接使用它 var t = typeof p; //否则进一步检测 if (t !== "object" && t !== "function") throw TypeError; function f() {}; //定义一个空构造函数 f.prototype = p; //将其原型属性设置p return new f(); //将f()创建p的继承对象 }
在看完第9章关于构造函数的内容后,上面的inherit()函数更容易理解。现在只要知道它返回的新对象继承了参数对象的属性就可以了。注意:inherit()并不能完全代替Object.create(),它不能通过传入null原型来创建对象,而且不能接收可选的第二个参数。不过我们任辉在本章和第9章示例代码中多次用到inherit()
inherit()函数的其中一个用途就是防止库函数无意间(非恶意地)修改那行不受你控制的对象。不是将对象直接作为参数传入函数,而是将它的继承对象传入函数。当函数读取继承对象的属性时,实际上读取的是继承来的值。如果给继承对象的属性赋值,则泽祥属性只会影响到这个继承对象自身。而不是原始对象:
var o = {x: "donot change this balue"}; library_function(inherit(o));//防止对o的意外修改
了解其工作原理,需要首先了解javascript中的属性的查询和设置机制。接下来会讲到。
2.属性的查询和设置
我们可以通过点.和方括号[]运算符来获取属性的值。运算符的左侧应当是一个表达式,它返回一个对象。对于.来说,右侧必须是一个属性名称命名的简单标识符。对于方括号来说[],内必须是一个计算结果为字符串的表达式,这个字符串就是属性的名字:
var book ={ "main title":"javascript", //属性名字里有空格,必须用字符串表示 ‘sub-title‘:"the defintive guide", //属性里有连接字符,因此需要使用双引号 "for":"all adiences", //"for是保留字,因此需要双引号。 author:{ //这个属性的值是一个对象 firstName:"dabid", //这里属性的值也是一个对象 surname:"flangan" //这里的属性名都没有引号 } }; var oAuthor = book.author; //得到book的“author”属性 var oName = oAuthor.surname //得到author的“surname”的属性 var oTitle =book["main title"] //得到book的main title属性 book.edition = 6;// 给book添加一个名为edition的属性 book["main title"] ="ECMAscript" //给"main title"属性赋值
和查询属性值的写法一样,通过点和方括号也可以创建属性或给属性赋值,但需要将他们放在属性表达式的左侧
在ECMAScript3中 ,点运算符后的标识符不能是保留字,比如o.for或o.class是非法的,因为for是javascript关键字,class是保留字。如果一个对象的属性名是保留字,则必须使用方括号的形式访问他们。比如o["for"]或o["classs"].ECMAScript5对此放宽了限制。可以在点运算符后直接使用保留字。
当使用方方括号时,我们说方括号内的表达式必须返回字符串。其实更严格的讲,表达式必须返回字符串或返回一个科可转换的字符串的值、在第七章第一个 例子里使用看方括号而非数组。这样的情况很常见。
i.作为关联数组的对象
上文提到,下面的两个表达式的值相同:
object.property object["property"]
第一种语法使用点运算符和一个标记符,这和C和Java中访问一个结构体或对象的静态字段非常类似。第二种语法使用方括号和一个字符串。看起来更像数组,只是这个数组元素是通过字符串索引而不是数字索引。这种数组就是我们所说的关联数组(associative array),也称作散列,映射或字典。javascript对象都是关联数组,本节讨论其重要性。
在c c++和java一些强类型(strong typed)语言中,对象只能拥有固定数目的属性,并且这些属性的名称需要提前定义好。由于javascript是弱类型语言,因此不必遵守这条规定,在任何程序对象中都可以创建任意数量的属性。但当通过.运算符访问对象的属性时,属性名用一个标识符来表示。标识符必须出现在javascript程序中,它们不是数据类型,因此程序无法修改它们(程序运行时,无法指定一个标识符。eval除外。)
反过来讲,当通过[]来访问对象的属性时,属性名通过字符串来表示。字符串是javascript的数据类型。在程序运行时都可以修改和创建它们。因此,在javascript可以使用下面的代码。
var addr = ""; for (i = 0; i < 4; i++) { addr += customer["address" + i] + ‘\n‘; };
这段代码读取customer对象的address0 address1 address2 address3 并将它们连接起来。
这个例子主要说明了使用数组写法和用字符串表达式来访问对象属性的灵活性。这段代码也可以通过点运算符来从写。但是很多场景智能使用数组的写法来完成。假设你正在写一个程序,这个程序计算当前股票市场的投资金融。程序允许用户输入每只股票的名称和股份份额,该程序使用名为portfolio对象来存储这些信息。每只股票在这个对象中都有对应的属性。属性的明川就是股票名称。属性值是就股票购股数量。例如用户持有ibm股票50股,那么portfilio.ibm属性值就是50
下面是程序的部分代码,这个函数用来给portfolio添加新股票
function addstock (portfolio,stockname,shares){ portfolio[stockname] = shares;
用于用户是在程序运行时输入股票名称,因此在之前无法得知这些股票的名称是什么 。而由于用户在写程序的时候不知道属性名称,因此无法通过点(.)运算符来访问对象portfolio的属性。但可以使用[]运算符,因为它使用字符串值(字符串是动态 的,可以在运行时更改),而不是标识符(标识符是静态的,必须写死在程序中)作为索引对属性访问。
第5章介绍的for/in循环,(6.5还会进一步介绍)。当使用for/in循环遍历关联数组时,就可以体会到for/in强大之处。下面的例子 就是for/in计算portfolio的总计算值
function getvalue(protfolio) { var total = 0.0; for (stock in protfolio) { //遍历protfolio中的每只股票 var shares = protfolio[stock]; //得到每只股票的份额 var price = getquote(stock); //查找股票的价格 total += shares * price; //将结果累加到total中 } return total; }
ii.继承
javascript对象具有“自有属性”(own property),也有一些属性是从原型对象继承而来的。为了 更好的理解这种继承,必须深入地了解属性访问的细节。本节中借用了6.1的inherit()函数,通过给它指定原型对象来创建实例。
假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x.如果原型对象中也没有x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查找。直到找到x或者查找到一个原型是null为止。可以看到,对象的原型属性构成了一个“链”,通过这个“链”可以实现属性的继承。
//inherit()返回了一个继承自原型对象p属性的新对象 //这里是有ECMAScript5中的Object.create()函数(如果存在的话) //如果不存在Object.create,则使用其他方法 function inherit(p) { if (p == null) throw TypeError(); //p是一个对象,不能是null if (Object.create) //如果Object.create存在 return Object.create(p); //直接使用它 var t = typeof p; //否则进一步检测 if (t !== "object" && t !== "function") throw TypeError; function f() {}; //定义一个空构造函数 f.prototype = p; //将其原型属性设置p return new f(); //将f()创建p的继承对象 } var o = {} o.x = 1; var p = inherit(o); p.y = 2; var q = inherit(p); q.z = 3; var s = q.toString(); q.x + q.y //=>3
现在假设给对象o的属性x赋值,如果o中已经有属性x(这个属性不是继承来的),那个这个操作只改变已有属性x的值。如果o中不存在属性x,那么赋值操作给o添加一个新属性x,那么继承的属性就被创新的同名属性覆盖了。
属性赋值操作首先检查原型链,以此判断是否允许赋值操作。例如:o继承自一个只读属性x,那么赋值操作是不允许的(6.2.iii详细讨论说明)。
如果允许属性赋值操作,它也总是在原始对象上创建属性或对已经有的属性赋值,而不会修改原型链。在javascript中,只有在查询属性时才能体会到继承的存在,而设置属性和继承无关,
这也是javascript的一个重要特性,该特性让程序员有选择地覆盖(override)继承的属性
//inherit()返回了一个继承自原型对象p属性的新对象 //这里是有ECMAScript5中的Object.create()函数(如果存在的话) //如果不存在Object.create,则使用其他方法 function inherit(p) { if (p == null) throw TypeError(); //p是一个对象,不能是null if (Object.create) //如果Object.create存在 return Object.create(p); //直接使用它 var t = typeof p; //否则进一步检测 if (t !== "object" && t !== "function") throw TypeError; function f() {}; //定义一个空构造函数 f.prototype = p; //将其原型属性设置p return new f(); //将f()创建p的继承对象 } var unit = { r: 1 } // 一个用来继承的的对象 var c = inherit(unit); //c继承r c.x = 1;c.y = 1; //c定义两个属性 c.r =2 ; //c覆盖继承来的属性 console.log(unit.r) //=>1 对象原型没有被修改
(本文未完结,请继续关注。)