變量、做用域與內存問題

變量、做用域與內存問題

(一)變量的那些事瀏覽器

  按照 ECMA-262的定義,JavaScript 的變量與其餘語言的變量有很大區別。JavaScript 變量鬆散類型的本質,決定了一個變量只是在特定時間用於保存特定值而已,這點與其餘語言不一樣,好比說 Java 定義一個變量必須同時指定它的數據類型,一旦指定了就不能儲存其餘類型的數據了。因爲不存在定義某個變量必需要保存何種數據類型值的規則,變量的值及其數據類型能夠在腳本的生命週期內改變。這是多爽的一件事兒~~函數

  ES 的變量可能包含幾種類型的值,好比說,基本類型或者引用類型,在前面也提到過,基本類型就是保存簡單的數據,好比說字符串,數值等,而引用數據類型保存的是複雜的數據。將一個值賦值給變量時,解析器必須肯定這個值是基本類型值仍是引用類型值。基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值;而引用類型是按引用訪問的。定義基本類型值和引用類型值的方式是相似的:建立一個變量併爲該變量賦值,可是,當這個值保存到變量中之後,對不一樣類型值能夠執行的操做則截然不同。對於引用類型的值,咱們能夠爲其添加屬性和方法,也能夠改變和刪除其屬性和方法;可是不能給基本類型的值添加屬性,儘管這樣作不會致使任何錯誤spa

  除了保存的方式不一樣以外,在從一個變量向另外一個變量複製基本類型值和引用類型值時,也存在不一樣。若是從一個變量向另外一個變量複製基本類型的值,會在變量對象上建立一個新值,而後把該值複製到爲新變量分配的位置上。好比:指針

let num = 1
let num2 = num

上面兩個變量保存的值都是1,可是他們之間是相互獨立,沒有半毛錢關係的,改變其中的某一個變量不會引發另一個變量的改變。那這裏面具體都作了什麼事呢?首先,在棧(一種內存結構)中建立一個變量 num ,這個變量保存的值是 1 ,而後,一樣的,在棧中建立另一個變量 num2 ,他保存的值跟此刻 num 變量的值一致,說白的了就是直接複製一份拿過來,哦,你保存的是1啊,那我這也弄一個1,這兩個保存的數據1是相互獨立,互不影響的。code

  當從一個變量向另外一個變量複製引用類型的值時,一樣也會將存儲在變量對象中的值複製一份放到爲新變量分配的空間中。不一樣的是,這個值的副本其實是一個指針,而這個指針指向存儲在堆(一種內存結構)中的一個對象。這裏也說明了,對於引用對象,在棧內存中保存的是一個指針,這個指針就指向了堆內存中一個空間,因此,當從一個變量向另外一個變量複製引用類型的值時,就會將這個指針複製過去,這就致使着能夠有多個變量來共同操做同一個對象,由於他們保存的指針是相同的,所以,經過一個變量改變這個對象,再經過其餘對象去訪問就會獲得剛纔改變後的結果,由於他們訪問和修改的都是同一個對象:對象

這裏的幾行代碼到底作了什麼事呢?首先,棧內存中建立一個變量 person ,堆內存開闢了一個空間,該空間對象的指針保存在棧內存的 person 變量中,而後在堆內存裏面保存着對象的屬性名和屬性值;而後,第二行代碼,在棧內存中建立變量person2 ,而且將變量person 保存的指針複製了一份拿過來;第三行代碼,經過變量 person2 的指針去操做對應的對象,由於 person 跟 person2 保存的指針是同樣的,因此經過其中一個變量去修改對象,而後另外一個變量再去讀取這個對象時,就會獲得修改後的結果。這裏舉個例子,指就像一把鑰匙,對象就是一個箱子,並且是一個上了鎖的箱子, 第一步,使用一個變量保存一個對象,就像給了這把鑰匙給這個對象,第二步,將一個保存着對象的變量賦值給另一個變量時,就等於把鑰匙複製了一條給了這個變量,這就至關於有兩個變量拿着兩條能開同一個箱子的鑰匙,因此,經過其中一個鑰匙去開,而後翻動箱子裏面的東西(修改對象的屬性),而後再由另一條鑰匙去開,確定獲得的是被翻動(修改後)的箱子。blog

  關於變量,最後說一點也是常常被誤會被質疑的一點 —— 函數傳遞參數的方式究竟是值傳遞仍是引用傳遞?生命週期

  首先,擺明觀點, ES 中的全部函數傳遞參數的方式都是值傳遞!也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另外一個變量同樣。若是是基本數據類型,直接複製一份基本類型的值;若是是引用類型,則複製一個引用!ip

對於基本類型而言,直接複製一份數據給參數,因此這是兩個獨立的數據,在函數內部修改的數據不會對函數外部的數據產生影響;然而對於引用類型,倒是不同了:內存

由於給參數傳遞的是這個變量保存的值,也就是指向 person 對象的一個指針,因此,在函數內部修改這個對象,在外部必然會獲得修改後的結果。但是您有可能還會以爲這就是一個引用在傳遞,然而不是這樣的:

這個例子就很好的說明了並無把引用傳遞過去,而僅僅傳遞的是變量保存的值,這個變量着什麼值就傳遞什麼值過去。

 

(二)執行環境與做用域

  執行環境(有時候也簡稱環境),是 JavaScript 中最重要的一個概念之一。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。全局執行環境就是最外圍的一個執行環境,根據 ECMAScript 實現所在的宿主環境不一樣,表示執行環境的對象也不同。在 Web 瀏覽器中,全局執行環境被認爲是 window 對象。所以全部全局變量和函數都是做爲 window 對象的屬性和方法建立的。執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬,全局執行環境直到應用程序退出,例如關閉網頁或瀏覽器纔會被銷燬。

  除了全局執行環境之外,每一個函數都有一個執行環境,當執行流進入一個函數時,函數的環境就會被推入一個環境棧中,這稱爲壓棧;而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境,稱爲出棧。當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈,做用域鏈的用途是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的始端,始終都是當前執行的代碼所在執行環境的變量對象,做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。也就是說,當在一個執行環境中讀取(查找)一個變量時,首先會在自身的變量對象上尋找,若是找到,則返回對應的值;若是沒有,則去下一個包含對象上尋找,一樣的,找到則返回,沒有找到則繼續下一個包含對象,直到找到全局對象爲止,若是全局對象也沒有,則返回 undefined ,這就是做用域鏈。

 

(三)垃圾回收機制

  JavaScript 具備自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程當中使用的內存。而在 C 或 C++語言中,還須要咱們手動的去跟蹤內存的使用狀況。JavaScript 的垃圾收集機制的原理其實很簡單:找出那些再也不繼續使用的變量而後釋放其佔用的內存。爲此,垃圾收集器會按照固定的時間間隔,週期性地執行這一操做。

  局部變量只在函數執行的過程當中存在,而在這個過程當中,會爲局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。而後在函數中使用這些變量,直至函數執行結束。此時,局部變量就沒有存在的必要了,所以能夠釋放它們的內存以供未來使用。在這種狀況下,很容易判斷變量是否還有存在的必要;但並不是全部狀況下都這麼容易就能得出結論。垃圾收集器必須跟蹤哪一個變量有用哪一個變量沒用,對於再也不有用的變量打上標記,以備未來收回其佔用的內存。

  JavaScript 中最經常使用的垃圾收集方式是標記清除。當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲「離開環境」。垃圾收集器在運行的時候會給存儲在內存中的全部變量都加上標記。而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。最後,垃圾收集器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。

相關文章
相關標籤/搜索