注意: 本文章爲 《重學js之JavaScript高級程序設計》系列第四章【變量、做用域和內存問題】。 關於《重學js之JavaScript高級程序設計》是從新回顧js基礎的學習。前端
ES變量包含兩種不一樣數據類型的值:基本類型值和引用類型值。vue
將一個值賦給變量的時候,解析器必須肯定這個值是基本類型仍是引用類型web
指的是那些保存在棧內存中的簡單數據,即這種值徹底保存在內存中的一個位置。一下五種爲基本類型值,它們在內存中分別佔有固定大小的空間,所以它們的值能夠保存在棧內存中。這樣也能夠提升查詢變量的速度。對於保存基本類型值的變量,它們是按值訪問的,咱們操做的是它們實際的值。正則表達式
指的那些保存在堆內存中的對象,意思是變量中保存的實際上只是一個指針,這個指針指向內存中的另外一個位置,該位置保存對象。算法
所以若是想要給一個變量賦值一個引用類型的值,那麼則必須在堆內存中爲這個值分配空間,因爲這種值的大小不固定,所以不能把她保存到棧內存中,可是內存地址的大小是固定的,所以能夠將內存地址保存在棧內存中。這樣,在查詢引用類型的變量時,就能夠首先從棧中讀取內存地址,在找到在堆內存中保存的值。這種訪問方式叫作引用訪問,由於咱們不是操做的實際的值,而是被那個值引用的對象。chrome
定義基本類型值和引用類型值的方式是相似的:建立一個變量併爲該變量賦值。可是,當這個值保存到變量中之後,對不一樣類型值能夠執行的操做則截然不同。對於引用類型的值,咱們能夠爲其添加屬性和方法。也能夠改變和刪除其屬性和方法。編程
var p = new Object()
p.name = 'vue'
alert(p.name) // vue
複製代碼
除了保存方式不一樣以外,從一個變量向另一個變量複製基本類型值和引用類型值時,也存在不一樣。若是從一個變量向另外一個變量複製基本類型的值,會在棧中建立一個新值,而後把該值複製到爲新變量分配的位置上:segmentfault
var n = 5
var n2 = n
// 注意n 和 n2 之間是獨立的。改變n 不會 改變 n2
複製代碼
當一個變量向另外一個變量複製引用類型的值時,一樣也會將存儲在棧中的值複製一份放到爲新變量分配的空間中。不一樣的是,這個值的副本其實是一個指針,而這個指針指向存儲在堆中的一個對象。複製操做結束後,兩個變量其實是引用的同一個對象。所以改變其中一個另一個也會改變。數組
var obj = new Object()
var obj2 = obj1
obj1.name = 'vue'
alert(obj2.name) // vue
複製代碼
ES中 全部函數的參數都是按值傳遞的,也就是說,把函數外部的值 複製給函數內部的參數。瀏覽器
在向參數傳遞基本類型的值時,被傳遞的值會被複制給一個局部變量(即命名參數,或者就是arguments對象中的一個元素),在向參數傳遞引用類型的值時,會把這個值在內存中的地址複製給一個局部變量,所以這個局部變量的變化會反映在函數的外部。
function addTen(num){
num += 10
return num
}
var count = 20
var result = addTen(count)
console.log(count) // 20
console.log(result) // 30
複製代碼
能夠經過 typeof 操做符檢測,在第三章中已經介紹過。固然typeof對基本數據類型的檢測是很好,可是對於引用類型的檢測則有點困難,這時候就能夠經過 instanceof 來檢測
result = variable instanceof constructor
複製代碼
若是變量是給定引用類型的實例,那麼 instanceof 操做符就會返回true,以下:
person instanceof Object // 變量 person 是 Object?
複製代碼
根據規定,全部引用類型的值都是Object的實例,所以,在檢測一個引用類型值和Object構造函數時,instanceof 操做符始終會返回 true,若是使用 instanceof 操做符檢測基本類型的值,則該操做符始終返回 false,由於基本類型不是對象。
注意: 使用 typeof 操做符檢測函數時,該操做符會返回 function 。在 safari 和 chrome 中使用 typeof 檢測正則表達式時,這個操做符會錯誤地返回 function
執行環境是js中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲,每一個執行環境都有一個與之關聯的變量對象。環境中定義的全部變量和函數都保存在這個對象中。
全局執行環境是最外圍的執行環境,根據 ES 實現所在的宿主環境不一樣,表示執行環境的對象也不同,在瀏覽器中,全局執行環境是 window 對象。所以全部全局變量和函數都是做爲 window 對象的屬性和方法建立的,某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬。
每一個函數在被調用時都會建立本身的執行環境,當執行流進入一個函數時,函數的環境就會被推入一個環境棧中,而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。
當代碼在一個環境中執行時,會建立由變量對象構成的一個做用域鏈,做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象做爲變量對象。活動對象子最開始時只包含一個變量,即 arguments對象。做用域鏈中的下一個變量對象來在包含環境,而在下一個變量對象則來自下一個包含環境。這樣一直延續到全局執行環境,全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。
標識符解析沿着做用域一級一級地搜索標識符的過程,搜索過程當中始終從做用域鏈的前端開始,而後逐級地向後回溯,直至找到標識符位置,若是找不到就報錯。
重點: 在一個特定的執行環境中,內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。這些環境之間的聯繫是線性、有次序的,每一個環境均可以向上搜索做用域鏈,以查詢變量和函數名;但任何環境都不能經過向下搜索做用域鏈而進入另一個執行環境。函數的參數也被當作變量來對待,所以其訪問規則與執行環境中的其餘變量相同。
雖然執行環境的類型總共只有兩種---全局和局部(函數),咱們能夠經過其餘辦法來延長做用域鏈。原理就是在做用域鏈的前端臨時增長一個變量對象,該變量對象會在代碼執行後被一處。能夠經過如下兩種方式來實現:
try-catch語句的catch塊
with語句
複製代碼
這個兩個語句都會在做用域鏈的前端添加一個變量對象,對 with 語句來講,其變量對象中包含着爲指定對象的全部屬性和方法所做變量的聲明。對catch 語句來講,其變量對象中包含的事被拋出的錯誤對象的聲明。這些變量都是隻讀的,所以在 with 和 catch 語句中聲明的變量都會被添加到所執行環境的變量對象中。
注意: 在IE的JavaScript實現中,存在一個與標準不一致的地方,即在 catch 語句中捕獲的錯誤對象會被添加到執行環境的變量對象中。也就是說,即便在catch塊的外部也能夠訪問到錯誤對象。
JavaScript 沒有塊級做用域。至少在當前這個版本沒有
if (true) {
var color = 'blue'
}
console.log(color) // blue
// 若是是其餘語言 在 {} 以內的定義 在{} 外部是沒法獲取的
複製代碼
在使用 var 關鍵字聲明變量時,這個變量將被自動添加到距離最近的可用環境中,對於函數而言,這個最近的環境就是函數的局部環境,對於前面例子中 with 語句而言,這個最近的環境也是函數的環境。若是變量未經聲明的狀況下被初始化,那麼該變量會被自動添加到全局環境。
注意: 在編寫 JavaScript 代碼的過程當中,不聲明而直接初始化變量時一個常見的錯誤作法,由於這樣可能會致使意外,咱們建議時在初始化變量以前,必定要先聲明。
當在某個環境中爲了讀取和寫入而引用一個標識符時,必須經過搜索來肯定該標識符實際表明什麼,搜索過程從做用域鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。若是在局部環境中找到了該標識符,搜索過程中止、變量就緒。若是在局部環境中沒有找到該變量名,則繼續沿做用域向上搜索。搜索過程一直追溯到全局環境的變量對象。若是在全局環境中也沒有找到這個標識符,那麼則意味着該變量未聲明。
var color = 'blue'
function getColor () {
return color
}
alert(getColor()) // 'blue'
複製代碼
上述例子, 在調用函數 getColor() 時 會引用變量color,爲了肯定變量color的值,將開始一個兩步搜索過程。首先,搜索 getColor()的變量對象,19查找其中是否包含一個名爲color的標識符,在沒有找到的狀況下,搜索繼續到下一個變量對象(全局環境的變量對象),而後在哪裏找到了名爲 color的標識符。由於搜索到了定義這個變量的變量對象,那麼搜索過程結束。
注意: 若是在搜索過程當中,若是存在一個局部的變量的定義,則搜索會自動中止,不在進入另外一個變量對象。換句話說,若是局部環境中有同名標識符,就不會使用位於父環境中的標識符。
注意: 另外變量查詢也是有代價的,訪問局部變量要比訪問全局變量更快,由於不用向上搜索做用域鏈。
JavaScript 具備自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程當中使用的內存。它的原理是:找出那些再也不繼續使用的變量,而後釋放其佔用的內存。爲此垃圾收集器會按照固定的時間間隔,週期性的執行這一操做。
JavaScript 最經常使用的垃圾收集方式是標記清除,當變量進入環境時,就爲這個變量標記一個 「進入環境」,從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要進入了環境,那麼就表明可能會用到它,當變量離開環境的時候,會被標記上「離開環境」。垃圾收集器運行的時候會給存儲在內存中全部的變量加上標記,而後去掉環境中的變量已經被環境中變量引用的變量標記,那麼剩下來的就是準備刪除的變量。最後垃圾收集器完成內存清除工做。
還有種垃圾收集策略叫作引用計數,就是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1,若是同一個值又被賦給另外一個變量則該值的引用次數加1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數減1.當這個值的引用次數變成 0 時。則說明沒有辦法在訪問這個值了,就能夠將其佔用的內存空間收回。
垃圾收集器時週期性運行的,並且若是爲變量分配的內存數量很可觀,那麼回收工做量也是至關大的。全部就致使性能問題。
因爲JavaScript分配給web瀏覽器的可用內存數量一般要比分配給桌面應用程序的少,這樣作的目的時出於安全方面的考慮。目的是防止運行 JavaScript的網頁耗盡所有系統內存而致使系統崩潰。內存限制問題不只會影響給變量分配內存,同時還會影響調用棧以及在一個線程中可以同時執行的語句數量。
所以,確保佔用最少的內存可讓頁面得到更好的新能,而優化內存佔用的最佳方式,就是爲執行中的代碼只保存必要的數據。一旦數據再也不有用,最好經過將其值設置爲 null 來釋放其引用---- 這個作法叫作 解除引用。這個方法適用大多數全局變量和全局對象的屬性。局部變量會在它們離開執行環境後自動被解除。
注意: 解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行的時候將其回收。
JavaScript變量能夠用來保存兩種類型的值:基本類型和引用類型。基本類型包括: Undefined、Null、Boolean、Number、String。它們具備如下的特色。
全部變量都存在一個執行環境當中,這個執行環境決定了變量的生命週期,以及哪一部分代碼能夠訪問其中的變量。以下:
JavaScript 是一門具備自動垃圾收集機制的編程語言,開發人員沒必要關心內存分配和回收問題,以下:
歡迎關注 公衆號【小夭同窗】
重學js系列
ES6入門系列
Git教程