在JS中,對象是一組無序屬性的集合。其中,屬性能夠是基本數據類型、引用類型、函數。以下面這個對象的例子:javascript
var chai={ name : "柴毛毛", // 屬性爲基本數據類型 perosn : { // 屬性爲引用類型 address : "xxx", sex : "man" }, getName : function () { // 屬性爲函數 return this.name; } }
也就是說,JS中的對象相似於Java中的Map,由鍵值對構成;其中鍵是字符串類型的屬性名,值能夠爲上述三種類型中的任意類型。html
JS中建立對象的方法有不少,各有千秋。這篇博客主要介紹對象建立過程當中的內存模型,所以只介紹經過構造函數建立對象的方法,其他方法期待下一篇博客吧。java
function Person (name,age) { this.name = name; this.age = age; this.getName = function(){ return name; } }
var person1 = new Person('柴毛毛',18);
經過上述兩步後,就能建立一個Person對象,並由變量person1指向,經過該變量就能訪問這個對象的全部屬性。瀏覽器
以上的概念較爲基礎,點到爲止,爲下面講解對象的內存模型做鋪墊,OK,重頭戲來了。markdown
咱們知道,構造函數也是函數,全部函數在被執行前都須要被初始化,函數初始化也就是建立該函數對象的過程。那麼函數何時會被初始化呢?函數
當構造函數所在的外層函數被執行時,JS引擎會爲該外層函數建立一個執行環境,並壓入執行環境棧中。這個過程稱爲函數執行環境的準備階段。
若構造函數以「函數聲明」的方式定義,那麼構造函數的初始化在外層函數的環境準備階段完成;
若構造函數是以「函數表達式」的方式定義,那麼只有當JS引擎執行到這一行代碼的時候纔會初始化構造函數。post
咱們知道,JS中的函數就是一個對象,所以函數的初始化過程其實就是建立了一個函數對象。this
PS:不單單是構造函數,全部函數初始化發生的時機都是如此。
綜上所述:
函數初始化的時機與函數所在的外層函數有關,也與函數的定義方式有關。
若函數以「函數聲明」的形式定義,那麼該函數的初始化在其外層函數執行前(即外層函數執行環境的準備階段)完成;
若函數以函數表達式的形式定義,那麼該函數的初始化在其外層函數執行到這行代碼的時候完成。spa
當構造函數的對象建立完成後,JS引擎隨之會爲該對象建立一個原型對象。
原型對象默認只有一個屬性「construcor」,指向它的構造函數對象。
而每一個函數對象默認都有一個prototype屬性,該屬性指向它的原型對象。
此時,內存中有以下對象:
PS:不單單是構造函數,全部函數初始化完成以後在內存中都有上述結構。
全部函數初始化完成後都會建立一個函數對象和一個原型對象,而且經過prototype、construcor屬性相互引用。.net
var person1 = new Person('柴毛毛',18);
當經過關鍵字new建立一個對象時,JS引擎會作以下幾件事:
var object = new Object();
這個object對象將會有一個指向原型對象的屬性,它是一個隱式屬性,咱們沒法經過JS代碼訪問,但JS引擎能夠訪問。
大多數瀏覽器將它定義爲「proto」。
此時內存中一共存在三個對象,分別是:構造函數自己的函數對象、構造函數的原型對象、構造函數的實例對象。它們之間經過prototype、proto、constructor指針相互引用。
- 準備構造函數的執行環境(變量對象、做用域鏈、執行環境)
對象建立完後,就要開始執行構造函數中的代碼。但在執行函數以前,首先須要準備函數的執行環境:
(本過程詳解請參閱:穩紮穩打JavaScript——做用域鏈)
* 建立構造函數的變量對象(用於存儲函數執行過程當中的全部變量,包括this和arguments)
* 建立構造函數的做用域,壓入做用域鏈的頭部,而且指向剛纔建立的變量對象;(函數的做用域鏈在函數初始化時就已建立)
* 建立構造函數的執行環境,並指向該函數的做用域鏈
此時內存模型以下:
到此爲止,內存中存在兩大塊內容,一塊是用於執行構造函數的「構造函數執行環境」,它包括:構造函數的執行環境、構造函數的做用域鏈、構造函數的變量對象。
另外一塊內存空間存儲了與建立對象相關的內容,包括:構造函數自己的函數對象、構造函數的原型對象、構造函數的實例對象。
但此時這兩塊內存間並無聯繫,接下來this就要出場了。
function Person (name,age) { this.name = name; this.age = age; this.getName = function(){ return name; } }
此時,JS引擎將會逐行執行構造函數中的代碼。
好比,當代碼執行到this.name=name時,JS引擎首先會在當前做用域鏈中尋找變量this。咱們知道,查找變量首先從做用域的頭部開始,所以首先尋找下標爲0的變量對象;在該變量對象中找到了this,所以查找成功,緊接着在this指向的對象中建立name屬性,並賦上局部變量name的值。
當構造函數中的代碼執行完畢,內存模型以下:
構造函數建立的那個對象中多出了幾個屬性。
先來複習下JS的變量查找過程(本過程詳解請參閱:穩紮穩打JavaScript——做用域鏈)。
仍以上述代碼爲例:
function Person (name,age) { this.name = name; this.age = age; this.getName = function(){ return name; } } var person1 = new Person('柴毛毛',18);
咱們知道,經過構造函數建立一個對象時,會發生如下幾件事:
1. 建立一個object對象
2. 準備構造函數的執行環境
3. 將構造函數的this指向object對象
4. 執行構造函數
5. 返回object對象
也就是說,經過new建立一個對象本質上仍然是把構造函數看成普通函數執行,只不過在構造函數執行前增長了一句var object=new Object();而且在構造函數執行結束後返回這個object;其他過程就是在執行一個普通函數。
那麼在函數執行過程當中,若是須要用到局部變量,就會發生變量查找。
當執行代碼「this.name = name」時,這句代碼涉及到兩個局部變量:this、name。
以查找this爲例,JS引擎會沿着當前函數的做用域鏈依次查找變量對象。若在某一個做用域指向的變量對象中找到this,則查找成功;不然就繼續沿着做用域鏈向後查找。
變量查找是在做用域鏈上進行,而屬性查找是在原型鏈上進行的。
繼續上述例子。當JS引擎在某個變量對象中找到this後,變量查找的過程結束,若查找的是一個基本數據類型的變量,那麼查找結束;若查找的變量是一個對象,而且須要對該對象的屬性進行操做,那麼接下來就要進入屬性查找的過程。
咱們知道,構造函數執行前會將它的this指向構造函數的實例對象,所以,當執行「this.name」時,JS引擎就會在this指向的實例對象中查找。若在this指向的實例對象中查找不到,就會繼續查找該實例對象proto屬性指向的原型對象,若該原型對象中沒有,則繼續查找原型對象的原型對象,直到查找成功或找到Object的原型對象爲止。