在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. bug-4——bootStrap中的table语言设置

    $(document).ready(function() {     $('.datatable').dataTable( {                 "Language" ...

  2. oracle 查询重复数据并且删除, 只保留一条数据重复数据

    最近面试中都遇到了这样一个数据库题: 删除表中的重复数据,有且只保留一条重复数据. 思路: 1)这个题需要用到rowid,首先找到重复数据的rowid,并找出rowid最大或最小值,作为删除的条件: ...

  3. R语言数据管理(二):模式与类

      最常用的4种数据类型是数值型(numeric).字符型(character)(字符串).日期型(Date)或POSIXct(基于日期的).逻辑型(logical)(TRUE或FALSE). 变量中 ...

  4. Python:笔记(2)——函数与模块

    Python:笔记(2)——函数与模块 Python函数 关于函数 1.我们可以使用Help来查看函数的帮助信息 2.调用函数的时候,如果传入的参数数量或者类型不符合均会报错. 3.函数名其实就是一个 ...

  5. Hadoop1.x Shell命令

    refer to http://hadoop.apache.org/docs/r1.0.4/cn/hdfs_shell.html FS Shell 调用文件系统(FS)Shell命令应使用 bin/h ...

  6. m3u8格式转MP4

    公司直播平台使用的是七牛直播,今天有客户表示想将直播回放视频下载下来,数据妹子犯了愁,表示这个不会下载给客户,于是乎这个任务就落在了我的头上.熟练的打开视频,在 HTML 源代码播放地址为 http: ...

  7. Python 3 并发编程多进程之守护进程

    Python 3 并发编程多进程之守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemo ...

  8. 高通8X16电池BMS算法(二)【转】

    本文转载自:http://www.voidcn.com/blog/yanleizhouqing/article/p-6051912.html 上一篇主要讲电池相关的一些知识,上节忘记讲了,电池一般分为 ...

  9. C#中在内容页获取其模板页中的变量,或者值

    在CSDN的博文中看到了 muziduoxi 的文章:http://blog.csdn.net/muziduoxi/article/details/5386543 虽然里面提到的方法没有解决我的难题, ...

  10. 分享知识-快乐自己:Mybatis 基础动态语句

    目录: User: package mlq.bean; /** * 用户实体类 */ public class User { private Integer uId; private String u ...