对象是一种复合值:它将很多值(原始值或其他对象)聚合在一起,可通过名字访问这些值。
对象也可看做是属性的无序集合,每个属性都是一个名/值对。
除了包含属性之外,每个对象还拥有3个特性(object attribute):
- 对象的原型(prototype):指向另外一个原型对象,本对象的属性继承自它的原型对象。
- 对象的类(class):一个标识对象类型的字符串。
- 对象的扩展标记(extensible flag):指明是否可以向该对象添加新属性。
JavaScript中包含了3类对象和2类属性:
- 内置对象(native object):由ECMAScript规范定义的对象或类。如,Array, function, Date, RegExp等。
- 宿主对象(host object): 由JavaScript解释器所嵌入的宿主环境(如Wbd浏览器)定义的。如,表示网页结构的HTMLElement对象。
- 自定义对象(user-defined object):由运行中的JavaScript代码创建的对象。
- 继承属性(inherited property):在对象的原型中定义的属性。
- 自有属性(own property):直接在对象中定义的属性。
对象的创建
直接量创建对象
对象直接量是同若干名/值对组成的映射表,名/值对中间用冒号分隔,名/值对之间用逗号分隔,整个映射表用花括号括起来。
var empty = {}; // 没有任何属性的对象
var point = { x:0, y:0 }; // 包含2个属性的对象
var point2 = { x:point.x, y:point.y+1 } // 使用对象的值来赋值属性的值
var book = {
"main title":"JavaScript", // 属性名字里有空格,必须用引号
"sub-title":"The Definitive Guide", // 属性名字里有连字符,必须用引号
"for":"all audiences", // "for"是保留字,必须使用引号
author:{ // 对象属性
firstname:"David",
surname:"Flanagan"
}
}
对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。
通过new创建对象
new运算符创建并初始化一个新对象,关键字new后面跟随一个构造函数(constructor)调用。
var o = new Object(); // 创建一个空对象,和{}一样(只包含Object.prototype)
var a = new Array(); // 创建一个空数组,和[]一样
var d = new Date(); // 创建一个表示当前时间的Date对象
var r = new RegExp("js"); // 创建一个可以模式匹配的RegExp对象
通过Object.creat()创建对象
Object.creat()是一个静态函数,其中第一个参数是待创建对象的原型,第二个参数可选,用以对对象的属性进行进一步描述。
var o1 = Object.create(Object.property); // 创建一个空对象,同{}、new Object()一样
var o2 = Object.create({x:1, y:2}); // 创建一个新对象,并继承了属性x和y
var o3 = Object.creat(null); // 创建一个对象,但不继承任何属性及方法
可以通过任意原型创建新对象,如:
// inherit()返回一个继承自原型对象p属性的新对象
function inherit(p) {
// p是一个对象,但不能是null
if(p == null) throw TypeError();
// 如果Object.create()存在,则直接使用它来创建对象
if(Object.create)
return Object.create(p)
var t = typeof p;
if(t !== "object" && t !== "function") throw TypeError();
// 通过prototype属性创建对象
function f() {}
f.prototype = p;
return new f();
}
inherit()并不能代替Object.create(),它不能传入null原型来创建对象,也不能接收第二个参数。
属性相关操作
属性继承
通过继承创建的新对象,对新对象的操作并不会影响到原型对象。
var a = { r:1 };
var b = inherit(a);
b.x = 1; b.y = 1; // 新对象增加2个属性
b.r = 2; // 修改新对象的继承属性
a.r; // => 1,原型对象的属性不会受影响
假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询,直接找到x或者找到一个原型是null的对象为止。
属性访问错误
- 查询一个不存在的属性并不会报错。
book.subtitle; // => undefined:属性不存在,但不会报错
- 但是,如果对象不存在,那么试图查询这个不存在对象的属性就会报错。
book.subtitle.length; // undefined没有length属性,抛出一个类型错误异常
- 有一此属性是只读的,不能重新赋值,但是设置属性的失败操作不会报错。
Object.prototype = 0; // => Object原型是只读的,赋值失败,但不会报错
下面提供两种避免出错的方法:
// 一种冗余但很易懂的方法
var len = undefined;
if(book) {
if(book.subtitle)
len = book.subtitle.length;
}
// 一种更简练的方法,len被赋值为undefined或length
var len = book && book.subtitle && book.subtitle.length;
删除属性
- delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性
a = { p:{x:1} };
b = a.p;
delete a.p;
b.x; // => 1,已经删除的属性引用(b)依然存在
- delete 只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响所有继承的对象)
- delete 不能删除那么可配置性为false的属性
var x = 1; // 使用var定义的全局变量
delete this.x; // 不能删除,可配置性为false
this.y = 1; // 不使用var定义的全局变量
delete y; // 可删除,可配置性为true
检测属性
可通过in运算符、hasOwnProperty()和propertyIsEnumerable()来判断某个属性是否存在于某个对象中。
其中propertyIsEnumerable()是hasOwnProperty()的增强版,保有检测到是自有属性并且这个属性是可枚举时才返回true。
var o = { x: 1 };
"x" in o; // true,x是o的属性
"y" in o; // false,y不是o的属性
"toString" in o; // true,【继承属性】(方法)
o.hasOwnProperty("x"); // true,x是【自有属性】
o.hasOwnProperty("y"); // false
o.hasOwnProperty("toString"); // false,toString是【继承属性】
除了使用in运算符之外,还有更简便的方法(!== undefined):
var o = { x: 1 };
o.x !== undefined; // true
o.y !== undefined; // false
o.toStirng !== undefined; // true
然而有一种场景这种方法是不可行的:属性值被显式地定义为undefined。
var o = { x: undefined };
o.x !== undefined; // false,属性实际上是存在的!!!
注:还有一种情况是,这个属性只有setter方法,而没有getter方法。
枚举属性
for/in循环可遍历对象中所有可枚举的属性(包括自有属性和继承属性),对象的内置方法是不可枚举的,但在代码中给对象添加的属性是可枚举的。
// 把p中的可枚举属性复制到o中,并返回o
// 如果o和p有同名属性,则会覆盖o中的属性
// 这个函数不处理getter和setter以及复制属性
function extend(o, p) {
// 遍历p中所有可枚举属性
for(prop in p) {
o[prop] = p[prop];
}
return o;
}
存取器属性(getter & setter)
在ECMAScript 5中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter。
- 和数据属性不同,存取器属性不具有可写性。如果属性同时具有getter和setter方法,那么它是一个读/写属性;如果只有getter方法,则是一个只读属性;如果只有setter方法,则是一个只写属性,读取时返回undefined。
- 和数据属性一样,存取器属性是可以继承的。
定义存取器属性(使用直接量语法)的语法如下:
var p = {
// 普通的数据属性
x: 1.0,
y: 1.0,
// 存取器属性
get r() { return Match.sqrt(this.x*this.x + this.y*this.y); } , // 不需要function声明,并且在最后添加“,”
set r(newvalue) {
this.x = newvalue;
this.y = newvalue;
} ,
get theta() { return Match.atan2(this.y, this.x); }
}
代码中定义了可读写属性r和只读属性theta。
属性的特性
除了包含名字和值之外,属性还包含可写、可枚举和可配置的特性。
属性类型 | 属性特性 |
---|---|
数据属性 | 值(value)、可写性(writable)、可枚举性(enumerable)、可配置性(configurable)。 |
存取器属性 | 存取(get)、写入(set)、可枚举性和可配置性。 |
可通过Object.getOwnPropertyDescriptor()获取属性描述符:
// 返回{value: 1, writable:true, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor({x:1}, "x");
var random = {
get octet() { return 1; }
}
// 返回{get:/*func*/, set:undefined, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor(random, "octet");
// 对于继承属性和不存在的属性,返回undefined
Object.getOwnPropertyDescriptor({}, "x"); // undefined,没有这个属性
Object.getOwnPropertyDescriptor({}, "toStirng"); // undefined,继承属性
Object.defineProperty()用于设置属性的特性:
var o = {}; // 创建一个空对象
// 添加一个属性x,并赋值为1
Object.defineProperty(o, "x", { value: 1,
writable: false,
enumerable: false,
configurable: true } );
o.x = 2; // 操作失败,属性不可写
// 可通过重新设置属性特性来改变属性的值
Object.defineProperty(o, "x", {value: 2});
o.x; // => 2
// 将数据属性修改存取器属性
Object.defineProperty(o, "x", { get: function() { return 3; });
o.x; // => 3
前提 | 特性 |
---|---|
对象不可扩展 | 可以编辑已有属性,但不能添加新属性 |
属性不可配置 | 不能修改它的可配置性和可枚举性 |
存取器属性不可配置 | 不能修改其getter和setter方法,也不能将它转换为数据属性 |
数据属性不可配置 | 则不能将它转换为存取器属性;不能修改它的可写性false=>true,但可以修改true=>false |
数据属性不可配置且不可写 | 不能修改属性的值 |
数据属性可配置不可写 | 可以修改属性的值(实际上先修改属性为可写,然后修改值) |
一个更完善的属性扩展属性函数extend():
Object.defineProperty(Object.prototype,
"extend",
{
writable: true,
enumerable: false,
configurable: true,
// 扩展值是一个函数
value: fucntion(o) {
// 得到所有的自有属性,包括不可枚举属性
var names = Object.getOwnPropertyNames(o);
// 将属性逐个扩展给Object.prototype
for(var i=0; i < names.length; i++) {
// 如果属性已经存在,则跳过
if(names[i] in this) continue;
var desc = Object.getOwnPropertyDescriptor(o, names[i]);
Object.defineProperty(this, names[i], desc);
}
}
}
);
对象的三个属性
原型属性
每一个JavaScript对象(null除外)都和另外一个对象相关联,这个对象就是原型对象,每一个对象都从原型继承属性。
没有原型的对象为数不多,Object.prototype就是其中之一。它不继承任何属性。
对象 | 原型 |
---|---|
通过对象直接量创建的对象 | Object.prototype。 |
通过new创建的对象 | 构造函数的prototype属性。 |
通过Object.creat()创建的对象 | 第一个参数(也可以是null)作为它们的原型。 |
使用isPrototypeOf()进行原型判断,判断在原型链(prototype chain)上进行,如:
var p = { x: 1 };
var o = Object.create(p);
p.isPrototypeOf(o); // true,o继承自p
Object.prototype.isPrototypeOf(o); // true,p继承自Object.prototype
类属性
对象的类属性(class attribute)是一个字符串,用以表示对象的类型信息。
默认的toString()方法(继承自Object.prototype)返回了如下这种格式的字符串:
[object class]
很多对象继承的toString()方法重写了,为了能调用正确的toString()方法,必须间接地调用Function.call()方法。
function classof(o) {
if(o === null) return "Null";
if(o == undefined) return "Undefined";
return Object.prototype.toString.call(o).slice(8, -1); // 使用Function.call方法
}
通过对象直接量、new构造函数或者Object.create()这3种方式创建的对象,类属性都是”Object”。
classof(null); // "Null"
classof(1); // "Number"
classof(""); // "String"
classof(false); // "Boolean"
classof({}); // "Object"
classof([]); // "Array"
classof(/./); // "RegExp"
classof(new Date());// "Date"
classof(window); // "Window"
function f() {}
classof(f); // "Function",注意与下面的区别
classof(new f()); // "Object"
可扩展性
对象的可扩展性表示是否可以给对象添加新属性。
所有的内置对象和自定义对象都是显式可扩展的,宿主对象的可扩展性由JavaScript引擎定义的。
方法 | 作用 |
---|---|
Object.isExtensible() | 判断对象是否可扩展。 |
Object.preventExtensions() | 将对象设置为不可扩展。注意:一旦设置为不可扩展,将无法再转换回可扩展。 |
Object.seal() | 将对象设置为不可扩展,同时所有的自有属性不可配置。注意:对于已经封闭(sealed)的对象不能解封。 |
Object.freeze() | 除了完成seal()的功能外,同时还将所有的数据属性设置为只读(如果对象的存取器属性具有settter方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)。 |
序列化对象(serialization)
对象序列化(JSON.stringify())是指对象的状态转换为字符串,也可将字符串还原(JSON.parse())为对象。
JSON.stringify()只能序列化对象可枚举的自有属性。
o = { x:1, y:{z:[false, null, ""]} };
s = JSON.stringify(o); // s是"{ x:1, y:{z:[false, null, ""]} }"
p = JSON.parse(s); // p是o的深拷贝
- Number、String、Boolean、null、对象、数组都可以序列化和还原。NaN、Infinity和-Infinity序列化结果是null。
- Date对象序列化的结果是ISO格式的日期字符串(参照Date.toJSON()函数),但JSON.parse()依然保留它们的字符串形态。
- 函数、RegExp、Error对象和undefined值不能序列化和还原(没有定义toJSON()方法)。
var obj = new Date();
var s = JSON.stringify(obj);
var d = JSON.parse(s);
alert(obj); // Sat May 14 2016 17:33:04 GMT+0800
alert(s); // "2016-05-14T09:33:04.874Z" (stringify返回时包含2层"")
alert(d); // 2016-05-14T09:33:04.874Z
alert(typeof d); // string类型,无法还原为Date类型
对象方法
toStirng()
由于默认的toString()方法并不会输出很多有用的信息([object Object]),因此很多类都带有自定义的toString()。
例如,数组转换为字符串时,结果是一个数组元素列表;当函数转换为字符串时,得到函数的源代码。
toLocaleString()
这个方法返回本地化字符串。Object默认的toLocaleString()并不做任何本地化自身的操作,它仅调用toString()方法并返回对应值。
Date和Number类对toLocaleString()方法做了定制,可以用它对数字、日期和时间做本地化的转换。
toJSON()
Object.property实际上没有定义toJSON()方法,但对于需要序列化的对象来说,JSON.stringify()方法会调用toJSON()方法。
如果在待序列化的对象中存在这个方法,则调用它,返回值即是序列化的结果,而不是原始的对象。
valueOf()
当JavaScript需要将对象转换为某种原始值时调用,尤其是转换为数字的时候。