接下来继续讨论对象的主要内容
一、属性描述符
对于对象中的属性,我们一般都是直接调用,但是属性本身又有什么特性呢,该怎么去描述?属性描述符就扮演了这样一种角色。属性描述符是在ES5以后才有的,看下面的代码。
var myobjec = {
a:2,
};
var descriptor = Object.getOwnPropertyDescriptor(myobjec,"a")
console.log(descriptor);
//返回的结果为一个object对象:
{
configurable:true
enumerable:true
value:2
writable:true
}
返回的对象中包含了四个描述符:configurable(可配置)、enumerable(可枚举)、value(值)、writable(可写入)。
这些属性描述符可以修改吗?当然,我们可以用Object.defineProperty(...)来添加一个新的属性或是修改一个已有属性。
1)writable
决定了属性是否可以被修改。如果将其设置为false,那么对应的value是不能被修改的。
var myobject = {
a:2,
};
Object.defineProperty(myobject,"a",{
writable:false,
configurable:true,
enumerable:true,
value:2
})
myobject.a = 4;
console.log(myobject.a);//2
2)configurable(可配置)
只要是可配置,就可以用defineProperty来修改属性描述符。
var myobject = {
a:2,
};
myobject.a = 3;
console.log(myobject.a ); //3
Object.defineProperty(myobject,"a",{
writable:true,
configurable:false,
enumerable:true,
value:4
});
console.log(myobject.a);//4
myobject.a = 5;
console.log(myobject.a);//5
Object.defineProperty(myobject,"a",{
writable:false,
configurable:false,
enumerable:true,
value:6
}); //Uncaught TypeError
如果将configurable设置为false后,再去配置就会报“未捕获类型”错误。
3)enumerable(可枚举)
通常属性的这个描述符默认为true,比如在for循环中,便可以枚举此属性。如果你在循环遍历中不想让该属性出现,那么将它的configurable设置为false即可。
二、属性不可变性
很多时候,你都希望属性是不可变的,在ES5中可以通过很多方法来实现:
1)将属性描述符writable和configurable设置为false即可;
2)禁用:可以使用Object.preventExtensions(..)来禁止一个对象添加新属性并且保留已有属性;
var myobject = {
a:2,
};
Object.preventExtensions(myobject);
myobject.b = 3;
console.log(myobject.b);//undefined
3)密封:Object.seal(...),调用这个方法时会调用preventExtention,并把所有的属性都标记为configurable:false,什么意思呢?就是说调用密封方法后,任何为对象添加、删除新属性的操作都是不允许的,但是属性值可以被修改,
4)冻结:Object.freeze(),这个方法会调用seal方法,同时将writable标记为false,所以这个不能修改的级别就比较高了,不仅不能添加删除,而且也不能修改了。
三、[[get]] 和 [[put]]操作
在访问对象属性时,表面上我们只是获取了属性值这么简单,其实是在对象上进行了[[get]]操作,对象的内置方法 [[get]] 会查找对象中是否存在改属性,如果存在返回属性对应的值,如果不存在,其实会执行另外一种行为,就是查找[[ prototype ]]链,也就是原型链,这个后面会讲到。
如果无论如何都找不到这个属性,那么就会返回undefined。
var myobject = {
a:2,
};
console.log(myobject.b);//undefined
console.log(c); //Uncaught ReferenceError: c is not defined
注意属性访问和变量访问的区别,如果对象中不存在属性,那么返回undefined,而变量在当前作用域中不存在的话,就会抛出 referenceError错误。原因就是属性访问调用了对象内置操作[[get]]。
对于[[put]]操作,你可能认为一般是在属性赋值时被调用,但实际情况并不完全这样,触发的原因有很多。不过首先要看对象中是否存在这个属性,如果存在的话,那么一般会检查下面的内容:
1)属性是否是属性描述符,如果是并且存在setter,就会调用setter,什么是setter呢,接下来会讲到;
2)属性描述符writable是否是false,如果是的话,在非严格模式下静默失败,在严格模式下抛出typeError错误;
3)如果上述情况都不是,那么将该值设置为属性值。
如果对象中不存在这个属性,那么[[put]]操作会查找原型链,这个后面会讲到。
四、getter 和 setter 操作
ES5中的getter和setter操作会改变对象属性的默认操作,但是只能应用在单个属性上而不是整个对象上。有的同学会问到,平时好像没有看到这两个方法啊?这是因为它们都是隐藏函数,都是在属性的获取和赋值时默默的调用的。
看下面这段代码:
var myobject = {
get a(){
return 2;
}
};
console.log(myobject.a);//2
Object.defineProperty(myobject,'b',{
get:function(){
return this.a*2;
}
})
console.log(myobject.b);//4
上面两种定义getter的方法都使得在访问属性时调用了getter。不过既然有了getter,那么setter也不能少对吧。
var myobject = {
get a(){
return this._a;
},
set a(val){
this._a = val*2;
}
};
myobject.a = 2;
console.log(myobject.a); //4
到此为止,同上篇文章一起对对象的一些基本属性做了系列的讨论和讲解,相信勤奋好学的同学们有了更深刻的认识吧。