《你不知道的JavaScript》第二部分 对象 第 2 篇。
自ES5开始,js中的对象属性具有属性描述符。可以直接检测与定义属性特性。
检测属性特性:
1var obj = {
2 a: 2
3}
4console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
5//打印
6/**
7configurable: true
8enumerable: true
9value: 2
10writable: true
11/
12
可以看到,检测属性的结果打印为4个属性数据描述符:value(属性值)、writable(可写)、enumerable(可枚举)、configurable(可配置)。
value
是属性的值。true
;writable
特性就是控制属性是否可改写;enumerable
特性是控制属性是否会出现在对象的属性枚举中,所谓的可枚举,就相当于 “可以出现在对象属性的遍历中”,比如for…in
循环;configurable
特性就是控制属性是否可配置,即是否能通过defineProperty()
方法来修改属性特性,当该特性值为false时,属性就不可配置。在用对象字符量方式创建对象时,对象的属性特性均会使用默认值。
如果想自定义属性特性,可以通过Object.defineProperty()
来添加一个新属性或者修改一个已有属性,当然想自定义的前提是configurable属性要为true。
1var newObj = {};
2Object.defineProperty(newObj, 'a', {
3 value: 10,
4 writable: true,
5 enumerable: true,
6 configurable: true
7})
8console.log(newObj.a); // 10
上例就是通过Object.defineProperty()
来为对象newObj
添加一个属性a
,并为这个属性配置了相关的属性特性。当然这种只是示例,在实际开发中不推荐这样定义一个对象,除非是要修改属性特性。
通过Object.defineProperty()
来控制对象属性的特性,比较好玩的一个实现就是生成一个真正的常量属性(不可修改、重定义或者删除):
1var obj = {};
2Object.defineProperty(obj, 'a', {
3 writable: false,
4 configurable: false
5})
当然还有其他好玩的实现,请多研究吧。
ES5对象属性除了有四个数据描述符,还有两个访问描述符getter和setter。当对属性定义访问描述符时,js会忽略它们的 value
和writable
特性,而改为关心 set
和get
以及configurable
和enumerable
特性。
1var obj = {
2 //给a定义一个getter
3 get a(){
4 return 3;
5 }
6}
7
8Object.defineProperty(obj, 'b', {
9 //给b设置一个getter
10 get: function(){return this.a*2;},
11 //确保b会出现对象的属性列表中
12 enmuerable: true
13})
14
15console.log( obj.a ); // 3
16console.log( obj.b ); // 6
不管是在对象字面量中的 get a(){...}
还是在defineProperty()
中的显式定义,二者都会在对象中创建一个不包含值的属性。
对于这个值的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值:
1var obj = {
2 get a(){
3 return 2;
4 }
5}
6
7obj.a = 10;
8console.log(obj.a); // 2
你看,即使再次对属性a进行set操作,返回值依然是是get隐藏函数的返回值,从而让set操作没有意义,也再次验证使用访问描述符时,js会忽略它们的value和writable特性。
所以为了让属性更合理,可以获取也可以修改值,还应当定义setter。通常getter和setter是成对出现的:
1var obj = {
2 get a(){
3 return this.res;
4 },
5 set a(val){
6 this.res = val;
7 }
8}
9obj.a = 10;
10console.log( obj.a ); // 10
11
12obj.a = 5;
13console.log( obj.a ); // 5
最后再来看下对象中属性的存在性检测:
in
操作符会检查属性是否在对象及其原型链中hasOwnProperty()
只会检查属性是否在对象中,不会检查到原型链中所有普通对象都可以通过对Object.protptype
的委托来访问hasOwnProperty()
方法
1var obj = {a:2};
2console.log(obj.hasOwnProperty('a')); // true
但有的对象可能是由于没有连接到Object.prototype
而不能访问hasOwnProperty()
方法,此时可以通过 call/apply 来借用:
1var emptyObj = Object.create(null);
2emptyObj.a = 11;
3console.log('hasOwnProperty' in emptyObj); // true emptyObj对象无法访问hasOwnProperty方法
4
5console.log(Object.prototype.hasOwnProperty.call(emptyObj, 'a')); // true
前几篇this的绑定规则还记得不,四个绑定规则里有一个是显式绑定,上例就是通过显式绑定来把Object.prototype.hasOwnProperty
方法里的this绑定到emptyObj
对象上,以达到借用hasOwnProperty
方法的目的。
补充个对象的枚举知识,有几点需要注意:
in
操作符可以用来判断属性是否在对象及其原型链中,for…in…
操作符只可以用来判断属性是否可枚举,即属性特性enumerable
为true时可枚举propertyIsEnumerable()
会检查给定的属性名是否直接存在于对象中(而不是存在于原型链中),并且还需满足enumerable: true
。Object.keys()
会返回一个数组,包含所有可枚举属性Object.getOwnPropertyNames()
会返回一个数组,包含所有属性,无论它们是否可枚举in
和hasOwnProperty()
的区别在于是否查找原型链,然而Object.keys()
和Object.getOwnPropertyNames()
都只会查找对象直接包含的属性in
操作符使用的属性列表(对象本身的属性及原型链上的属性)。不过可以递归遍历某个对象的整条原型链并保存每层中使用Object.keys()
得到的属性列表,这里只包含可枚举属性。