《JavaScript面向對象精要》之三:理解對象

JavaScript 中的對象是動態的,可在代碼執行的任意時刻發生改變。基於類的語言會根據類的定義鎖定對象。chrome

3.1 定義屬性

當一個屬性第一次被添加到對象時,JavaScript 會在對象上調用一個名爲 [[Put]] 的內部方法。數組

[[Put]] 方法會在對象上建立一個新節點來保存屬性。函數

當一個已有的屬性被賦予一個新值時,調用的是一個名爲 [[Set]] 的方法。ui

3.2 屬性探測

檢查對象是否已有一個屬性。JavaScript 開發新手錯誤地使用如下模式檢測屬性是否存在。this

if(person.age){
  // do something with ag
}
複製代碼

上面的問題在於 JavaScript 的類型強制會影響該模式的輸出結果。
當 if 判斷中的值以下時,會判斷爲spa

  • 對象
  • 非空字符串
  • 非零
  • true

當 if 判斷中的值以下時,會判斷爲prototype

  • null
  • undefined
  • 0
  • false
  • NaN
  • 空字符串

所以判斷屬性是否存在的方法是使用 in 操做符。code

in 操做符會檢查自有屬性和原型屬性對象

全部的對象都擁有的 hasOwnProperty() 方法(實際上是 Object.prototype 原型對象的),該方法在給定的屬性存在且爲自有屬性時返回 trueip

var person = {
  name: "Nicholas"
}

console.log("name" in person); // true
console.log(person.hasOwnpropert("name")); // true

console.log("toString" in person); // true
console.log(person.hasOwnproperty("toString")); // false
複製代碼

3.3 刪除屬性

設置一個屬性的值爲 null 並不能從對象中完全移除那個屬性,這只是調用 [[Set]]null 值替換了該屬性原來的值而已。

delete 操做符針對單個對象屬性調用名爲 [[Delete]] 的內部方法。刪除成功時,返回 true

var person = {
  name: "Nicholas"
}

person.name = null;
console.log("name" in person); // true
delete person.name;
console.log(person.name); // undefined 訪問一個不存在的屬性將返回 undefined
console.log("name" in person); // false
複製代碼

3.4 屬性枚舉

全部人爲添加的屬性默認都是可枚舉的。可枚舉的內部特徵 [[Enumerable]] 都被設置爲 true

for-in 循環會枚舉一個對象全部的可枚舉屬性。

ECMAScript 5 的 Object.keys() 方法能夠獲取可枚舉屬性的名字的數組。

var person = {
  name: "Nicholas",
  age: 18
}

Object.keys(person); // ["name", "age"];
複製代碼

for-inObject.keys() 的一個區別是:前者也會遍歷原型屬性,然後者返回自有(實例)屬性。

實際上,對象的大部分原生方法的 [[Enumerable]] 特徵都被設置爲 false。可用 propertyIsEnumerable() 方法檢查一個屬性是否爲可枚舉的。

var arr = ["abc", 2];
console.log(arr.propertyIsEnumerable("length")); // false
複製代碼

3.5 屬性類型

屬性有兩種類型:數據屬性訪問器屬性

數據屬性包含一個值。[[Put]] 方法的默認行爲是建立數據屬性

訪問器屬性不包含值而是定義了一個當屬性被讀取時調用的函數(稱爲 getter)和一個當屬性被寫入時調用的函數(稱爲 setter)。訪問器屬性僅須要 gettersetter 二者中的任意一個,固然也能夠二者。

// 對象字面形式中定義訪問器屬性有特殊的語法:
var person = {
  _name: "Nicholas",

  get name(){
    console.log("Reading name");
    return this._name;
  },
  set name(value){
    console.log("Setting name to %s", value);
    this._name = value;
  }
};
    
console.log(person.name); // "Reading name" 而後輸出 "Nicholas"

person.name = "Greg";
console.log(person.name); // "Setting name to Greg" 而後輸出 "Greg"
複製代碼

前置下劃線 _ 是一個約定俗成的命名規範,表示該屬性是私有的,實際上它仍是公開的。

訪問器就是定義了咱們在對象讀取或設置屬性時,觸發的動做(函數),_name 至關於一個內部變量。

當你但願賦值(讀取)操做會觸發一些行爲,訪問器就會很是有用。

當只定義 getter 或 setter 其一時,該屬性就會變成只讀或只寫。

3.6 屬性特徵

在 ECMAScript 5 以前沒有辦法指定一個屬性是否可枚舉。實際上根本沒有方法訪問屬性的任何內部特徵。

爲了改變這點,ECMAScript 5 引入了多種方法來和屬性特徵值直接互動。

3.6.1 通用特徵

數據屬性和訪問器屬性均由如下兩個屬性特製:

  • [[Enumerable]] 決定了是否能夠遍歷該屬性;
  • [[Configurable]] 決定了該屬性是否可配置。

全部人爲定義的屬性默認都是可枚舉、可配置的。

能夠用 Object.defineProperty() 方法改變屬性特徵。

其參數有三:擁有該屬性的對象、屬性名和包含須要設置的特性的屬性描述對象。

var person = {
  name: "Nicholas"
}
Object.defineProperty(person, "name", {
  enumerable: false
})

console.log("name" in person); // true
console.log(person.propertyIsEnumerable("name")); // false

var properties = Object.keys(person);
console.log(properties.length); // 0

Object.defineProperty(person, "name",{
  configurable: false
})

delete person.name; // false
console.log("name" in person); // true

Object.defineProperty(person, "name",{ // error! 
  // 在 chrome:Uncaught TypeError: Cannot redefine property: name
  configurable: true
})
複製代碼

沒法將一個不可配置的屬性變爲可配置,相反則能夠。

3.6.2 數據屬性特徵

數據屬性額外擁有兩個訪問器屬性不具有的特徵。

  • [[Value]] 包含屬性的值(哪怕是函數)。
  • [[Writable]] 布爾值,指示該屬性是否可寫入。全部屬性默認都是可寫的。
var person = {};

Object.defineProperty(person, "name", {
  value: "Nicholas",
  enumerable: true,
  configurable: true,
  writable: true
})
複製代碼

Object.defineProperty() 被調用時,若是屬性原本就有,則會按照新定義屬性特徵值去覆蓋默認屬性特徵(enumberableconfigurablewritable 均爲 true)。

但若是用該方法定義新的屬性時,沒有爲全部的特徵值指定一個值,則全部布爾值的特徵值會被默認設置爲 false。即不可枚舉、不可配置、不可寫的。

當你用 Object.defineProperty() 改變一個已有的屬性時,只有你指定的特徵會被改變。

3.6.3 訪問器屬性特徵

訪問器屬性額外擁有兩個特徵。[[Get]][[Set]],內含 gettersetter 函數。

使用訪問器屬性特徵比使用對象字面形式定義訪問器屬性的優點在於:能夠爲已有的對象定義這些屬性。然後者只能在建立時定義訪問器屬性

var person = {
  _name: "Nicholas"
};

Object.defineProperty(person, "name", {
  get: function(){
    return this._name;
  },
  set: function(value){
    this._name = value;
  },
  enumerable: true,
  configurable: true
})

for(var x in person){
  console.log(x); // _name \n(換行) name(訪問器屬性)
}
複製代碼

設置一個不可配置、不可枚舉、不能夠寫的屬性:

Object.defineProperty(person, "name",{
  get: function(){
    return this._name;
  }
})
複製代碼

對於一個新的訪問器屬性,沒有顯示設置值爲布爾值的屬性,默認爲 false

3.6.4 定義多重屬性

Object.defineProperties() 方法能夠定義任意數量的屬性,甚至能夠同時改變已有的屬性並建立新屬性。

var person = {};

Object.defineProperties(person, {

  // data property to store data
  _name: {
    value: "Nicholas",
    enumerable: true,
    configurable: true,
    writable: true
  },

  // accessor property
  name: {
    get: function(){
      return this._name;
    },
    set: function(value){
      this._name = value;
    }
  }
})
複製代碼

3.6.5 獲取屬性特徵

Object.getOwnPropertyDescriptor() 方法。該方法接受兩個參數:對象和屬性名。若是屬性存在,它會返回一個屬性描述對象,內涵 4 個屬性:configurableenumerable,另外兩個屬性則根據屬性類型決定。

var person = {
  name: "Nicholas"
}

var descriptor = Object.getOwnPropertyDescriptor(person, "name");

console.log(descriptor.enumerable); // true
console.log(descriptor.configuable); // true
console.log(descriptor.value); // "Nicholas"
console.log(descriptor.wirtable); // true
複製代碼

3.7 禁止修改對象

對象和屬性同樣具備指導其行爲的內部特性。

其中, [[Extensible]] 是布爾值,指明該對象自己是否能夠被修改。默認是 true。當值爲 false 時,就能禁止新屬性的添加。

建議在 "use strict"; 嚴格模式下進行。

3.7.1 禁止擴展

Object.preventExtensions() 建立一個不可擴展的對象(即不能添加新屬性)。 Object.isExtensible() 檢查 [[Extensible]] 的值。

var person = {
  name: "Nocholas"
}

Object.preventExtensions(person);

person.sayName = function(){
  console.log(this.name)
}

console.log("sayName" in person); // false
複製代碼

3.7.2 對象封印

一個被封印的對象是不可擴展的且其全部屬性都是不可配置的(即不能添加、刪除屬性或修改其屬性類型(從數據屬性變成訪問器屬性或相反))。只能讀寫它的屬性

Object.seal()調用此方法後,該對象的 [[Extensible]] 特徵被設置爲 false,其全部屬性的 [[configurable]] 特徵被設置爲 false

Object.isSealed() 判斷一個對象是否被封印。

3.7.3 對象凍結

被凍結的對象不能添加或刪除屬性,不能修改屬性類型,也不能寫入任何數據屬性。簡言而之,被凍結對象是一個數據屬性都爲只讀的被封印對象。

  • Object.freeze() 凍結對象。
  • Object.isFrozen() 判斷對象是否被凍結。

Object.freeze()Object.seal()更嚴格,它阻止更改任何現有屬性

3.8 總結

  • in 操做符檢測自有屬性和原型屬性,而 hasOwnProperty() 只檢查自有屬性。
  • delete 操做符刪除對象屬性。
  • 屬性有兩種類型:數據屬性和訪問器屬性。
  • 全部屬性都有一些相關特徵。[[Enumerable]][[Configurable]] 的兩種屬性都有的,而數據屬性還有 [[Value]][[Writable]],訪問器屬性還有 [[Get]][[Set]]。可經過 Object.defineProperty()Object.defineProperties() 改變這些特徵。用 Object.getOwnPropertyDescriptor() 獲取它們。
  • 3 種能夠鎖定對象屬性的方式。
相關文章
相關標籤/搜索