JavaScript中的对象描述符(属性特性)
我们先创建一个对象:
var person = { name: "Nicholas", _job: "Software Engineer", sayName: function(){ alert(this.name); }, get job(){ return this._job; }, set job(newJob){ this._job=newJob; } }
在这个对象中,我们定义了一个name属性和一个_job属性;至于以set和get开头的两处代码,他们共同定义了一个属性job。明显属性job和_job、name的是不同的。是的,JavaScript中的对象有两种不同类型的属性:数据属性和访问器属性。
name和_job是数据属性,job是访问器。数据属性和访问器属性的最大的不同就在于:当访问一个访问器属性时,得到get后面函数的返回值;给一个访问器属性赋值时,执行的是set后面的函数,这个函数以赋的值为参数:
console.info(person.job);//Software Engineer person.job="Coder"; console.info(person.job);//Coder console.info(person._job);//Coder
在set函数中我们通过this改变了_job的值(this指向person这个对象);
在了解了对象属性的类型后,我们再来看看对象属性特性。每个对象的属性在创建时都带有一些特征值,JavaScript通过这些特征值来定义它们的行为。
我们在创建person对象时没有为它的属性们直接指定特征值,JavaScript自动为它们创建了属性特性。在ES3中属性特性不可访问,但是ES5中属性的特性可以通过Object.getOwnPropertyDescriptors或Object.getOwnPropertyDescriptor得到:
var descriptors= Object.getOwnPropertyDescriptors(person) //descriptors的内容如下; { age:{value: 29, writable: true, enumerable: true, configurable: true} job:{enumerable: true, configurable: true, get: ƒ, set: ƒ} name:{value: "Nicholas", writable: true, enumerable: true, configurable: true} sayName:{writable: true, enumerable: true, configurable: true, value: ƒ} _job:{value: "Coder", writable: true, enumerable: true, configurable: true} }
Object.getOwnPropertyDescriptors返回一个对象,包含了描述person对象的所有属性的特性。其中每一个属性的特性用一个对象表示,我们叫它属性描述符。我们可以看到:数据属性的描述符有四个属性:value,writable ,enumerable ,configurable ;访问器属性的描述符也有四个属性:enumerable ,configurable,get,set ;
那么我们分别看下数据属性及访问器属性的这几个描述符:
数据属性
数据属性的描述符的有四个属性分别是:
1.value:包含这个属性的数据值。读取属性的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认为undefined.
2.writable:表示能否修改属性的值。是一个bool值,默认为true
3.enumerable:属性是否可枚举,即能否通过for-in循环返回属性。是一个bool值,默认为true
4.configrable:属性是否可配置。即属性能否通过delete删除,能否修改属性的特性,或者能否把属性修改为访问器属性。是一个bool值,默认为true
我们最初开始创建的person对象的属性name,它的value为“Nicholas”,其他描述符为true。
请看例子:
访问器属性
访问器属性的描述符的有四个属性分别是:
1.get:在读取属性时调用的函数。默认值为 undefined。
2.set:在写入属性时调用的函数。默认值为 undefined。
3.enumerable:属性是否可枚举,即能否通过for-in循环返回属性。是一个bool值,默认为true
4.configrable:属性是否可配置。即属性能否通过delete删除,能否修改属性的特性,或者能否把属性修改为数据属性。是一个bool值,默认为true
我们最初开始创建的person对象的属性job,它的get和set值分别是我们指定的函数,其他描述符为true。
要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty或 Object.defineProperties。
有了定义属性特性的方法,那我们通过代码来探索下这些属性特性的作用:
数据属性:
var person ={}; //除了configrable之外,其他三个属性相互之间不会影响,读者可以自己测试 console.info('---------------------writable start--------------------------------'); Object.defineProperty(person, "name", { writable: false, enumerable:true, configurable:true, value: "Nicholas" }); console.info(person.name); //"Nicholas" person.name = "Greg"; //writable为false,属性不可修改 console.info(person.name); //"Nicholas" //writable为false,但configrable为true,我们可以重新配置属性描述符, Object.defineProperty(person, "name", { writable: false, enumerable:true, configurable:true, value: "John" }); console.info(person.name)//John delete person.name //但configrable为true,属性可以被删除 console.info (person.name)//undefined console.info('---------------------writable end--------------------------------'); console.info('---------------------enumerable start--------------------------------'); var person={}; Object.defineProperty(person, "name", { writable: false, enumerable:true, configurable:true, value: "Nicholas" }); //enumerable为true属性可枚举 for(var prop in person){ console.info(prop)//name } Object.defineProperty(person, "name", { writable: false, enumerable:false, configurable:true, value: "Nicholas" }); //enumerable为false属性不可枚举,循环体不执行 for(var prop in person){ console.info(prop)// } console.info('---------------------enumerable end--------------------------------'); console.info('---------------------configurable start--------------------------------'); var person={}; Object.defineProperty(person, "name", { writable: true, enumerable:true, configurable:false, value: "Nicholas" }); //configurable为false,writable为true,属性仍然可修改 person.name="John" console.info(person.name);//John //configurable为false,writable为true,仍然可以通过配置的方式改变属性值 Object.defineProperty(person, "name", { writable: true, enumerable:true, configurable:false, value: "Nicholas" }); console.info(person.name) //configurable为false,enumerable为ture,属性可枚举 for(var prop in person){ console.info(prop)//name } //configurable为false,我们仍然可以把writable属性由true改为false Object.defineProperty(person, "name", { writable: false, enumerable:true, configurable:false, value: "Nicholas" }); console.info(Object.getOwnPropertyDescriptor(person,"name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} //configurable为false,writable为false,不能通过配置改变value的值 try{ Object.defineProperty(person, "name", { writable: false, enumerable:true, configurable:false, value: "John" }); }catch(error){ console.info("value change error"); console.info(Object.getOwnPropertyDescriptor(person,"name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} } //configurable为false,但是不能把writable属性由false改为true try{ Object.defineProperty(person, "name", { writable: true, enumerable:true, configurable:false, value: "Nicholas" }); }catch(error){ console.info("writable false to true error") console.info(Object.getOwnPropertyDescriptor(person,"name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} } //configurable为false,不能改变enumerable的值 try{ Object.defineProperty(person, "name", { writable: false, enumerable:false, configurable:false, value: "Nicholas" }); }catch(error){ console.info("enumerable change error"); console.info(Object.getOwnPropertyDescriptor(person,"name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} } var person={}; Object.defineProperty(person, "name", { writable: true, enumerable:true, configurable:true, value: "Nicholas" }); //configurable为true,可以把数据属性修改为访问器属性 try{ Object.defineProperty(person, "name", { get: function(){return "Nicholas"}, enumerable:true, configurable:false }); }catch(error){ console.info("get error"); } console.info(Object.getOwnPropertyDescriptor(person,"name"))//{set: undefined, enumerable: true, configurable: false, get: ƒ} var person={}; Object.defineProperty(person, "name", { writable: true, enumerable:true, configurable:false, value: "Nicholas" }); //configurable为false,不可以把数据属性修改为访问器属性 try{ Object.defineProperty(person, "name", { get: function(){return "Nicholas"}, enumerable:true, configurable:false }); }catch(error){ console.info("get error"); } console.info(Object.getOwnPropertyDescriptor(person,"name"))//{value: "Nicholas", writable: true, enumerable: true, configurable: false} console.info('---------------------configurable end--------------------------------');
访问器属性
//访问器属性 console.info("------------------------------------------------------"); var person ={_name:"Nicholas"}; Object.defineProperty(person, "name", { get: function(){ console.info("get 被调用") return this._name }, set:function(newName){ console.info("set 被调用") this._name=newName }, enumerable:true, configurable:true, }); person.name;//get 被调用 person.name="John";//set 被调用 console.info("------------------------------------------------------"); console.info("----------------------不设set 开始--------------------------------"); var person ={_name:"Nicholas"}; Object.defineProperty(person, "name", { get: function(){ console.info("get 被调用") return this._name }, enumerable:true, configurable:true, }); person.name;//get 被调用 person.name="John";//没有设置set,什么也没发生 console.info(person.name)//Nicholas, console.info("----------------------不设set 结束--------------------------------"); console.info("----------------------不设get 开始--------------------------------"); var person ={_name:"Nicholas"}; Object.defineProperty(person, "name", { set:function(newName){ console.info("set 被调用") this._name=newName }, enumerable:true, configurable:true, }); console.info(person.name);//没有get,得到 undefined console.info(person._name);//Nicholas person.name="John";//set 被调用 console.info(person._name)//John,通过set,_name的值被改变 console.info("----------------------不设get 结束--------------------------------"); console.info("----------------------不设get set开始--------------------------------"); //虽然不报错,但是这个属性没有任何意义 var person ={_name:"Nicholas"}; Object.defineProperty(person, "name", { enumerable:true, configurable:true, }); console.info("----------------------不设get 结束--------------------------------"); console.info("----------------------enumerable 开始--------------------------------"); var person ={_name:"Nicholas"}; Object.defineProperty(person, "name", { get: function(){ console.info("get 被调用") return this._name }, set:function(newName){ console.info("set 被调用") this._name=newName }, enumerable:true, configurable:true, }); for(var prop in person){ console.info(prop)//_name,name } Object.defineProperty(person, "name", { get: function(){ console.info("get 被调用") return this._name }, set:function(newName){ console.info("set 被调用") this._name=newName }, enumerable:false, configurable:true, }); for(var prop in person){ console.info(prop)//_name } console.info("----------------------enumerable 结束--------------------------------"); console.info("----------------------configurable 开始--------------------------------"); var person ={_name:"Nicholas"}; Object.defineProperty(person, "name", { get: function(){ console.info("get 被调用") return this._name }, set:function(newName){ console.info("set 被调用") this._name=newName }, enumerable:true, configurable:false, }); person.name;//get 被调用 person.name="John";//set 被调用 console.info(person.name);//John //报错 try{ Object.defineProperty(person, "name", { get: function(){ console.info("get 被调用") return this._name }, set:function(newName){ console.info("set 被调用") this._name=newName }, enumerable:true, configurable:false, }); }catch(e){ console.info("不能重新定义name的属性标识符") } //报错 try{ Object.defineProperty(person, "name", { value:"123", writable:true, enumerable:true, configurable:false, }); }catch(e){ console.info("不能重新定义name的属性标识符") } console.info("----------------------configurable 结束--------------------------------");
总结一下:
1.writable为true,属性值就可以修改,无论是通过.运算符还是通过Object.defineProperty方法;
2.writable为false,不能通过.运算符修改属性。但是在configurable为true的情况下可以通过通过Object.defineProperty方法重新设置value值,从而修改属性值;
3.只要enumerable为true,属性就可枚举,为false则不可枚举;
4.configurable为true的情况下,可以对属性描述符对象进行任何修改;
5.configurable为fasle的情况下,可以通过Object.defineProperty把writable改为true,在writabe为true的情况下,可以修改value的值。
6.configurable为false的情况下,除第5条的所述的情况外,不能通过Object.defineProperty修改属性的任何特性值。
可扩展性
ES5定义了三个方法Object.preventExtensions、Object.seal、Object.freeze分别定义了不同级别的可扩展性。可点击连接前往MDN阅读;
最后:get、set 与继承
function Person(){} var p=Person.prototype; p._name="John"; Object.defineProperty(p,"name",{ get: function(){return this._name}, set: function(newName){ this._name=newName}, enumerable:true, configurable:true }) var person=new Person(); console.info(person.name)//John console.info(person.hasOwnProperty("_name"))//false //虽然name属性的set get方法是定义在原型中的 //但是通过person调用时,它们的this属性会指向person //所以通过person.name设置属性时,执行set方法中的this._name=newName时, //会给person对象添加_name属性,并把newName赋给person新建的属性_name; person.name="Nicholas"; console.info(person.name);//Nicholas console.info(p._name)//John console.info(person.hasOwnProperty("name"))//false console.info(person.hasOwnProperty("_name"))//true
写的有点冗长,但是算是基本测试了各种情况,方便查阅;