死磕JavaScript-鬆散型別、js變數儲存模型、變數提升

joytoy發表於2021-09-09

好久沒來慕課網學習了,上研究生之後,發現突然又變回學生後對自己的要求也松很多,開始到處旅遊,做些沒計劃的事情,也很少寫技術部落格了,最近靜下心來開始研究底層的東西,以後就在這寫了,希望能死磕自己,堅持下去。好了,乾貨走起......

  • 什麼是鬆散型別

  • JavaScript兩種變數型別的記憶體模型

  • 預載入

  • 變數提升

    javascript裡的變數和其他語言有很大的不同,javascript的變數是一個鬆散的型別,鬆散型別變數的特點是變數定義時候不需要指定變數的型別,變數在執行時候可以隨便改變資料的型別,但是這種特性並不代表javascript變數沒有型別,當變數型別被確定後javascript的變數也是有型別的。

但是在現實中,很多程式設計師把javascript鬆散型別理解為了javascript變數是可以隨意定義即你可以不用var定義,也可以使用var定義,其實在javascript語言裡變數定義沒有使用var,變數必須有賦值操作,只有賦值操作的變數是賦予給window,這其實是javascript語言設計者提升javascript安全性的一個做法。

此外javascript語言的鬆散型別的特點以及執行時候隨時更改變數型別的特點,很多程式設計師會認為javascript變數的定義是在執行期進行的,更有甚者有些人認為javascript程式碼只有執行期,其實這種理解是錯誤的,javascript程式碼在執行前還有一個過程就是:預載入,預載入的目的是要事先構造執行環境例如全域性環境,函式執行環境,還要構造作用域鏈,而環境和作用域的構造的核心內容就是指定好變數屬於哪個範疇,因此在javascript語言裡變數的定義是在預載入完成而非在執行時期。

講一個例子來講解:

var a = 1;
function test(){
    console.log(a);//undefined
    var a = 2;
    console.log(a);//2
}
test();

這是一個令人詫異的結果,為什麼第一個彈出框顯示的是undefined,而不是1呢?這種疑惑的原理我描述如下:

一個頁面裡直接定義在script標籤下的變數是全域性變數即屬於window物件的變數,按照javascript作用域鏈的原理,當一個變數在當前作用域下找不到該變數的定義,那麼javascript引擎就會沿著作用域鏈往上找直到在全域性作用域裡查詢,按上面的程式碼所示,雖然函式內部重新定義了變數的值,但是內部定義之前函式使用了該變數,那麼按照作用域鏈的原理在函式內部變數定義之前使用該變數,javascript引擎應該會在全域性作用域裡找到變數定義,而實際情況卻是變數未定義,這到底是怎麼回事呢?

這裡我要先講一個知識點,就是JavaScript的變數儲存模型。

javascript語言和java語言一樣變數是分為兩種型別:基本資料型別和引用型別。基本型別是指:Undefined、Null、Boolean、Number和String;而引用型別是指物件,所以javascript的物件指的是引用型別。但是實際開發裡如果我們對基本型別和引用型別的區別不是很清晰,就會碰到我們很多不能理解的問題,下面我們來看看下面的程式碼:

 var str = “Sharpxiajun";
    var num = 1;
    var xxx;
    console.log(str);//執行結果:sharpxiajun
    console.log(num);//執行結果:1
    console.log(xxx);//執行結果:undefined
當我們使用引用型別時候,結果就和上面完全不同了,大家請看下面的程式碼:
var obj1 = new Object();
obj1.name = "obj1 name”;
console.log(obj1.name);// 執行結果:obj1 name
Javascript裡的基本變數是存放在棧區的(棧區指記憶體裡的棧記憶體),它的儲存結構如下圖所示:

圖片描述

javascript裡引用變數的儲存就比基本型別儲存要複雜多,引用型別的儲存需要記憶體的棧區和堆區(堆區是指記憶體裡的堆記憶體)共同完成,如下圖所示:
圖片描述

理解基本型別變數和引用型別變數的儲存結構後,結合上面開始講的預載入的知識點,我們就能分析出開始那個例子的深層原因了。

引子裡的程式碼在函式的區域性作用域下變數a被重新定義了,在預載入時候a的作用域範圍也就被框定了,a變數不再屬於全域性變數,而是屬於函式作用域,只不過賦值操作是在執行期執行(這就是為什麼javascript語言在執行時候會改變變數的型別,因為賦值操作是在執行期進行的),所以第一次使用a變數時候,a變數在區域性作用域裡沒有被賦值,只有棧區的標示名稱,因此結果就是undefined了。(這也就是js裡的變數提升的原理)

不過賦值操作也不是完全不對預載入產生影響,預載入時候javascript引擎會掃描所有程式碼,但不會執行它,當預載入掃描到了賦值操作,但是賦值操作的變數有沒有被var定義,那麼該變數就會被賦予全域性變數即window物件。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4650/viewspace-2798793/,如需轉載,請註明出處,否則將追究法律責任。

相關文章