不出你所料,对象属性自然也有其相应的特征属性,但是这个话题有点复杂,让我们先从简单的说起,对象属性的分类。
面对一个复杂的事物,寻找其内在共性,妥善分类往往是快速认知该事物的捷径,这与程序员“将难以解决的大问题拆解为可以解决的小问题”的思维有异曲同工之妙。
那么,对象的属性根据不同的维度,可以如何分类呢?你或许想不到,竟然有如此多的分类方法,而不同的类别,有牵扯出特定的方法解决这一类别的某些问题。让我们看看吧:
按来源分类
私有属性
原型属性
JavaScript是一门基于原型链的语言,对象继承是节省内存空间,避免代码重复,逻辑混乱的好方法。而对象继承对于属性而言则带来一个问题,即我们需要区分某对象内的属性,究竟是对象自有的(私有属性),还是继承于其他对象(继承属性),无论是进行属性遍历还是对属性进行操作,我们都需要谨慎的思考这个问题。
让我们举例两个典型场景看看JavaScript是如何帮助我们解决这个问题的:
情景一: 属性查找有时候,我们需要查找某个对象是否有某个属性,再进一步决定是否要执行下一步操作,JavaScript提供给我们的查找工具是in操作符,in操作符用以在给定对象中查找一个给定名称的属性,如果找到则返回true值。实际上,in操作符就是在哈希表中查找一个键是否存在。
但是遗憾的是,in操作符会检查所有的私有属性和原型属性,因此你并不能通过in操作符知道该属性的真正来源。
但好在JavaScript还给我们提供了一个.hasOwnProperty()方法,每一个对象都有这样一个方法,专门用来判断某个属性是否是该对象的私有属性。
我们终于得到了我们想要的。太棒了。
小结:
查找属性(不区分属性来源):in操作符
查找私有属性:对象的.hasOwnProperty()方法
情景二: 属性枚举有些时候,我们想要获得一个对象内所有属性的键或值(或者全部都要),这时我们就要枚举一个对象内的所有属性,通常,我们会使用for-in循环去实现这一点。
然而,很不巧的是,for-in循环会遍历所有可枚举的原型属性,注意这里有两点需要进一步说明:
可枚举:这牵扯到我们很快要谈到的属性特征属性(有点拗口是吧:))
会遍历原型属性:这样当一个对象的继承链很长而我们又只关心对象的私有属性时就会变得非常麻烦
当然,你可以在for-in循环中,再使用我们刚提到的.hasOwnProperty()方法,但是JavaScript给予了我们更好的选择:使用Object.keys()方法:
Object.keys()方法是ECMAScript5引入的方法,它可以获取可枚举属性的名字的数组,并且它只返回对象的自有属性。
因此,你可以基于是否需要一个数组,是否只需要对象自有属性来判断使用哪一种方法。
小结:
枚举属性(不区分属性来源):for-in循环
只枚举私有属性,且返回数组:Object.keys()方法
(在这里推荐下我自己的web前端学习交流群:675498134,不管你是小白还是大神,我都欢迎你们过来学习交流,不定期分享干货,包括我自己整理的最新的前端资料和教程送给大家,欢迎初学和进阶中的小伙伴,一起学习一起交流,共同进步。)
按作用分类
数据属性
访问器属性
你也许很少听说过这样的分类方式,因为我们几乎都在使用数据属性,让我来简要说明这两种类型的属性的区别:
数据属性包含了一个值,我们之前提到的对象的内部方法[[Put]]的默认行为就是创建数据属性。
访问器属性不包含值,而是定义了一个当属性被读取时调用的函数(称为“getter”)和一个当属性被写入时调用的函数(称为“setter”)。
之所以访问器属性很少见到是因为我们很少需要在进行属性赋值或读取操作时触发一些行为,不过反过来说,如果这恰恰是你面临的场景,就大胆的使用吧。
对象属性的特征属性绕了一大圈,终于可以回到正题,谈谈属性的特征属性了,相较于对象只有一个孤零零的[[Extensiable]]特征属性,对象属性要复杂的多:
因为所有对象属性都具有:
[[Enumerable]]特征属性:决定一个属性是否可以被遍历;
[[Configurable]]特征属性:决定一个属性是否可以被配置
而只有数据属性有以下两个属性:
[[Value]]特征属性:即属性的值;
[[Writable]]特征属性:值为布朗类型,决定该属性值是否可以写入;
而只有访问器属性有以下两个属性:
[[Get]]特征属性:即为getter函数内容;
[[Set]]特征属性:即为setter函数内容;
让我们先来看看这些特征属性的意义,再来谈谈如何配置这些特征属性:
[[Enumerable]]并不是所有的属性都是可枚举的,实际上,对象的大部分原生方法的[[Enumerable]]特征属性的值都被设置为false(所以使用for-in循环时,不会遍历出一大堆你不需要的内容),那我们该如何判断一个属性是否是可枚举的呢?
JavaScript为我们提供了.propertyIsEnumerable()方法去检查一个属性是否可枚举,像.hasOwnProperty()方法一样,每个对象都拥有这个方法。
[[Configurable]]可配置是指:
删除操作;
属性类型变更操作(从数据属性变为访问器属性,或者相反):
使用Object.defineProperty()方法配置属性(别着急,我们之后会着重讲解这个方法);
因此,当你设置某个属性的[[Configurable]]特征属性为false时,以上三种操作就都不能正确执行。
配置特征属性是时候讲解JavaScript为我们提供的配置属性特征属性的方法了:Object.defineProperty()
该方法接收三个参数:
拥有该属性的对象
属性名(字符串)
包含需要设置的特征的属性描述对象(属性描述对象具有和特征属性额同名的属性,但是名字中不包含中括号)
我们通过Object.defineProperty()方法使obj对象的x属性为不可遍历的,在之后的检测中,我们看到控制台输入属性存在,但不可遍历。
使用访问器属性特征属性比使用对象字面形式定义访问器属性的优势在于,你可以为已有的对象定义这些属性。如果你想要用对象字面形式,你只能在创建对象时定义访问器属性。
需要注意的是,一旦你决定使用Object.defineProperty()方法配置属性的特征属性,你需要完整在配置对象中列出enumerable属性与configurable属性,因为在默认情况下,这些属性的值皆为false,这可能不是你想要的。
对象封印对象封印是指,通过使用Object.seal()方法使一个对象不仅不可扩展,其所有的属性都不可配置,也就是说,对于一个被封印的对象,你不能:
添加新属性;
删除属性或改变属性类型;
当一个对象被封印时,你只能读写它已有的属性。另外,我们可以通过Object.isSealed()方法检验一个对象是否为被封印对象。
对象冻结让我们好好想想对象封印都做了些什么,它使我们不能添加属性,只能对已有的属性进行读写操作,但却无法改变已有属性的特征属性,也无法删除已有属性,我们的对象的封闭性已经非常强了。
而对象冻结则更近一步,将对象属性的操作限制为只读,它更像是一个对象某一时刻的快照,除了看之外我们不能对它有任何操作。
在JavaScript中,我们使用Object.freeze()冻结一个对象,并且使用Object.isFrozen()来判断一个对象是否被冻结。
来源: http://geek.csdn.net/news/detail/246026