JavaScript 對象屬性底層原理

對象屬性類型

1. 數據屬性

  • [[Configurable]]:表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性,特性默認值爲true
  • [[Enumberable]]:表示可否經過for-in循環返回屬性,特性默認值爲true
  • [[Wtiteable]]:表示可否修改屬性的值,特性默認值爲true
  • [[Value]]:包含這個屬性的數據值,讀取屬性值/寫入屬性值,從這個位置讀/把新值保存在這個位置,這個特性的默認值爲undefined

2. 訪問器屬性

  • [[configurable]]: 表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性,特性默認值爲true
  • [[Enumberable]]:表示可否經過for-in循環返回屬性,特性默認值爲true
  • [[Get]]:在讀取屬性時調用的函數,默認值爲undefined
  • [[Set]]:在寫入屬性時調用的函數,默認值爲undefined

3. 定義對象的訪問器屬性 - Object.defineProperty

var book = {
    _year: 2004,
    edition: 1
};

Object.defineProperty(book, "year", {
    get: function(){
        return 2018;
        },
    set: function(newValue){
        if (newValue > 2004) {
        this._year = newValue;
        this.edition += newValue - 2004;
        }
    }
});
console.log(book.year);//2018
book.year = 2005;
console.log(book.year);//2018

對象建立底層分析

1. 對象建立

爲何下面第一種方式會報錯而第二種不會?javascript

//構造函數建立
var object=new Object();
object.x=1;
object.2=1; //Unexpected number
//字面量建立
var object = {
  x: 1,
  2: 2
};//不報錯

分析以下:html

window.2=2; //假設不會出錯
2==2;//這個地方應該怎麼處理?--無法處理

那麼字面量爲何不會出錯?往下看
當啓用慢模式時以Hash做爲底層存儲結構,key爲字符串,字面量方式會存在類型轉換。java

2. 對象屬性詳解 Chrome(V8)

2.1 V8中的快速屬性

對象大多數時候表現爲Dictionary:以字符串爲key,任意object爲值。可是...往下看chrome

命名屬性:
如:{a:'foo',b:'bar'}數組

  • 存儲結構能夠是數組也能夠是HashMap
  • 具備額外的輔助信息(存儲在描述符數組中)

數組索引屬性(元素):
如:數組['foo','bar']有兩個數組索引屬性:0,值爲'foo'; 1,值爲'bar'。緩存

  • 存儲結構一般爲簡單的數組結構。但某些狀況下也會切換到Hash結構以節省內存。
  • 能夠使用鍵來推斷它們在屬性數組中的位置

數組索引屬性和命名屬性存儲在兩個單獨的數據結構中:
數據結構

2.2 隱藏類和描述符數組

每一個JS對象都有一個隱藏類與之關聯。
隱藏類存儲有對象結構信息(屬性數和對對象原型的引用),以及從屬性名稱到屬性的索引映射。
隱藏類是動態建立的,並隨着對象的變化而動態更新。ide

在V8中,位於堆內存並由GC管理的全部JS對象的第一個字段都指向隱藏類。
隱藏類存儲中包含屬性的數量,和一個指向描述符數組的指針。
在這個描述符數組中包含有命名屬性的信息,例如命名屬性的名稱和存儲屬性值的位置。函數

注意:具備相同結構的JS對象(相同順序和相同命名的屬性),他們的隱藏類會指向同一個,以此達到複用的目的。對於不一樣結構的JS對象將使用不一樣的HiddenClass。this

每次添加新屬性時,都會更改對象的HiddenClass。V8維護了一個把HiddenClasses連接在一塊兒的轉換樹。按相同屬性添加順序將獲得同樣的隱藏類。

若是咱們建立一個添加了不一樣屬性的新對象('d'),則會建立一個單獨的隱藏類分支。

結論:
具備相同結構的對象(相同屬性的相同順序)具備相同的HiddenClass
默認狀況下,每一個添加的新命名屬性都會致使建立一個新的HiddenClass。
添加數組索引屬性不會建立新的HiddenClasses。

2.3 三種不一樣的命名屬性

  1. 內嵌屬性與普通屬性:
    • V8支持對象內屬性,存儲在對象自己,能夠直接訪問,速度最快。
    • 內嵌屬性的數量由對象的初始大小預先肯定。
    • 若是添加的屬性多於對象中的空間,則它們將存儲在隱藏類鏈上,由隱藏類指向的一個屬性數組。

  1. (普通屬性中的)快屬性與慢屬性:
    • 直接存儲在屬性數組(如上圖中的Properties結構)中的屬性爲'快屬性'。可經過屬性數組中的索引訪問,若要從屬性名稱獲取屬性數組中的實際位置,必須查看HiddenClass上的描述符數組才能知道(如上)。
    • 慢屬性使用HashMap做爲屬性存儲,全部屬性元信息再也不存儲在HiddenClass上的描述符數組中,而是直接存儲在屬性Hash中(沒有緩存,因此叫慢屬性)。

注意:過多的添加或刪除屬性,會從快屬性模式切換爲慢屬性模式。

結論:
三種不一樣的命名屬性類型:in-object,fast和slow(dictionary)。
內嵌屬性直接存儲在對象自己上,並提供最快的訪問。
快速屬性存在於屬性存儲中,全部元信息都存儲在HiddenClass上的描述符數組中。
慢屬性存在於自包含的屬性字典中,再也不經過HiddenClass共享元信息。
慢屬性提供有效的屬性刪除和添加,但訪問速度比其餘兩種類型慢。

2.4 數組索引屬性

  1. 連續和有缺口的數組索引屬性:
    若是刪除索引元素,或者例如沒有定義它,則會在連續存儲中出現漏洞。一個簡單的例子是[1,,3],其中第二個項是一個缺口。
const o = ["a", "b", "c"];
console.log(o[1]);          // Prints "b".

delete o[1];                // Introduces a hole in the elements store.
console.log(o[1]);          // Prints "undefined"; property 1 does not exist.
o.\_\_proto\_\_ = {1: "B"};     // Define property 1 on the prototype.

console.log(o[0]);          // Prints "a".
console.log(o[1]);          // Prints "B".
console.log(o[2]);          // Prints "c".
console.log(o[3]);          // Prints undefined

當有缺口的時候會在該位置打上the_hole標記表示不存在的屬性,能夠大大提升數組操做效率。

  1. 快速數組索引屬性或Hash數組索引屬性:
    快速數組索引屬性是簡單的VM內部數組,其中屬性索引映射到數組索引屬性存儲中的索引。可是,該結構對於較大的數組但佔用元素較少的狀況至關浪費內存,這種狀況會使用HashMap來節省內存,但代價是訪問速度稍慢。
const sparseArray = [];
sparseArray [9999] ='foo'; //建立一個包含字典元素的數組。

注意:只要使用自定義描述符定義索引屬性,V8就會轉向慢數組索引屬性:

const array = [];
Object.defineProperty(array, 0, {value: "fixed", configurable: false});
console.log(array[0]);      // Prints "fixed".
array[0] = "other value";   // Cannot override index 0.
console.log(array[0]);      // Still prints "fixed".

refs:
http://www.javashuo.com/article/p-sscrflrf-h.html
https://v8project.blogspot.com/2017/08/fast-properties.html

相關文章
相關標籤/搜索