1. 程式人生 > >JavaScript中的對象描述符(屬性特性)

JavaScript中的對象描述符(屬性特性)

http rabl catch defined tor pro 其他 mas art

  我們先創建一個對象:

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

寫的有點冗長,但是算是基本測試了各種情況,方便查閱;

  

JavaScript中的對象描述符(屬性特性)