第六章对象
对象可以看成其属性的无序集合,每个属性都是一个名/值对。javascript对象是动态的,可以新增也可以删除属性,可以通过引用而非值来操作对象。如果变量x是指向一个对象的引用,那么执行代码var y=x;变量y也会指向同一个对象的引用,而非这个对象的副本,所以通过变量y修改这个对象也会对x产生影响。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate)它的属性。
属性包括名字和值。属性名可以是包含空字符串在内的任意字符串,值可以是任意值。除了名字和值之外,属性还有一些与之相关的值,称为属性特性(property attribute):
(1)可写:表示是否可以设置该属性的值。
(2)可枚举:表示是否可以通过for/in循环返回该属性。
(3)可配置:表示是否可以删除或修改该属性。
一、创建对象
1、对象直接量
var empty={}; //没有任何属性的对象 var point={x:0,y:0}; var point2={x:point.x,y=point.y}; var book={ "main title":"JavaScript", //属性名有空格,必须用字符串表示 "sub-tit":"The definitive guide", //属性名有连字符,必须用字符串表示 "for":"all audiences", //属性名为保留字,必须用引号 author:{ firstname:"David", surname:"Flanagan" } };
2、通过new创建对象
通过关键字new后跟随一个构造函数的调用实现。
var o=new Object(); //创建一个空对象,和直接量{}直接给定一样 var a=new Array(); //创建一个空数组,和[]一样 var d=new Date(); //创建一个表示此刻时间的Date对象 var r= new RegExp("js"); //创建一个可以进行模式匹配的RegExp对象
3、原型
每一个JavaScript对象都和另一个对象相关联,这另一个对象就是原型,每一个对象都从原型继承属性。
二、属性的查询和设置
var author=book.author; //获得book的"author"属性 var name=author.surname; var title=book["main title"];//获得book的"main title"属性
1、作为关联数组的对象。
下面两个JavaScript表达式的值相同:
object.property
object["property"]
其中第二种语法更像数组,只不过索引是字符串,这里就是所说的关联数组(associalive array)的意思,也称为散列、映射或字典。JavaScript对象都是关联数组。
在C、Java等强类型语言中,对象只能拥有固定数目的属性,并且这些属性名称必须提前定义。由于JavaScript是弱类型语言,因此不必遵循这些要求,任何对象中程序都可以创建任意数量的属性。
在JavaScript中,当通过点运算符(.)访问属性时,属性名用一个标识符来表示(非数据类型),所以这种情况下程序无法修改属性名。而当通过[]访问对象的属性时,属性名通过字符串来表示,字符串是数据类型,可以对属性名进行修改或者创建,如下代码:
var addr=""; //读取costomer对象的属性address0、address1 //address2、address3; //并将各属性的值连接起来 for (i=0;i<4;i++){ addr+=customer["address"+i]+‘\n‘;}
这段代码也可以通过点运算符来重写,但是很多场景只能使用数组写法来完成。假设有个程序利用网络资源计算当前用户股票市场投资额。程序允许用户输入每只股票的名称和购股份额。该程序使用portfolio对象来存储这些信息,属性名就是股票名称,属性值就是购股数量。下面这个函数用来给portfolio对象添加新的股票:
function addstock(portfolio,stockname,shares){ //这里显然只能用数组写法 //如果用“portfolio.stockname”的写法 //则是给对象指定了一个名为“stockname”的属性 //实际需求并非如此 portfolio[stockname]=shares; }
2、继承
JavaScript对象具有“自有属性”(own property),也有一些属性是从原型对象继承而来的。
如果查询对象o的属性x,如果o中不存在x,则会在o的原型对象中继续查询属性x,如果o的原型属性中也没有x,则会继续在这个原型对象的原型对象中查找,直到找到x或者找到一个原型是null的对象为止。
var o={}; //o从object.prototype继承对象的方法 o.x=1; var p=inherit(o); //p继承o和object.prototype p.y=2; var q=inherit(p); //q继承o/p/object.prototype q.z=3; var s=q.toString(); //toString()方法继承自object.prototype q.x+q.y; //输出3
针对上述代码,如果给对象o的属性x赋值,如果属性x已经存在,则是修改它的值;如果属性不存在,则赋值操作会给对象o添加一个新属性x。
属性赋值操作首先检查原型链,以此判定是否允许赋值操作。比如如果o继承自一个只读属性x,那么赋值操作是不允许的;赋值操作也不会影响原型对象的属性。在JavaScript中,只有在查询属性时候才体现出继承的作用,而设置属性则和继承无关。
var unitcircle={r:1}; //一个用来继承的对象 var c=inherit(unitcircle); //c继承unitcircle c.x=1;c.y=1; //设置c的属性 c.r=2; //覆盖原来继承的属性 unitcircle.r; //输出1,原型对象的属性没有改变
属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性,但有一个例外:如果o继承自属性x,而这个属性是一个具有setter方法的accessor属性,那么这是将调用setter方法而不是给o创建一个属性x。需要注意的是,setter方法是由对象o调用的,而不是定义这个属性的原型对象调用的,这个操作只针对o本身,并不会修改原型链。
3、属性访问错误
当查询一个不存在的属性,即在对象自身属性或继承属性中均未找到,则返回undefined,而不会报错。但是如果对象本身就不存在,那么则会报错。另外一种情况,null和undefined值均没有属性,因此查询这些值的属性会报错。
三、删除属性
delete book.author; //删除book的author属性 delete book["main title"]; //删除book的“main title”属性
delete运算符只能删除自有属性,不能删除继承属性,要删除这个继承属性则必须从定义这个属性的原型对象上去删除,而且这会影响到所有继承这个原型的对象。
当delete删除成功或没有任何副作用,返回true;如果delete后不是一个属性访问表达式,同样返回true(因为没有意义)。
o={x:1}; delete o.x; //返回true delete o.x; //x已经没有了,无意义操作,返回true delete o.toString; //无意义操作,toString是继承来的,返回true delete 1; //无意义操作,返回true
delete运算符不能删除可配置性为false的属性,如通过变量声明和函数声明创建的全局对象的属性。
delete Object.prototype; //不能删除,该属性不可配置 var x=1; delete this.x; //不能删除这个全局对象的属性(全局变量) function f() {} delete this.f; //不能删除全局函数
//非严格模式中删除全局对象的可配置属性时 //可省略对全局对象的引用,直接用属性名即可 //用var声明的则是不可配置 this.x=1; //创建一个可配置的全局属性 delete x; //删除x //严格模式中,必须显式指定对象及其属性 delete x; //严格模式下会报语法错误 delete this.x; //正常工作
四、检测属性
可以通过in运算符、hasOwnProperty()和propertyIsEnumerable()方法来判断一个属性是否存在于某个对象中。
var o={x:1} "x" in o; //true "y" in o; //false "toString" in o; //true,继承属性 //hasOwnProperty()检测属性是否为自有属性 var o={x:1} o.hasOwnProperty("x"); //true o.hasOwnProperty("y"); //false o.hasOwnProperty("toString"); //false
propertyIsEnumerable()方法只有检测到这个属性是自有属性并且数可枚举的才返回true.
var o=inherit({y:2}) o.x=1; o.propertyIsEnumerable("x"); //true o.propertyIsEnumerable("y"); //false Object.prototype.propertyIsEnumerable("toString"); //false,不可枚举
五、枚举属性
for/in循环可以在循环体内遍历对象中所有可枚举的属性,把属性名赋值给变量。对象继承的内置方法是不可枚举的,但在代码中给对象添加的属性都是可枚举的。
var o={x:1,y:2,z:3} o.propertyisEnumerable("toString"); //false for(p in o) console.log(p); //输出x,y,z.不会输出toString
//用来枚举属性的对象工具函数 /* *把p中的可枚举属性复制到o中,并返回o *如果o和p有同名属性,则覆盖o中属性 *这个函数并不处理getter和setter以及复制属性 */ function extend(o,p){ for (pro in p) { o[pro]=p[pro]; //将属性添加至o中 } return o; } /* *把p中的可枚举属性复制到o中,并返回o *如果o和p有同名属性,o中的属性不受影响 *这个函数并不处理getter和setter以及复制属性 */ function merge(o,p) { for(prop in p) { if(o.hasOwnProperty[prop]) continue; //如果有同名属性,跳过 o[prop]=p[prop]; } return o; } /* *如果o中属性在p中没有同名,则删除o中的属性 */ function restric(o,p) { for(prop in p) { if(!o.hasOwnProperty(prop)) //可简单用"!(prop in o)替代 delete o[prop]; } return o; } /* *如果o中属性在p中有同名,则删除o中的属性 */ function restric(o,p) { for(prop in p) { if(prop in o) delete o[prop]; } return o; } /*返回一个新对象,这个对象同时拥有o和p的属性 *如果o和p中有重名属性,使用p中属性 */ function union(o,p) { x={}; for(prop in p) { if (prop in o) x[prop]=p[prop]; } return x; } //更简洁的写法 function union1(o,p) { return restric(extend({},o),p); }
六、属性getter和setter
由getter和setter定义的属性称为“存取器属性”(accessor property),它不同于“数据属性”(data property),数据属性只有一个简单的值。当程序查询存取器属性的值时,会调用getter方法;当程序设置存取器属性的值时,则调用setter方法。
定义存取器属性最简单的方法是使用对象直接量语法的一种扩展写法:
var o= { //普通的数据属性 data_prop:value, //存取器属性都是成对定义的函数 get accessor_prop() { /*函数体内容*/ }, set accessor_prop(value) { /*函数体内容*/ } }
存取器属性定义为一个或两个和属性同名的函数,需要注意的是这个函数定义没有使用function关键字,而是使用get和set。
var p={ //x和y是普通的可读写的数据属性 x:1.0, y:1.0, //r是可读写的存取器属性 //函数体结束后需要加逗号 get r() { return Math.sqrt(this.x*this.x+this.y*this.y); }, set r(newvalue) { var oldvalue=Math.sqrt(this.x*this.x+this.y*this.y); var ratio=newvalue/oldvalue; this.x *=ratio; this.y *=ratio; }, //theta是只读存取器属性,只有getter方法 get theta() { return Math.atan2(this.y,this.x); } };
和数据属性一样,存取器属性是可以继承的,针对上述代码的示例如下:
var q=inherit(p); //创建一个继承getter和setter的新对象 q.x=1,q.y=1; console.log(q.r); console.log(q.theta);
七、对象的三个属性
每个对象都有与之相关的原型(prototype)、类(class)和可扩展性(extensible attribute)三个属性。
1、原型属性
原型属性是在实例对象创建之初就设置好的。通过对象直接量创建的对象使用Object.prototype作为它们的原型,通过new创建的对象使用构造函数的prototype属性作为原型。通过Object.create()创建的对象使用第一个参数作为原型。
要检测一个对象是否是另一个对象的原型(或处于原型链中),要使用isPrototypeOf()方法。
var p={x:1}; //定义一个原型对象 var o=Object.create(p); //使用这个原型p创建一个对象o p.isPrototypeOf(o) //true Object.prototype.isPrototypeOf(o) //p继承自object.prototype
2、类属性
对象的类属性(class attribute)是一个字符串,用以表示对象的类型信息。要想获得对象的类,可以调用对象的toString()方法。
3、可扩展性
对象的可扩展性用以表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的。