程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> ES5規范之Object增強

ES5規范之Object增強

編輯:JAVA綜合教程

ES5規范之Object增強


在ES5規范中,還有一個比較重要的改進,就是Object對象的增強,ES5為Object新增了一系列函數,用於編寫安全健壯的程序,今天我們就來一一介紹它們的使用方法。

下面就是ES5中Object新增的函數:

Object.defineProperty(object, propertyName, descriptor);

Object.defineProperties(object, descriptors);

Object.getOwnPropertyDescriptor(object, propertyName);

Object.create(prototype, descriptors);

Object.getPrototypeOf(object);

Object.keys(object);

Object.getOwnPropertyNames(object);

Object.preventExtensions(object);

Object.isExtensible(object);

Object.seal(object);

Object.isSealed(object);

Object.freeze(object);

Object.isFrozen(object);


下面我們逐一介紹:


Object.defineProperty(object, propertyName, descriptor);

defineProperty函數用於定義一個對象上的屬性以及這個屬性的描述符。這裡涉及到描述符這個概念,需要先理解一下,描述符是用來描述一個屬性的值和這個屬性在運行期的訪問控制,一個描述符包含下面幾個聲明:

configurable: 表示是否能通過delete刪除對象中的該屬性,以及是否能重新定義該屬性,默認是false,默認情況下,不能用delete刪除屬性,不能重新定義該屬性。需要注意的是,一旦明確設置configurable為false之後,就再也不能重新設置這個規則為true了。

enumerable: 表示能否通過for-in循環獲得對象中的該屬性,默認值是false,默認情況下,使用for-in循環將不能看到該屬性出現。

writable: 表示能否修改該屬性,默認值是false,默認情況下,不能再更改該屬性的值。

value: 該屬性的值,默認值是undefined。通常我們會設置value為一個有意義的值。

下面是每個聲明的默認值列表:(Get和Set後續會介紹)

\

這裡需要注意,當configurable和writable為false的情況下,試圖刪除或更改相對應的屬性,常規模式下操作將會被忽略,如果是嚴格模式,將會拋出異常。我們的代碼會加上"use strict";來啟用嚴格模式。關於嚴格模式的細節,我們後續的文章會專門介紹。

我們先來使用defineProperty函數定義一個對象的屬性:

"use strict";

var person = {};

Object.defineProperty(person, 'name', {
  value: 'Scott'
});

person.name = 'John';
上面的代碼我們使用defineProperty為person對象定義了一個name屬性,然後試圖更改它的值。如果在常規模式下運行,更改操作將會被忽略,這裡我們加上了嚴格模式的聲明,將會拋出下面的異常:

\

如果通過delete試圖刪除name屬性,同樣也會得到一個異常結果:

delete person.name;
\

<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+ztLDx8nUzqK4xLav0rvPwrT6wuujrM6qZGVmaW5lUHJvcGVydHm6r8r9tcTX7rrz0ru49rLOyv3M7bzT0ru49ndyaXRhYmxlyfnD96O6PC9wPgo8cD48L3A+CjxwcmUgY2xhc3M9"brush:java;">"use strict"; var person = {}; Object.defineProperty(person, 'name', { writable: true, //add writable as true value: 'Scott' }); person.name = 'John'; console.log('after changing the name: ', person.name); delete person.name; console.log('after deleting the name: ', person.name);添加writable為true後,再次運行程序,看看結果如何:

\

我們現在可以更改name屬性的值了。只不過試圖刪除name時,仍然會拋出一個異常,現在我們需要再添加一個configurable聲明:

"use strict";

var person = {};

Object.defineProperty(person, 'name', {
  configurable: true, //add configurable as true
  writable: true, //add writable as true
  value: 'Scott'
});

person.name = 'John';
console.log('after changing the name: ', person.name);

delete person.name;
console.log('after deleting the name: ', person.name);

\

從打印結果來看,更改操作和刪除操作在嚴格模式下都順利執行了。現在大家應該都了解到configurable和writable的作用了吧。configurable允許或禁止刪除操作的執行,writable允許或禁止更改操作的執行。

另外,上面我們也提到過,如果明確聲明了configurable為false,則不能再使用defineProperty將其定義configurable為true了,下面代碼將會拋出一個異常:

'use strict';

var person = {};

Object.defineProperty(person, 'name', {
  configurable: false,  //declare configurable as false
  writable: false,  //declare writable as false
  value: 'Scott'
});

//try to redefine the name's descriptor
Object.defineProperty(person, 'name', {
  configurable: true,
  writable: true,
  value: 'Scott'
});

\

當外部代碼試圖更改對象屬性時,適當的使用configurable和writable為其加一些限制,可以提高代碼的安全性,這一點對模塊開發非常有用。

下面來介紹一下enumerable聲明,我們用一個簡單的示例來講解:

"use strict";

var person = {};

Object.defineProperty(person, 'name', {
  value: 'Scott'
});

//nothing will be logged
for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key + ': ' + person[key]);
  }
}
上面這段代碼我們不會看到person中的鍵值對,因為默認情況下使用defineProperty定義屬性時,屬性的enumerable為false,即不可被遍歷,如果需要在for-in中獲取到name屬性,我們需要為其聲明enumerable為true:

"use strict";

var person = {};

Object.defineProperty(person, 'name', {
  enumerable: true, //add enumerable as true
  value: 'Scott'
});

//name: Scott
for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key + ': ' + person[key]);
  }
}

檢測一個對象的屬性是否可遍歷,我們還可以使用Object的另外一個原型方法,它更為方便:

var isNameEnumerable = person.propertyIsEnumerable('name');
除了上面這些之外,在defineProperty函數的descriptor中還可以使用setter和getter對屬性進行定義:

var getPerson = function() {
  var person = {};
  
  var personAge = 20;

  Object.defineProperty(person, 'age', {
    get: function() {
      return personAge;
    },
    set: function(newAge) {
      if (newAge < 0) {
        newAge = 0;
      }
      if (newAge > 150) {
        newAge = 150
      }

      personAge = newAge;
    }
  });
  
  return person;
}

var person = getPerson();

person.age = -1;
console.log(person.age);  //0
person.age = 200;
console.log(person.age);  //150

上面代碼中我們把創建person對象的操作封裝在getPerson函數中,然後使用set方法和get方法定義age屬性的行為,在set和get方法中,間接使用了局部變量personAge來表示person的age屬性,這種方式的好處在於,可以對賦值操作加一些驗證,使其符合我們業務邏輯的要求。不過需要注意的是,不是一定非要同時指定setter和getter的,在只定義getter時該屬性不能寫,只指定setter時不能讀,如果只有getter而嘗試去寫、只有setter嘗試去讀的話,嚴格模式下會拋出異常,所以正確地使用setter和getter才能寫出高質量的代碼。


Object.defineProperties(object, descriptors);

在了解上面介紹的defineProperty函數之後,對於這個函數就比較容易理解了,defineProperties函數用於一次性定義多個屬性,我們用一段代碼來解釋:

var person = {};

Object.defineProperties(person, {
  'name': {
    writable: false,
    value: 'Scott'
  },
  'address': {
    writable: true,
    value: 'Beijing'
  }
});

代碼中我們最後一個參數包含兩個屬性:name和address,分別都有自己的屬性描述信息,這種方式比較單一屬性的聲明來說簡單明了,在一次性有多個屬性需要聲明時比較實用。


Object.getOwnPropertyDescriptor(object, propertyName);
這個函數用於獲取指定屬性的描述符,其中會包括上面提到的一些描述符聲明,我們直接看下面示例代碼:

var person = {};

Object.defineProperty(person, 'name', {
  configurable: true,
  writable: false,
  enumerable: true,
  value: 'Scott'
});

var descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor.configurable); //true
console.log(descriptor.writable); //false
console.log(descriptor.enumerable); //true
console.log(descriptor.value);  //Scott

需要注意的是,如果定義屬性時使用了set和get方法,那麼返回的descriptor對象也是可以通過set和get訪問到相應的setter和getter的。

接著我們來了解一下與原型有關的兩個函數:


Object.create(prototype, descriptors);

create函數用於在指定原型基礎之上創建一個新的對象,第一個參數即指定的原型對象,第二個參數就是我們上面介紹到的描述符,裡面可以定義多個屬性的描述信息。原型參數只能是一個對象或者指定為null,下面三種方式都可以創建一個空對象:

var obj0 = Object.create({});
var obj1 = Object.create(null);
var obj2 = Object.create(Object.prototype);

我們當然還可以指定一個包含多個屬性描述信息的描述符參數,來創建一個新的對象,在原來對象上進行擴展。下面這個例子我們在一個已有對象的基礎之上創建一個person對象,包含name屬性和gender屬性,其中name可更改不可刪除,gender是只讀狀態:

var human = {
  info: 'human being'
};

var person = Object.create(human, {
  name: {
    configurable: false,
    writable: true,
    enumerable: true,
    value: 'Scott'
  },
  gender: {
    get: function() {
      return "Male";
    }
  }
});

console.log(person);
上面代碼相當於在human對象上進行擴展,添加了name和gender屬性,進而創建一個新對象,我們來看一下person對象的結構:

\

從打印信息中,我們可以看出,因為name屬性是可遍歷的,所以顯示在Object{}中,當我們展開後,會看到gender的get方法,也可以看得到person的原型鏈,包含info屬性的原型就是我們上面定義的human對象了,證明person對象確實是human對象的擴展。create與defineProperties有些相象之處,不同的是,create會創建一個新的對象,而defineProperties不會,我們也可以獲取它的返回值,返回值就是原對象本身。


Object.getPrototypeOf(object);

此函數用於獲取指定對象的原型,來看下面這個精簡過的例子:

var human = {
  info: 'human being'
};

var person = Object.create(human, {
  name: {
    value: 'Scott'
  }
});

console.log(Object.getPrototypeOf(person) === human); //true
我們使用human作為原型創建一個person對象,然後使用getPrototypeOf函數獲取person對象的原型,結果會返回human,我們上面也介紹到了,person對象的確是從human對象擴展而來,getPrototypeOf獲取到的原型和我們期望的是一致的。

Object.keys(object);

此函數用於獲取對象中可被遍歷的全部屬性的key的集合。看下面例子:

var array = ['a', 'b', 'c'];
console.log(Object.keys(array)); //["0", "1", "2"]

var person = Object.defineProperties({}, {
  name: {
    enumerable: true,
    value: 'Scott'
  },
  info: {
    enumerable: true,
    value: 'I am Scott'
  },
  address: {
    enumerable: false,  //not enumerable
    value: 'Beijing'
  }
});

console.log(Object.keys(person)); //["name", "info"]
首先我們創建一個數組,使用keys函數獲取所有的key,結果會打印出數組的下標組成的集合;然後我們定義了person對象的屬性,其中name和info都是可遍歷的,address是不可遍歷的,結果會打印出name和info,而不會出現address,使用keys函數的時候需要注意這一點。


Object.getOwnPropertyNames(object);

此函數與keys函數相似,不過它會返回所有的鍵,包括哪些不可被遍歷的屬性。現在我們把keys函數替換成getOwnPropertyNames,看看結果如何:

var array = ['a', 'b', 'c'];
console.log(Object.getOwnPropertyNames(array));

var person = Object.defineProperties({}, {
  name: {
    enumerable: true,
    value: 'Scott'
  },
  info: {
    enumerable: true,
    value: 'I am Scott'
  },
  address: {
    enumerable: false,  //not enumerable
    value: 'Beijing'
  }
});

console.log(Object.getOwnPropertyNames(person));
結果打印如下:

\
我們會發現,array裡面增加了一個length的key,而person對象也增加了一個address的key,我們可以得知,數組對象的length被設置為不可遍歷的,也證明了getOwnPropertyNames確實可以把所有的屬性的key獲取到。


Object.preventExtensions(object); & Object.isExtensible(object);

preventExtensions函數用於禁止指定對象的擴展,即禁止向對象中添加新的屬性。我們定義一個簡單的對象,然後測試一下這個函數:

'use strict';

var person = {
  name: 'Scott'
};

Object.preventExtensions(person);

console.log(Object.isExtensible(person)); //false

//Uncaught TypeError: Can't add property age, object is not extensible
person.age = 20;
測試證明,試圖添加一個age屬性,嚴格模式下將會拋出異常,證明person對象已經是不可擴展的。

Object.seal(object); &Object.isSealed(object);

seal函數用於對指定對象進行封存,已封存的對象禁止再添加新的屬性,禁止刪除已有的屬性,禁止重新定義已有屬性的cnofigurable為true。來看下面一段代碼:

'use strict';

var person = {
  name: 'Scott'
};

Object.seal(person);

console.log(Object.isSealed(person)); //true

person.name = 'John';

//Uncaught TypeError: Can't add property age, object is not extensible
//person.age = 20;

//Uncaught TypeError: Cannot delete property 'name' of #
delete person.name;

上面我們定義了一個普通字面量的person對象,它的name屬性是可寫可刪除可遍歷的,接著我們調用seal函數對其進行封存,如果我們想要判斷一個對象是否已被封存,可以使用isSealed函數。被封存的對象禁止添加屬性和刪除已有的屬性,所以我們試圖添加一個age屬性和刪除已有的name屬性都會在嚴格模式下拋出異常。需要注意的是,只要是可寫的屬性,即使被封存也可以更改其值,所以更改name的值是不會有問題的。

另外,對已被封存對象的屬性重新定義也要特別小心,因為被封存的對像使用defineProperty重新定義規則時,只能更改writable和value,不能更改原來的enumerable,對於configurable更為嚴苛,不能明確聲明。如下代碼列舉了一些注意事項:

'use strict';

var person = {
  name: 'Scott'
};

Object.seal(person);

person.name = 'Jack';
console.log(person.name); //Jack

//after sealed, can't declare the configurable, and can't change the original enumerable.
Object.defineProperty(person, 'name', {
  //configurable: true,
  writable: false,
  enumerable: true, //the enumerable here must be equal to the original if declare explicitly
  value: 'John'
});

//Uncaught TypeError: Cannot assign to read only property 'name' of object '#'
person.name = 'Scott';可以看出writable是可以更改為false的,但注意enumerable只能和原來對象一樣為true,而configurable在這裡不能明確聲明,大家可以親自測試一下。


Object.freeze(object); & Object.isFrozen(object);

freeze函數用於對指定對象進行凍結,凍結後的對象禁止添加屬性,禁止刪除屬性,禁止更改屬性,禁止使用defineProperty更改其configurable,writable,和enumerable的值。可以說freeze函數禁止對象屬性上的所有操作,不過需要注意的是,如果對象的屬性也是對象類型,那麼這個屬性對象下面的屬性是不會被凍結的,我們依舊可以操作:

'use strict';

var person = {
  name: 'Scott',
  info: {
    weight: 65,
    height: 175
  }
};

Object.freeze(person);

console.log(Object.isFrozen(person)); //true

person.info.age = 20; //add 'age' property to info
person.info.weight = 70;  //change the 'weight' property

上面代碼中,我們向info中添加age屬性,或者更改weight的值,這些操作都是沒有問題的,所以如果需要對一個對象及下面的屬性進行完全的凍結,我們需要遞歸的對每一個屬性對象進行凍結。


以上就是ES5對Object的增強,有了這些強大的API,可以確保我們的代碼更加安全,進而可以構建出健壯的應用。另外,在上面的示例中,我們多次提到了嚴格模式,這也是ES5規范中重要的一個方面,下次我們會對嚴格模式進行一個全面的概括。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved