对象是JavaScript的基本数据类型,它是一种复合值,将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。对象也可以看做是属性的无序集合,每个属性都是一个名/值对。值可以是任意JavaScript值,或者可以是一个get或set函数。每个属性还有一些与之相关的值,称为“属性特性”:
· 可写(writable attribute),表明是否可以设置该属性的值。
· 可枚举(enumerable attribute),表明是否可以通过for/in循环返回该属性。
· 可配置(configurable attribute),表明是否可以删除或修改该属性。
除了包含属性之外,每个对象还拥有三个相关的对象特性:
· 对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象。
· 对象的类(class)是一个标识对象类型的字符串
· 对象的扩展标记(extensible flag)指明了是否可以对该对象添加新属性
最后,用下面这些术语对三类JavaScript对象和两类属性作区分:
· 内置对象(native object) 是由ECMAScript规范定义的对象或类。例如,数组、函数、日期和正则表达式都是内置对象。
· 宿主对象(host object)是由JavaScript解释器所嵌入的宿主环境(比如web浏览器)定义的。客户端JavaScript中表示网页结构的HTMLElement对象均是宿主对象。既然宿主环境定义的方法可以当成普通的JavaScript函数对象,那么宿主对象也可以当成内置对象。
· 自定义对象(user-defined object) 是由运行中的JavaScript代码创建的对象。
· 自有属性(own property) 是直接在对象中定义的属性。
· 继承属性(inherited property) 是在对象的原型对象中定义的属性。
一、创建对象
1、对象直接量
创建对象最简单的方式就是在JavaScript代码中使用对象直接量:
var point = {x: 1, y: 0};
对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象,每次计算对象直接量的时候,也都会计算它的每个属性的值。
2、通过new创建对象
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
return this.name;
};
}
var person = new Person("tom", 21, "WEB");
使用关键字new创建新实例对象经过了以下几步:
1) 创建一个新对象,如:var person = {};
2) 新对象的_proto_属性指向构造函数的原型对象。
3) 将构造函数的作用域赋值给新对象。(也就是this对象指向新对象)
4) 执行构造函数内部的代码,将属性添加给person中的this对象。
5) 返回新对象person。
3、Object.create()
Object.create()是一个静态函数,而不是提供给某个对象调用的方法。第一个参数是这个对象的原型,第二个是可选参数,用以对对象的属性进行进一步描述,下面会详细讲述。
可以通过传入参数null来创建一个没有原型的对象,但通过这种方式创建的对象不会继承任何东西,甚至不包括基础方法,比如toString(),也就是说,它将不能和“+”运算符一起正常工作。
var o1 = Object.create({x: 1, y: 2}); // o1继承了属性x和y
var o2 = Object.create(null); // o2不继承任何属性和方法
如果想创建一个普通的空对象(比如通过new Object()或{}创建的对象),需要传入Object.prototype:
var o3 = Object.create(Object.prototype); // o3和new Object()和{}一样
二、属性的查询和设置
通过点(.)或方括号([])来获取属性的值。对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符,对于方括号([])来说,方括号内必须是一个以计算结果为字符串的表达式,这个字符串就是属性的名字。
一种简练的获取subtitle的length属性或undefined:
var len = book && book.subtitle && book.subtitle.length;
三、删除属性
delete运算符可以删除对象的属性,但它只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
var a = {p: {x: 1}};
var b = a.p;
delete a.p;
console.log(b.x); //1
由于已经删除的属性的引用依然存在,因此在JavaScript的某些实现中,可能因为这种不严谨的代码而造成内存泄漏。
delete运算符只能删除自有属性,不能删除继承属性,不能删除可配置性为false的属性,比如通过变量声明和函数声明创建的全局对象的属性。
四、检测属性
可以通过in运算符,hasOwnProperty()和propertyIsEnumerable()方法来进行检测
in运算符左侧是属性名(字符串),右侧是对象,如果对象的自有属性或继承属性中包含这个属性则返回true。
对象的hasOwnProperty()方法来检测给定的名字是否是对象的自有属性,对于继承属性它将返回false。
var o = {x: 1};
o.hasOwnProperty("x"); //true
o.hasOwnProperty("y"); //false
o.hasOwnProperty("toString"); //false ,toString是继承属性
"toString" in o; //true
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测是自有属性且这个属性的可枚举性(enumerable attribute)为true时它才返回true。
var o = {x: 1};
o.propertyIsEnumerable("x"); //true
o.propertyIsEnumerable("toString"); //false:不可枚举
五、枚举属性
1、for/in循环
for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性)。
2、Object.keys()
它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。
var o = {x: 1, y: 2, z: 3};
console.log(Object.keys(o)); //["x", "y", "z"]
3、Object.getOwnPropertyNames()
它和Object.keys()类似,只是它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。
六、属性getter和setter
属性值可以用一个或两个方法替代,这两个方法就是getter和setter(ie除外)。由getter和setter定义的属性称作“存取器属性”(accessor property),它不同于“数据属性”(data property),数据属性只有一个简单的值。
当程序查询存取器属性的值时,JavaScript调用get方法(无参数),这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时,JavaScript调用set方法,将赋值表达式右侧的值当作参数传入set,从某种意义上讲,这个方法负责“设置”属性值,可以忽略set方法的返回值。
和数据属性不同,存取器属性不具有可写性。如果属性同时具有get和set方法,那么它是一个可读/写属性。如果它只有get方法,那么它是一个只读属性。如果它只有set方法,那么它是一个只写属性(数据属性中有一些例外),读取只写属性总是返回undefined。
存取器属性定义为一个或两个和属性同名的函数,这个函数定义没有使用function关键字,而是使用get和set。注意:这里没有使用冒号将属性名和函数体分开,但在函数体的结束和下一个方法或数据属性之间有逗号分隔。
定义存取器属性最简单的方法是使用对象直接量语法的一种扩展语法:
var o = {
data_prop: value,
get access_prop() { /*这里是函数体*/ },
set access_prop(value) { /*这里是函数体*/ }
}
简单应用:只能检测属性的写入值以及在每次属性读取时返回不同的值:
//这个对象产生严格自增的序列号
var serialnum = {
//这个数据属性包含下一个序列号
//$符号暗示这个属性是一个私有属性
$n : 0 ,
//返回当前值,然后自增
get next(){ return this.$n++ },
set next(n){
if(n >= this.$n) this.$n = n;
else throw "序列号的直不能比当前值小";
}
};
serialnum.$n; //0
serialnum.next; //0
serialnum.$n; //1
serialnum.next; //1
serialnum.next = 0; //Uncaught 序列号的直不能比当前值小
serialnum.next = 10;
serialnum.$n; //10
七、属性的特性
一个属性包含一个名字和4个特性,数据属性的4个特性分别是它的值(value)、可写性(writable)、可枚举性(enumerable)和可配置性(configurable)。存取器属性的4个特性是读取(get)、写入(set)、可枚举性和可配置性。
通过调用Object.getOwnPropertyDescriptor()可以获得某个对象特定属性的属性描述符,但只能得到自有属性的描述符:
Object.getOwnPropertyDescriptor({x: 1}, "x");
//返回{value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor({}, "toString");
//对于继承属性和不存在的属性返回undefined
要想设置属性的特性,或者想让新建属性具有某种特性,则需要调用Object.defineProperty(),传入要修改的对象、要创建或修改的属性的名称以及属性描述符对象(注意:该方法不能修改继承属性):
var o = {};
Object.defineProperty(o, "x", {
value: 1,
writable: true,
enumerable: false,
configurable: true
});
//属性是存在的,但不可枚举
o.x; // 1
Object.keys(o); //[]
Object.defineProperty(o, "x", {writable: false}); //修改x的属性变为只读
o.x = 2; //操作失败但不报错
如果要同时修改或创建多个属性,使用Object.defineProperties():
var p = Object.defineProperties({},{
x: {value: 1, writable: true, enumerable: false, configurable: true},
y: {value: 1, writable: true, enumerable: false, configurable: true},
r: {
get: function(){return Math.sqrt(this.x * this.x + this.y * this.y)},
enumerable: true,
configurable: true
}
});
可写性控制着对值特性的修改,可配置性控制着对其他特性(包括属性是否可以被删除)的修改。
下面是完整的规则,任何对Object.defineProperty()或Object.defineProperties()违反规则的使用都会抛出类型错误异常:
1.如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性
2.如果属性是不可配置的,则不能修改它的可配置性和可枚举性。
3.如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转换为数据属性
4.如果数据属性是不可配置的,则不能将它转换为存取器属性
5.如果数据属性是不可配置的,则不能将它的可写性从false修改为true,但可以从true修改为false。
6.如果数据属性是不可配置的且不可写的,则不能修改它的值。然而可配置但不可写属性的值是可以修改的(实际上是先将它标记为可写的,然后修改它的值,最后转换为不可写的)
八、对象的三个属性
1、原型属性
在ES5中,将对象作为参数传入Object.getPrototypeOf()可以查询它的原型。
2、类属性
对象的类属性(class attribute)是一个字符串,用以表示对象的类型信息。ES3和ES5都未提供设置这个属性的方法,并只有一种间接的方法可以查询它。默认的toString()方法(继承自Object.prototype)返回了如下这种格式的字符串:
[object,class]
var o = {x: 1};
Object.prototype.toString.call(o); //"[object Object]"
因此,可以用这个方法来检测是否为数组:
var arr = [];
Object.prototype.toString.call(arr).slice(8,-1); //"Array"
3、可扩展性
对象的可扩展性用以表示是否可以给对象添加新属性。
ES5定义了用来查询和设置对象可扩展性的函数。通过将对象传入Object.isExtensible(),来判断该对象是否是可扩展的。
如果想将对象转换为不可扩展的,需要调用Object.preventExtensions(),将待转换的对象作为参数传进去。
注意:一旦将对象转换为不可扩展的,就无法再将其转换回可扩展的了。
同样需要注意的是,preventExtensions()只影响到对象本身的可扩展性。如果给一个不可扩展的对象的原型添加属性,这个不可扩展的对象同样会继承这些新属性。
Object.seal()和Object.preventExtensions()类似,除了能够将对象设置为不可扩展的,还可以将对象的所有自有属性都设置为不可配置的。
也就是说,不能给这个对象添加新属性,而且它已有的属性也不能删除或配置,不过它已有的可写属性依然可以设置。
对于那些已经封闭(sealed)起来的对象是不能解封的。可以使用Object.isSealed()来检测对象是否封闭。
Object.freeze()将更严格地锁定对象----"冻结"。除了将对象设置为不可扩展的和将其属性设置为不可配置的之外,还可以将它自有的所有数据属性设置为只读(如果对象的存取器属性具有setter方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)。使用Object.isFrozen()来检测对象是否冻结。
九、序列化对象
对象序列化是指将对象的状态转换为字符串,也可将字符串还原为对象。JSON.stringify()和JSON.parse()用来序列化和还原JS对象。
o = {x:1,y:{z:[false,null,""]}}; //定义一个测试对象
s=JSON.stringify(o); //s是 '{"x":1,"y":{"z":[false,null,""]}}'
p=JSON.parse(s); //p是o的深拷贝
JSON的语法是JS语法的子集,它并不能表示JS里的所有值。支持对象、数组、字符串、无穷大数字、true、false或null,并且它们可以序列化和还原。
NaN、Infinity和-Infinity序列化的结果是null,
日期对象序列化结果是ISO格式的日期字符串,但JSON.parse()依然保留它们的字符串形态,而不会将它们还原为原始日期对象。
函数、RegExp、Error对象和undefined值不能序列化和还原。
JSON.stringify()只能序列化对象可枚举的自有属性。对于一个不能序列化的属性来说,在序列化后的输出字符串中会将整个属性省略掉。
JSON.stringify()和JSON.parse()都可以接收第二个可选参数,通过传入需要序列化或还原的属性列表来定制自定义的序列化或还原操作。
十、valueOf()方法
valueOf()和toString()非常类似,但往往当JavaScript需要将对象转换为某种原始值而非字符串的时候才会调用它,尤其是转换为数字的时候。请看
https://blog.csdn.net/ww_lxr/article/details/80274435类型转换部分。
文章参考来源:《JavaScript 权威指南》(原书第六版) 作者 弗兰纳根