在ES5规范中。另一个比較重要的改进,就是Object对象的增强。ES5为Object新增了一系列函数。用于编写安全健壮的程序,今天我们就来一一介绍它们的用法。
以下就是ES5中Object新增的函数:
Object.defineProperty(object, propertyName, descriptor);
Object.defineProperties(object, descriptors);
Object.getOwnPropertyDescriptor(object, propertyName);
Object.create(prototype, descriptors);
Object.getPrototypeOf(object);
Object.keys(object);
Object.getOwnPropertyNames(object);
Object.preventExtensions(object);
Object.isExtensible(object);
Object.seal(object);
Object.isSealed(object);
Object.freeze(object);
Object.isFrozen(object);
以下我们逐一介绍:
Object.defineProperty(object, propertyName, descriptor);
defineProperty函数用于定义一个对象上的属性以及这个属性的描写叙述符。这里涉及到描写叙述符这个概念,须要先理解一下,描写叙述符是用来描写叙述一个属性的值和这个属性在执行期的訪问控制。一个描写叙述符包括以下几个声明:
configurable: 表示能否通过delete删除对象中的该属性。以及能否又一次定义该属性。默认是false,默认情况下,不能用delete删除属性,不能又一次定义该属性。须要注意的是,一旦明白设置configurable为false之后,就再也不能又一次设置这个规则为true了。
enumerable: 表示是否能通过for-in循环获得对象中的该属性,默认值是false。默认情况下,使用for-in循环将不能看到该属性出现。
writable: 表示是否能改动该属性,默认值是false。默认情况下。不能再更改该属性的值。
value: 该属性的值,默认值是undefined。通常我们会设置value为一个有意义的值。
以下是每一个声明的默认值列表:(Get和Set兴许会介绍)
这里须要注意。当configurable和writable为false的情况下,试图删除或更改相相应的属性。常规模式下操作将会被忽略,假设是严格模式。将会抛出异常。
我们的代码会加上"use strict";来启用严格模式。关于严格模式的细节,我们兴许的文章会专门介绍。
我们先来使用defineProperty函数定义一个对象的属性:
"use strict";var person = {};Object.defineProperty(person, 'name', { value: 'Scott'});person.name = 'John';
上面的代码我们使用defineProperty为person对象定义了一个name属性。然后试图更改它的值。
假设在常规模式下执行。更改操作将会被忽略,这里我们加上了严格模式的声明,将会抛出以下的异常:
假设通过delete试图删除name属性。相同也会得到一个异常结果:
delete person.name;
我们略微修改一下代码,为defineProperty函数的最后一个參数加入一个writable声明:
"use strict";var person = {};Object.defineProperty(person, 'name', { writable: true, //add writable as true value: 'Scott'});person.name = 'John';console.log('after changing the name: ', person.name);delete person.name;console.log('after deleting the name: ', person.name);
加入writable为true后,再次执行程序,看看结果怎样:
我们如今能够更改name属性的值了。仅仅只是试图删除name时,仍然会抛出一个异常,如今我们须要再加入一个configurable声明:
"use strict";var person = {};Object.defineProperty(person, 'name', { configurable: true, //add configurable as true writable: true, //add writable as true value: 'Scott'});person.name = 'John';console.log('after changing the name: ', person.name);delete person.name;console.log('after deleting the name: ', person.name);
从打印结果来看。更改操作和删除操作在严格模式下都顺利运行了。如今大家应该都了解到configurable和writable的作用了吧。
configurable同意或禁止删除操作的运行。writable同意或禁止更改操作的运行。
另外。上面我们也提到过。假设明白声明了configurable为false。则不能再使用defineProperty将其定义configurable为true了,以下代码将会抛出一个异常:
'use strict';var person = {};Object.defineProperty(person, 'name', { configurable: false, //declare configurable as false writable: false, //declare writable as false value: 'Scott'});//try to redefine the name's descriptorObject.defineProperty(person, 'name', { configurable: true, writable: true, value: 'Scott'});
当外部代码试图更改对象属性时。适当的使用configurable和writable为其加一些限制,能够提高代码的安全性。这一点对模块开发很实用。
以下来介绍一下enumerable声明,我们用一个简单的演示样例来解说:
"use strict";var person = {};Object.defineProperty(person, 'name', { value: 'Scott'});//nothing will be loggedfor (var key in person) { if (person.hasOwnProperty(key)) { console.log(key + ': ' + person[key]); }}上面这段代码我们不会看到person中的键值对,由于默认情况下使用defineProperty定义属性时,属性的enumerable为false。即不可被遍历,假设须要在for-in中获取到name属性,我们须要为其声明enumerable为true:
"use strict";var person = {};Object.defineProperty(person, 'name', { enumerable: true, //add enumerable as true value: 'Scott'});//name: Scottfor (var key in person) { if (person.hasOwnProperty(key)) { console.log(key + ': ' + person[key]); }}
检測一个对象的属性是否可遍历。我们还能够使用Object的另外一个原型方法,它更为方便:
var isNameEnumerable = person.propertyIsEnumerable('name');除了上面这些之外,在defineProperty函数的descriptor中还能够使用setter和getter对属性进行定义:
var getPerson = function() { var person = {}; var personAge = 20; Object.defineProperty(person, 'age', { get: function() { return personAge; }, set: function(newAge) { if (newAge < 0) { newAge = 0; } if (newAge > 150) { newAge = 150 } personAge = newAge; } }); return person;}var person = getPerson();person.age = -1;console.log(person.age); //0person.age = 200;console.log(person.age); //150
上面代码中我们把创建person对象的操作封装在getPerson函数中。然后使用set方法和get方法定义age属性的行为。在set和get方法中,间接使用了局部变量personAge来表示person的age属性,这样的方式的优点在于,能够对赋值操作加一些验证,使其符合我们业务逻辑的要求。只是须要注意的是,不是一定非要同一时候指定setter和getter的。在仅仅定义getter时该属性不能写。仅仅指定setter时不能读。假设仅仅有getter而尝试去写、仅仅有setter尝试去读的话,严格模式下会抛出异常,所以正确地使用setter和getter才干写出高质量的代码。
Object.defineProperties(object, descriptors);在了解上面介绍的defineProperty函数之后,对于这个函数就比較easy理解了,defineProperties函数用于一次性定义多个属性,我们用一段代码来解释:
var person = {};Object.defineProperties(person, { 'name': { writable: false, value: 'Scott' }, 'address': { writable: true, value: 'Beijing' }});
代码中我们最后一个參数包括两个属性:name和address,分别都有自己的属性描写叙述信息。这样的方式比較单一属性的声明来说简单明了,在一次性有多个属性须要声明时比較有用。
Object.getOwnPropertyDescriptor(object, propertyName);这个函数用于获取指定属性的描写叙述符。当中会包含上面提到的一些描写叙述符声明。我们直接看以下演示样例代码:var person = {};Object.defineProperty(person, 'name', { configurable: true, writable: false, enumerable: true, value: 'Scott'});var descriptor = Object.getOwnPropertyDescriptor(person, 'name');console.log(descriptor.configurable); //trueconsole.log(descriptor.writable); //falseconsole.log(descriptor.enumerable); //trueconsole.log(descriptor.value); //Scott
须要注意的是。假设定义属性时使用了set和get方法,那么返回的descriptor对象也是能够通过set和get訪问到对应的setter和getter的。
接着我们来了解一下与原型有关的两个函数:
Object.create(prototype, descriptors);
create函数用于在指定原型基础之上创建一个新的对象,第一个參数即指定的原型对象,第二个參数就是我们上面介绍到的描写叙述符,里面能够定义多个属性的描写叙述信息。
原型參数仅仅能是一个对象或者指定为null,以下三种方式都能够创建一个空对象:
var obj0 = Object.create({});var obj1 = Object.create(null);var obj2 = Object.create(Object.prototype);
我们当然还能够指定一个包括多个属性描写叙述信息的描写叙述符參数。来创建一个新的对象。在原来对象上进行扩展。
以下这个样例我们在一个已有对象的基础之上创建一个person对象,包括name属性和gender属性,当中name可更改不可删除。gender是仅仅读状态:
var human = { info: 'human being'};var person = Object.create(human, { name: { configurable: false, writable: true, enumerable: true, value: 'Scott' }, gender: { get: function() { return "Male"; } }});console.log(person);上面代码相当于在human对象上进行扩展。加入了name和gender属性。进而创建一个新对象,我们来看一下person对象的结构:
从信息打印中,我们能够看出,由于name属性是可遍历的,所以显示在Object{}中,当我们展开后。会看到gender的get方法,也能够看得到person的原型链,包括info属性的原型就是我们上面定义的human对象了,证明person对象确实是human对象的扩展。
create与defineProperties有些相象之处,不同的是。create会创建一个新的对象,而defineProperties不会,我们也能够获取它的返回值。返回值就是原对象本身。
Object.getPrototypeOf(object);此函数用于获取指定对象的原型。来看以下这个精简过的样例:
var human = { info: 'human being'};var person = Object.create(human, { name: { value: 'Scott' }});console.log(Object.getPrototypeOf(person) === human); //true我们使用human作为原型创建一个person对象,然后使用getPrototypeOf函数获取person对象的原型。结果会返回human。我们上面也介绍到了,person对象的确是从human对象扩展而来。getPrototypeOf获取到的原型和我们期望的是一致的。 Object.keys(object);
此函数用于获取对象中可被遍历的所有属性的key的集合。
看以下样例:
var array = ['a', 'b', 'c'];console.log(Object.keys(array)); //["0", "1", "2"]var person = Object.defineProperties({}, { name: { enumerable: true, value: 'Scott' }, info: { enumerable: true, value: 'I am Scott' }, address: { enumerable: false, //not enumerable value: 'Beijing' }});console.log(Object.keys(person)); //["name", "info"]首先我们创建一个数组。使用keys函数获取全部的key,结果会打印出数组的下标组成的集合。然后我们定义了person对象的属性,当中name和info都是可遍历的,address是不可遍历的,结果会打印出name和info,而不会出现address,使用keys函数的时候须要注意这一点。
Object.getOwnPropertyNames(object);
此函数与keys函数相似,只是它会返回全部的键,包含哪些不可被遍历的属性。如今我们把keys函数替换成getOwnPropertyNames,看看结果怎样:
var array = ['a', 'b', 'c'];console.log(Object.getOwnPropertyNames(array));var person = Object.defineProperties({}, { name: { enumerable: true, value: 'Scott' }, info: { enumerable: true, value: 'I am Scott' }, address: { enumerable: false, //not enumerable value: 'Beijing' }});console.log(Object.getOwnPropertyNames(person));结果打印例如以下: 我们会发现,array里面添加了一个length的key,而person对象也添加了一个address的key,我们能够得知。数组对象的length被设置为不可遍历的,也证明了getOwnPropertyNames确实能够把全部的属性的key获取到。
Object.preventExtensions(object); & Object.isExtensible(object);
preventExtensions函数用于禁止指定对象的扩展,即禁止向对象中加入新的属性。我们定义一个简单的对象。然后測试一下这个函数:
'use strict';var person = { name: 'Scott'};Object.preventExtensions(person);console.log(Object.isExtensible(person)); //false//Uncaught TypeError: Can't add property age, object is not extensibleperson.age = 20;測试证明,试图加入一个age属性,严格模式下将会抛出异常,证明person对象已经是不可扩展的。
Object.seal(object); & Object.isSealed(object);
seal函数用于对指定对象进行封存,已封存的对象禁止再加入新的属性。禁止删除已有的属性,禁止又一次定义已有属性的cnofigurable为true。来看以下一段代码:
'use strict';var person = { name: 'Scott'};Object.seal(person);console.log(Object.isSealed(person)); //trueperson.name = 'John';//Uncaught TypeError: Can't add property age, object is not extensible//person.age = 20;//Uncaught TypeError: Cannot delete property 'name' of #
上面我们定义了一个普通字面量的person对象,它的name属性是可写可删除可遍历的,接着我们调用seal函数对其进行封存。假设我们想要推断一个对象是否已被封存,能够使用isSealed函数。被封存的对象禁止加入属性和删除已有的属性。所以我们试图加入一个age属性和删除已有的name属性都会在严格模式下抛出异常。须要注意的是,仅仅要是可写的属性。即使被封存也能够更改其值。所以更改name的值是不会有问题的。
另外。对已被封存对象的属性又一次定义也要特别小心,由于被封存的对像使用defineProperty又一次定义规则时,仅仅能更改writable和value。不能更改原来的enumerable,对于configurable更为严苛,不能明白声明。例如以下代码列举了一些注意事项:
'use strict';var person = { name: 'Scott'};Object.seal(person);person.name = 'Jack';console.log(person.name); //Jack//after sealed, can't declare the configurable, and can't change the original enumerable.Object.defineProperty(person, 'name', { //configurable: true, writable: false, enumerable: true, //the enumerable here must be equal to the original if declare explicitly value: 'John'});//Uncaught TypeError: Cannot assign to read only property 'name' of object '#能够看出writable是能够更改为false的,但注意enumerable仅仅能和原来对象一样为true,而configurable在这里不能明白声明,大家能够亲自測试一下。
Object.freeze(object); & Object.isFrozen(object);
freeze函数用于对指定对象进行冻结,冻结后的对象禁止加入属性,禁止删除属性,禁止更改属性。禁止使用defineProperty更改其configurable。writable,和enumerable的值。能够说freeze函数禁止对象属性上的全部操作,只是须要注意的是,假设对象的属性也是对象类型,那么这个属性对象以下的属性是不会被冻结的,我们依然能够操作:
'use strict';var person = { name: 'Scott', info: { weight: 65, height: 175 }};Object.freeze(person);console.log(Object.isFrozen(person)); //trueperson.info.age = 20; //add 'age' property to infoperson.info.weight = 70; //change the 'weight' property
上面代码中,我们向info中加入age属性,或者更改weight的值,这些操作都是没有问题的,所以假设须要对一个对象及以下的属性进行全然的冻结。我们须要递归的对每个属性对象进行冻结。
以上就是ES5对Object的增强,有了这些强大的API,能够确保我们的代码更加安全,进而能够构建出健壮的应用。另外,在上面的演示样例中,我们多次提到了严格模式,这也是ES5规范中重要的一个方面,下次我们会对严格模式进行一个全面的概括。