1、对象再认识
(1)对象属性和特性
什么是属性(Property),什么是特性(Attribute),这有什么区别?我不想也不会从语义学上去区分,对于这系列文章来说,属性就是组成对象的一个部分,广义上也包括对象的方法,而特性则是指被描述主体所具有的特征,换句话说,属性是我们可以通过编码来访问的具体存在,而特性则主要是为了便于理解概念的抽象存在,当然,特性也可以通过相应的属性来具体外化。这一小节所讲的对象属性的特性就是对对象属性特征的一个描述,主要来自于ECMA-262规范的第5版,该规范使用两个中括号的形式来描述不能直接访问的内部特性。
A、属性类型(先给属性分下类):
- 数据属性:直接访问属性值的属性
- 访问器属性:通过getter/setter方法来访问属性值的属性
- 内部属性:不能通过代码直接访问的属性,只是为了规范说明目的而存在,在规范中也使用两个中括号的形式来描述
B、对象内部属性
内部属性不能通过代码直接访问,它主要是为了描述规范,也是给ECMAScript实现者参考的,而对于开发者来说,了解这些可以便于理解一些内部机制。比如在给一个属性赋值时,在实现中会调用[[Put]]内部方法,而读取一个属性值时,则调用[[Get]]方法。
所有对象公共的内部属性 | 个别对象特有的内部属性 | |||
名称 | 规范 | 名称 | 规范 | 对象 |
[[Prototype]] | Object/Null | [[PrimitiveValue]] | primitive | Boolean|Date|Number|String |
[[Class]] | String | [[Construct]] | SpecOp(a List of any) → Object | new |
[[Extensible]] | Boolean | [[Call]] | SpecOp(any, a List of any) → any|Reference | call |
[[Get]] | SpecOp (propName) →any | [[HasInstance]] | SpecOp(any) → Boolean | Function |
[[GetOwnProperty]] | SpecOp (propName) →Undefined|Property Descriptor | [[Scope]] | Lexical Environment | Function |
[[GetProperty]] | SpecOp (propName) →Undefined|Property Descriptor | [[FormalParameters]] | List of Strings | Function |
[[Put]] | SpecOp (propName, any, Boolean) | [ ] | ECMAScript code | Function |
[[CanPut]] | SpecOp (propName) → Boolean | [[TargetFunction]] | Object | Function.prototype.bind |
[[HasProperty]] | SpecOp (propName) → Boolean | [[BoundThis]] | any | Function.prototype.bind |
[[Delete]] | SpecOp (propName, Boolean) → Boolean | [[BoundArguments]] | List of any | Function.prototype.bind |
[[DefaultValue]] | SpecOp (Hint) → primitive | [[Match]] | SpecOp(String, index) → MatchResult | RegExp |
[[DefineOwnProperty]] | SpecOp (propName, PropDesc, Boolean) → Boolean | [[ParameterMap]] | Object |
说明:
- 每一个对象都有一个原型对象[[Prototype]],一般我们不能在代码中直接访问这个内部属性,但可以通过Object.getPrototypeOf(object)来获取原型对象(在Firefox中可以通过__proto__来直接访问)。
- 在Object.prototype.toString方法中,按照规范内建对象会返回包含[[Class]]的值“[object class]”,而内建对象的[[Class]]值就是相应的名称(比如Array对象的[[Class]]值为'Array'),因此可以通过Object.prototype.toString.call(value) == '[object Array]'来判断value是否是一个Array。
- 给一个属性赋值时,后台调用[[Put]]来实现,获取一个属性值时,后台调用[[Get]]来获取。
- 使用new操作符调用一个函数时,后台调用[[Construct]],而使用call方法来调用函数时,后台会调用[[Call]]。
- [[HasInstance]]方法返回给定参数是否是通过调用函数来创建的,和Object的方法isPrototypeOf(obj)类似。
- 当一个函数被执行时,就会创建一个[[Scope]]对象,可以理解为[[Scope]]就是我们前面所说的活动对象,也就是说this、arguments、形参、函数内部定义的变量和函数都是的[[Scope]]对象的属性。
C、属性特性(用来描述属性的特性)
内部特性 | 配置属性 | 属性类型 | 数据类型 | 默认值 | 含义 | 备注 |
[[Configurable]] | configurable | 数据属性 访问器属性 | Boolean | true | 能否通过delete删除属性从而重新定义属性 能否修改属性的特性 能否把属性修改为访问器特性 | 一旦把属性定义为不可配置的,就不能再变为可配置的 如果为false,不能做删除、也不能修改属性特性,但是允许修改属性值 非严格模式下会忽略相应操作,严格模式下则抛出异常 |
[[Enumerable]] | enumerable | 数据属性 访问器属性 | Boolean | true | 能否通过for-in循环返回属性 | 为true时可以通过for-in来枚举,否则不能通过for-in枚举 |
[[Writable]] | writable | 数据属性 | Boolean | true | 能否修改属性的值 | 为false时不能修改属性值,非严格模式下会忽略相应操作,严格模式下则抛出异常 |
[[Value]] | value | 数据属性 | 任意类型 | undefined | 属性值 | |
[[Get]] | get | 访问器属性 | Undefined/Function | undefined | 读取属性时调用的函数 | 为一个函数时,会无参数调用这个函数,并将返回值作为属性值返回 |
[[Set]] | set | 访问器属性 | Undefined/Function | undefined | 写入属性时调用的函数 | 为一个函数时,会将传入的值作为参数调用这个函数,赋给属性 |
说明:
- 配置属性是指使用下面要讲的属性定义方法时用以定义相关特性的配置项名称。
- 对于访问器属性,[[Get]]、[[Set]]不一定都有,没有[[Get]]的属性不能读(返回undefined,严格模式下抛出异常),没有[[Set]]的属性不能写(会忽略,严格模式下抛出异常)。
- 注意区分对象内部属性和对象属性的特性。
D、属性定义方法(用来定义属性的方法)
最常见的定义属性的方法就是直接在对象上添加属性,比如obj.name = 'linjisong',这种情况下定义的属性所具有的内部特性都是默认的,如果想定义一个值不能被修改的属性要怎么做呢?在ES中给我们提供了几个方法用于实现类似的功能。
方法名 | 功能说明 | 参数和返回值 | 说明 | 调用示例 |
defineProperty() | 定义一个属性 | (1)目标对象 (2)属性的名字 (3)属性描述符对象 | 使用属性定义方法时 [[Enumerable]] [[Configurable]] [[Writable]] 默认值为false | // 创建一个包含一个默认属性job的对象(job属性可以修改、删除、在for-in中枚举) var person = {job:'it'}; // 添加一个不能被修改、删除的name属性 Object.defineProperty(person, 'name', { value:'linjisong',//这里的配置属性和上面特性列表中的配置属性一致 enumerable:true }); // 定义多个属性(数据属性year和访问器属性age) Object.defineProperties(person, { year:{ value : 2012, configurable:true, writable:true }, age:{ get : function(){ return this.year-1983; } } }); var job = Object.getOwnPropertyDescriptor(person, 'job'); console.info(job.configurable);//true,直接添加属性时默认为true var name = Object.getOwnPropertyDescriptor(person, 'name'); console.info(name.configurable);//false,使用属性定义方法添加属性时默认为false console.info(person.name);//linjisong person.name = 'oulinhai';//由于不能修改,所以值不会改变,在严格模式下会抛出异常 console.info(person.name);//linjisong person.year = 2015; console.info(person.year);//2015 console.info(person.age);//32,在修改year的同时,也修改了age属性 |
defineProperties() | 定义一组属性 | (1)目标对象 (2)多个属性描述符组成的一个对象 | ||
getOwnPropertyDescriptor() | 获取属性的特性 | (1)目标对象 (2)属性的名字 (3)返回一个包括了属性特性的对象 |
注:这些方法设置或获取的属性特殊和属性的类型有关,比如数据属性只能设置[[Confirurable]]、[[Enumerable]]、[[Writable]]、[[Value]]。
(2)防篡改对象
所谓防篡改对象,就是给对象一定级别的保护以防止在这个级别上对对象的变更,在ES5规范中,定义了依次升高的三种保护级别:
保护级别 | 描述 | 操作方法 | 判断方法 | 说明 |
不可扩展 | 不能给对象添加新属性和方法,但可以修改已有属性和方法 | preventExtensions() | isExtensible():不能扩展时返回false | |
密封 | 不可扩展,并且已有成员的[[Configurable]]设置为false,不能删除属性,但可以修改属性值 | seal() | isSeal():被密封时返回true | isSeal()为true时一定有isExtensible()为false |
冻结 | 密封,其[[Writable]]设置为false,但如果定义了[[Set]],访问器属性仍然可写 | freeze() | isFrozen():被冻结时返回true | isFrozen()为true时一定有isSeal()为true,isExtensible()为false |
注:一旦定义成了防篡改对象,就不能撤销。
(3)对象的其它方法
名称 | 描述 |
create(prototype[,descriptors]) | 创建一个具有指定原型且可选择性地包含指定属性的对象 |
getOwnPropertyNames(object) | 返回对象的属性(方法)的名称 |
getPrototypeOf(object) | 返回对象的原型 |
keys(object) | 返回对象的可枚举属性(方法)的名称 |
这里的create(prototype[,descriptors])是一个非常有意思的方法,规范中这样描述它的行为:
[code]
①如果prototype不是Null或Object,抛出TypeError异常
②var obj = new Object()
③设置obj的内部属性[[Prototype]]为prototype
④如果descriptors存在且不为undefined,使用Object.defineProperties(obj,descriptors)来添加属性
⑤返回obj
JavaScript高级程序设计(第3版)学习笔记10 再访js对象
声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Reply on: @reply_date@
@reply_contents@