先從計算機角度說一下內存:內存,包括三個部分:只讀存儲器(ROM)、隨機存儲器(RAM)和高速緩衝存儲器(Cache)。javascript
而其中,高速緩衝存儲器(Cache)又分爲三種:一級緩存(L1 Cache)、二級緩存(L2 Cache)、三級緩存(L3 Cache)。java
當CPU須要數據的時候,就會先找緩存,由於緩存最快,緩存找不到,纔去找慢一點的內存,而後找到後繼續將數據放入緩存,下次還要的時候,仍是先找緩存。git
簡單來講,緩存中的數據只是內存的一部分,可是讀寫速度最快,因此CPU先找它。github
扯遠了,回過頭來。算法
再解釋一下常說的「堆內存」、「棧內存」,「棧內存」也叫作「堆棧內存」,不是兩個一塊兒的總稱,實際上就是棧內存,因此我這裏就分別說「堆內存」和「棧內存」。緩存
這裏面就有「堆」和「棧」兩個概念了。網絡
先說說棧,它是放在是在於一級緩存中的,數據被調用的時候放入存儲空間中,而後被調用完成時候,就會被馬上釋放。函數
再說堆,它是放在二級緩存中的,它的生命週期由虛擬機的垃圾回收算法來決定。因此調用這些對象的速度要相對來得低一些。this
舉個很簡單的例子來解釋「堆」和「棧」這兩個概念:spa
棧,有點像漢諾塔那樣的套圈圈,一圈一圈地放上去,前面放的都被壓在了底部,後面的就壓在上面,一層一層疊羅漢,可是取出來的時候,就是後面放上去的先取出來,越早放進去的越後取出來,簡單來講,就是遲來先上岸。
堆,就像是一堆東西那些,就好像你雜亂的房間,一堆雜物,你想找東西,翻來翻去,找到了,能夠拿走,有些東西,你不拿走,放在那裏,其實就是垃圾。
通常來講,javascript中的數據類型分爲基本數據類型和引用數據類型,而基本數據類型中的變量名和變量直接存放在棧內存中,而引用數據類型的變量值其實是存放的一個地址指針,因此它的變量名和變量值也是存放在棧內存中,而地址指向的實際內容,則是存放在堆內存中。
好了,開始說一下執行上下文了。
有些人會混淆執行上下文和做用域的概念,後面的文章我會說到做用域和做用域鏈,如今先說執行上下文。
從執行上下文的生命週期來講,包括三個部分:
一、建立階段;二、執行階段;三、執行完畢階段。
1、建立階段
執行上下文是在函數被調用的時候才建立,主要有三個內容:
一、建立變量對象;二、初始化做用域鏈;三、肯定this的指向。
2、執行階段
發生在函數代碼執行階段,主要有三個內容:
一、變量賦值;二、函數引用;三、執行其餘代碼。
3、執行完畢階段
主要內容:執行完畢後跳出執行上下文棧,等待被回收。
關於建立階段和執行階段的具體內容,可能你們會有疑惑,裏面的具體內容後面文章會慢慢細說。這裏單純說說執行上下文棧。
舉個簡單例子,函數裏面也會有嵌套函數的狀況,就像這樣:
//函數father function father(age){ var me = age + 20; //函數son function son(age){ return age; } return son(me); } father(33);
既然執行上下文是函數被調用的時候建立的,那麼上面這個father函數被調用以後,而後son也被調用了,那它們的執行上下文是什麼關係呢?
在這裏,Javascript會利用執行上下文棧(Execution context stack,ECS)來管理執行上下文。
回想一下前面棧內存的概念就很容易理解。
當調用某個函數的時候,就會建立執行上下文,並壓入執行上下文棧中,當執行完畢的時候,就會從執行上下文棧中跳出,等待被回收。像上面這種函數內嵌套函數的情形,調用father函數的時候,father建立的執行上下文壓入棧中,而後開始執行father的函數體內代碼,由於father函數還沒執行完畢,因此調用son函數時候會將son建立的執行上下文壓入棧中,當son執行完畢,就會跳出,而後father執行完畢,繼續跳出。這就完成了整個father的執行上下文週期。
仍是那句,遲來先上岸的感受。就好像下面的圖這樣(圖片引用自網絡),下面就是一個執行上下文棧,最底層確定是全局了,而後只要函數沒執行完畢繼續在函數內調用其它函數的話,其它函數的執行上下文就會接着壓上去,最後執行完畢,壓在最上面的上下文先清出,而後其它執行上下文又變成最上面的了,而後執行完畢,繼續清出,就和圖那樣了。
實際狀況可能不是圖的那樣簡單,可能清出到EC2那一層的時候,還沒執行完這個函數,又調用其它函數,其它的執行上下文又接着壓上去了。
固然,道理都是同樣的。