在JavaScript中,属性决定了一个对象的状态,本文详细的研究了它们是如何工作的.

属性类型

JavaScript中有三种不同类型的属性:命名数据属性(named data properties),命名访问器属性(named accessor properties)以及内部属性(internal properties).

命名数据属性

这种属性就是我们通常所用的"普通"属性,它用来将一个字符串名称映射到某个值上.比如,下面的对象obj有一个名为字符串"prop"的数据属性,该属性的值为数字123.

var obj = {
prop: 123
};

你可以获取(读取)到一个属性的值:

console.log(obj.prop); //
console.log(obj["prop"]); //

你还可以设置(写入)一个属性的值:

obj.prop = "abc";
obj["prop"] = "abc";

命名访问器属性

另外,还可以借助函数来获取或设置一个属性的值.这些函数称之为访问器函数(accessor function).控制属性读取的访问器函数称之为getter.控制属性写入的访问器函数称之为setter.

var obj = {
get prop() {
return "Getter";
},
set prop(value) {
console.log("Setter: "+value);
}
}

让我们操作一下obj的属性:


> obj.prop
'Getter'
> obj.prop = 123;
Setter: 123

内部属性

有一些属性仅仅是为规范所用的,称之为内部属性,因为它们无法通过JavaScript直接访问到,但是它们的确存在,并且影响着程序的表现.内部属性的名称比较特殊,它们都被两个中括号包围着.下面有两个例子:

  • 内部属性[[Prototype]]指向了所属对象的原型.该属性的值可以通过Object.getPrototypeOf()函数读取到.该属性的值只能在创建一个新对象的时候通过Object.create()或者__proto__来设置 [1].
  • 内部属性[[Extensible]]决定了是否能给所属对象添加新的属性.该属性的值可以通过Object.isExtensible()读取到.还可以通过Object.preventExtensions()将该属性的值设置为false.一旦设置为false,就无法再设置回true了.

属性特性

一个属性的所有状态,包括它的数据和元数据,都存储在该属性的特性(attributes)中.属性拥有自己的特性,就像对象拥有自己的属性一样.特性的名称经常写成类似内部属性的形式(双中括号).

下面是命名数据属性拥有的特性:

  • [[Value]] 存储着属性的值,也就是属性的数据.
  • [[Writable]] 存储着一个布尔值,表明该属性的值是否可以改变.

下面是命名访问器属性拥有的特性:

  • [[Get]] 存储着getter,也就是在读取这个属性时调用的函数.该函数返回的值也就是这个属性的值.
  • [[Set]] 存储着setter,也就是在为这个属性赋值时调用的函数.该函数在调用时会被传入一个参数,参数的值为所赋的那个新值.

下面是两种类型的属性都有的特性:

  • [[Enumerable]] 存储着一个布尔值.可以让一个属性不能被枚举,在某些操作下隐藏掉自己(下面会有详细讲解).
  • [[Configurable]] 存储着一个布尔值.如果为false,则你不能删除这个属性,不能改变这个属性的大部分特性(除了[[Value]]),不能将一个数据属性重定义成访问器属性,或者反之.换句话说就是:[[Configurable]]控制了一个属性的元数据的可写性.

默认值

如果你不明确的指定某个特性的值,则它们会被赋一个默认值:

特性名称 默认值
[[Value]]   undefined
[[Get]] undefined
[[Set]] undefined
[[Writable]] false
[[Enumerable]] false
[[Configurable]] false

这些默认值对于属性描述符尤其重要.

属性描述符

属性描述符(property descriptor)可以将一个属性的所有特性编码成一个对象并返回.该对象的每个属性都对应着所属属性的一个特性.例如,下面是一个值为123的只读属性的属性描述符:

{
value: 123,
writable: false,
enumerable: true,
configurable: false
}

你也可以使用一个访问器属性来实现上面这个拥有只读特性的数据属性,其属性描述符如下:

{
get: function () { return 123 },
//没有set,也就是只读
enumerable: true,
configurable: false
}

使用属性描述符的函数

使用属性描述符的函数

在使用下面的函数时会用到属性描述符:

  • Object.defineProperty(obj, propName, propDesc)

  创建或改变对象obj的propName属性,propName属性的特性通过属性描述符propDesc给出.返回修改后的obj对象.例如:

var obj = Object.defineProperty({}, "foo", {
value: 123,
enumerable: true
// writable和configurable为默认值
});
  • Object.defineProperties(obj, propDescObj)

  Object.defineProperty()的批处理版本.对象propDescObj的每个属性都指定了要给原对象obj添加或修改的一个属性和对应的属性描述符.例如:

var obj = Object.definePropertys({}, {
foo: { value: 123, enumerable: true },
bar: { value: "abc", enumerable: true }
});
  • Object.create(proto, propDescObj?)

  首先,创建一个原型为proto的对象.然后,如果提供了可选参数propDescObj,则会按照Object.defineProperties添加属性的方式给这个新对象添加属性.最后,返回操作后的新对象.例如,下面的代码创建的对象和上面的Object.definePropertys例子创建的对象完全一样:

  

var obj = Object.create(Object.prototype, {
foo: { value: 123, enumerable: true },
bar: { value: "abc", enumerable: true }
});
  • Object.getOwnPropertyDescriptor(obj, propName)

  返回对象obj的名为propName的自身属性(非继承来的)的属性描述符.如果没有这个自身属性,则返回undefined.

> Object.getOwnPropertyDescriptor(Object.prototype, "toString")
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true } > Object.getOwnPropertyDescriptor({}, "toString")
undefined

可枚举性

本节会解释什么操作会受到属性的可枚举性的影响,什么操作不会.我们首先假设已经定义了如下这样的对象proto和obj:

var proto = Object.defineProperties({}, {
foo: { value: 1, enumerable: true },
bar: { value: 2, enumerable: false }
});
var obj = Object.create(proto, {
baz: { value: 1, enumerable: true },
qux: { value: 2, enumerable: false }
});

需要注意的是,所有对象(包括上面的proto)通常来说都至少有一个原型Object.prototype [2]:

> Object.getPrototypeOf({}) === Object.prototype
true

我们常用的内置方法比如toString和hasOwnPropertyare等实际上都是定义在Object.prototype身上的.

受可枚举性影响的操作

可枚举性只影响两种操作:for-in循环和Object.keys().

for-in循环会遍历到一个对象的所有可枚举属性的名称,包括继承来的属性:

> for (var x in obj) console.log(x);   //没有遍历到Object.prototype上不可枚举的属性qux
baz
foo

Object.keys()返回一个对象的所有可枚举的自身属(非继承的)的名称组成的数组:

> Object.keys(obj)
[ 'baz' ]

如果你想获取到所有的自身属性,则应该使用Object.getOwnPropertyNames().

不受可枚举性影响的操作

除了上面的两个操作,其他的操作都会忽略掉属性的可枚举性.一些读取操作会使用到继承来的属性:

> "toString" in obj
true
> obj.toString
[Function: toString]

还有一些操作只会考虑自身属性:

> Object.getOwnPropertyNames(obj)
[ 'baz', 'qux' ] > obj.hasOwnProperty("qux")
true
> obj.hasOwnProperty("toString")
false > Object.getOwnPropertyDescriptor(obj, "qux")
{ value: 2,
writable: false,
enumerable: false,
configurable: false }
> Object.getOwnPropertyDescriptor(obj, "toString")
undefined

创建,删除,定义属性的操作只会影响到自身属性:

obj.propName = value
obj["propName"] = value delete obj.propName
delete obj["propName"] Object.defineProperty(obj, propName, desc)
Object.defineProperties(obj, descObj)

最佳实践

一般的规则是:系统创建的属性是不可枚举的,用户创建的属性是可枚举的:

> Object.keys([])
[]
> Object.getOwnPropertyNames([])
[ 'length' ]
> Object.keys(['a'])
[ '0' ]

特别是针对原型对象上的方法来说:

> Object.keys(Object.prototype)
[]
> Object.getOwnPropertyNames(Object.prototype)
[ hasOwnProperty',
'valueOf',
'constructor',
'toLocaleString',
'isPrototypeOf',
'propertyIsEnumerable',
'toString' ]

因此,在你自己写的代码中,通常不应该给内置的原型对象添加属性,如果你必须要这么做,则应该把这个属性设置为不可枚举的,以防止影响到其他代码.

正如我们所看到的,不可枚举的好处是:能确保已有的代码中的for-in语句不受到从原型继承来的属性的影响.但是,不可枚举的属性只能够创建一种"for-in只会遍历一个对象的自身属性"这样的幻觉.在你的代码中,仍应该尽可能避免使用for-in[3].

如果你把对象当成是字符串到值的Map来使用的话,则你应该只操作自身属性且要忽略掉可枚举性.不过这种情况下还有很多其他陷阱需要考虑[4].

结论

在本文中,我们对属性的性质(称之为特性)进行了研究.需要注意的是,实际上JavaScript引擎并不是必须得通过特性来组织一个属性,它们主要是作为ECMAScript规范中定义的一个抽象操作.但有时候这些特性也会明确的出现在语言代码中,比如在属性描述符中.

更进一步的知识(2ality):

参考

  1. JavaScript: __proto__
  2. What object is not an instance of Object?
  3. Iterating over arrays and objects in JavaScript
  4. The pitfalls of using objects as maps in JavaScript
 <注> 本文转自 紫云飞 的文章[译]JavaScript中对象的属性

JavaScript中对象的属性的更多相关文章

  1. JavaScript中对象的属性类型

    JavaScript中,对象的属性有两种:数据属性和访问器属性. 数据属性 特性: 数据属性包括一个数据值的位置.在这个位置可以读取和写入值.数据属性有4个特性. [[configurable]]:可 ...

  2. 记录,javascript中对象的属性名是字符串,却可以不用引号

    问题描述:今日看书,里面介绍js的对象的属性名是包括空字符串在内的所以字符串 问题来了,我们平时定义的对象如下,是没有引号""or’'的 var someone  = {    f ...

  3. javascript中对象的属性的特性

    1.ES5的属性特性包括下面六个: configurable: 表示能否通过delete来删除属性从而重新定义属性,能够修改属性的特性,默认为true enumberable: 表示是否能通过for- ...

  4. JavaScript中对象的属性:如何遍历属性

    for/in 语句循环遍历对象的属性. js中获取key得到某对象中相对应的value的方法:obj.key js中根据动态key得到某对象中相对应的value的方法有二: 一.var key = & ...

  5. javascript中对象字面量的理解

    javascript中对象字面量与数组字面量 第一部分 我们知道JavaScript中的数据类型有基本数据类型和引用类型,其中Object类型就是非常常用的类型.那么如果创建一个Object类型的实例 ...

  6. 关于JavaScript中对象的继承实现的学习总结

    一.原型链 JavaScript 中原型链是实现继承的主要方法.其主要的思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.实现原型链有一种基本模式,其代码如下. function Super ...

  7. javascript中对象的深度克隆

    记录一个常见的面试题,javascript中对象的深度克隆,转载自:http://www.2cto.com/kf/201409/332955.html 今天就聊一下一个常见的笔试.面试题,js中对象的 ...

  8. Javascript中对象的Obeject.defineProperty()方法-------------(ES5/个人理解)

    在讲到Obeject.defineProperty()方法之前先得说明一下ECMAScript中有两种属性:数据属性和访问器属性. 两种属性存在的意义:描述对象属性(key)的一些特性,因为这些属性是 ...

  9. javascript中常用坐标属性offset、scroll、client

    原文:javascript中常用坐标属性offset.scroll.client 今天在学习js的时候觉得这个问题比较容易搞混,所以自己画了一个简单的图,并且用js控制台里面输出测试了下,便于理解. ...

随机推荐

  1. Jquery点击事件出发顺序

    鼠标点击触发事件执行顺序: mouse down -> mouse up -> click 键盘点击出发事件执行顺序: 点击后马上抬起:key down -> key press - ...

  2. python 学习2:生成器,迭代器,装饰器

    1.生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万  个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那 ...

  3. linux 创建账户

    linux下创建用户 linux下创建用户(一) Linux 系统是一个多用户多任务的分时操作系统,不论什么一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统. ...

  4. LeetCode:零钱兑换【322】【DP】

    LeetCode:零钱兑换[322][DP] 题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成 ...

  5. 高性能javascript学习总结(1)--加载与运行

    一.脚本的位置         我们知道,一个<script>标签可以放在 HTML 文档的<head>或<body>标签中,但是浏览器是怎么加载和执行这些java ...

  6. mysql表数据压缩

    记得一次面试中,面试官问我是否知道表的压缩,这个时候我才知道mysql有个表压缩这么个功能,今天试用下看看表的压缩率怎么样. 这里分两个部分说明,第一部分:官方文档说明:第二部分:具体实例测试. [第 ...

  7. case 练习

    #!/bin/bash RED_COLOR="\E[1;31m" GREEN_COLOR="\E[1;32m" YELLOW_COLOR="\E[1; ...

  8. codeforces 676B 模拟 递推

    题意:每秒从最高处的杯子倒一杯酒下来,酒流的方式如图,问t秒装满酒的杯子的数目. 思路:把第一杯的值设为t,glass[i][j]=(glass[i-1][j-1]-1)/2+(glass[i-1][ ...

  9. Spring Cloud之Hystrix服务保护框架

    服务保护利器 微服务高可用技术 大型复杂的分布式系统中,高可用相关的技术架构非常重要. 高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的 ...

  10. 大话设计模式--享元模式 Flyweight -- C++实现实例

    1. 享元模式: 运用共享技术有效地支持大量细粒度的对象. 享元模式可以避免大量非常相似类的开销,在程序设计中,有时需要生成大量颗粒度的类实例来表示数据,如果能发现这些实例除了几个参数外基本都是相同的 ...